我读到 C 中的函数可能会使用基于本地堆栈的变量,并且只需将堆栈指针减少所需的空间量即可分配它们。这总是以四字节块的形式完成(如果我没记错的话)。但是,如果运行如下代码怎么办:
void foo(void)
{
char str[6];
......
}
varstr
占用什么大小?根据四字节块的6
字节或字节。6 × 4
我读到 C 中的函数可能会使用基于本地堆栈的变量,并且只需将堆栈指针减少所需的空间量即可分配它们。这总是以四字节块的形式完成(如果我没记错的话)。但是,如果运行如下代码怎么办:
void foo(void)
{
char str[6];
......
}
varstr
占用什么大小?根据四字节块的6
字节或字节。6 × 4
四字节块规则只是意味着堆栈指针必须指向一个四的倍数的地址。在这种情况下,分配 8 个字节满足该规则,并且这样的块足够大,可以容纳一个只有 2 个填充字节的 6 字符数组。
数据对齐是 CPU 要求,这意味着对齐量从一个 CPU 更改为另一个,请记住这一点。
谈到堆栈数据对齐,gcc
例如使用名为-mpreferred-stack-boundary=n
where the data will bealigned to的选项保持数据对齐2^n
。默认情况下, 的值为n
4,这使得堆栈对齐为 16 字节。
这意味着您会发现自己在堆栈内存中分配了 16 个字节,尽管您明确分配的只是一个整数。
int main()
{
char ar[6] = {1,2,3,4,5,6};
int x = 10;
int y = 12 + (int) ar[1] + x;
return y;
}
在我的 CPU 上使用 gcc 编译此代码会生成以下程序集(仅发布堆栈分配指令):
subl $32, %esp
但是为什么是32?我们正在分配恰好适合 16 个字节的数据。好吧,gcc 需要保存 8 个字节leave
,ret
这使得所需的总内存为 24。
但是,对齐要求是 16 字节,因此 gcc 需要分配堆栈空间,以便它由 16 字节块组成; 将 24 字节变为 32 可以解决问题。
您将有足够的空间来存放变量,ret
andleave
和它由两个 16 字节的块组成。
如果你有:
char str[6];
int a;
char b;
char c;
堆栈将有足够的大小来包含所有这些变量,并且可以被 4 整除(或任何需要对齐)。但是每个变量不需要在同一边界上对齐(尽管可能有硬件要求)。
在我的系统上,编译上述内容并打印出堆栈变量的地址(为简洁起见,前导数字被删除):
&str -- 18
&a -- 12
&b -- 10
&c -- 11
即编译器将安排堆栈对齐,但不需要填充变量。
我猜你对数据大小和数据对齐感到困惑。没有一般规则,但是,在现代计算机上,您的变量将存储在 6 个字节中。另一方面,下一个元素不一定会存储在下一个字节中。这称为数据结构填充。
字对齐架构(每个变量都必须从字长倍数的地址开始)变得越来越少见。对于 SPARC 或 x86 等新处理器,变量是自对齐的。这意味着它们必须从一个是其类型大小倍数的地址开始。
因此,在非外来计算机上没有“四字节卡盘规则”。在您的示例中,str
将存储 6 个字节。例如,如果您声明一个对齐为 8 个字节的变量(例如double
在 x86 上),您的编译器将插入 2 个填充字节。
对齐由编译器根据您的体系结构固定。所以标准没有定义任何关于它的东西。您可以在Wikipedia上找到更多信息。
分配 4 字节块的规则并非在所有情况下都有效。例如,ARM eabi 要求对齐 64 位整数并在 8 字节边界上加倍。
通常分配的空间与数据打包成结构的规则相匹配。所以char[6]
实际上需要 6 个字节(通常),但是数据的填充(对于下一个字段)可以使用更多的字节。
例子:
struct X
{
char field1[6];
};
所以结构 X 大小将是8
structure Y
{
char field1[2];
double field2;
};
结构 Y 通常类似于8
,12
或16
字节,具体取决于架构。
相同的规则适用于自动堆栈变量:通常填充不是由您正在使用的类型决定,而是由您将使用的下一个类型决定。规则有时有点模糊。