我读到C中的函数可能使用基于堆栈的本地变量,并且只需通过将堆栈指针递减所需的空间量来分配它们。这总是在四字节块中完成(如果我没有记错的话)。但是,如果运行如下代码:
void foo(void)
{
char str[6];
......
}
var str
的大小是多少?根据四字节块,6
个字节或6 × 4
个字节。
答案 0 :(得分:2)
四字节块规则只意味着堆栈指针必须指向一个4的倍数的地址。在这种情况下,分配8个字节满足该规则,并且这样的块足够大以容纳6个字符的数组,只有2个字节的填充。
答案 1 :(得分:2)
数据对齐是CPU要求,这意味着对齐量会从CPU变为另一个,请记住这一点。
关于堆栈数据对齐,gcc
例如使用名为-mpreferred-stack-boundary=n
的选项保持数据对齐,其中数据将与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需要为leave
和ret
保存8个字节,这使得所需的总内存为24。
但是,对齐要求是16字节,因此gcc需要分配堆栈空间,以便它由16个字节的块组成;使24字节到32解决问题
对于ret
和leave
,您将拥有足够的空间用于变量,并且它由两个16字节的块组成。
答案 2 :(得分:0)
我猜您在数据大小和数据对齐之间感到困惑。没有一般规则,但是,在现代计算机上,您的变量将以6个字节存储。另一方面,下一个元素不一定存储在下一个字节中。这称为数据结构填充。
字对齐体系结构,其中每个变量必须从字长的倍数开始,变得越来越少。使用SPARC或x86等新处理器,变量自对齐。这意味着他们必须从一个地址开始,该地址是其类型大小的倍数。
因此,在非外来计算机上没有“四字节吸盘规则”。在您的示例中,str
将以6个字节存储。例如,如果声明一个对齐为8字节的变量(例如x86上的double
),则编译器将插入2个填充字节。
根据您的体系结构,编译器会修复对齐方式。所以标准没有定义任何关于它的内容。您可以在Wikipedia找到更多信息。
答案 3 :(得分:0)
以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
字节,具体取决于架构。
相同的规则应用于自动堆栈变量:通常填充不是由您使用的类型决定的,而是由您将要使用的下一种类型决定。规则有时候有点模糊。
答案 4 :(得分:0)
如果你有:
char str[6];
int a;
char b;
char c;
堆栈的大小足以包含所有这些变量,并且可以被4整除(或者需要任何对齐)。但是每个变量不需要在同一边界上对齐(尽管可能有硬件要求)。
在我的系统上,编译上面的内容并打印出堆栈变量的地址(为简洁起见,删除了前导数字):
&str -- 18
&a -- 12
&b -- 10
&c -- 11
即编译器会安排堆栈对齐,但不需要填充变量。