什么是堆栈对齐? 为什么用它? 可以通过编译器设置来控制吗?
这个问题的细节来自于尝试将ffmpeg库与msvc一起使用时遇到的问题,但我真正感兴趣的是对“堆栈对齐”的解释。
详情:
谢谢,
丹
答案 0 :(得分:106)
内存中变量的对齐(历史较短)。
过去,计算机有8位数据总线。这意味着,每个时钟周期可以处理8位信息。那很好。
然后是16位电脑。由于向下兼容性和其他问题,保留了8位字节并引入了16位字。每个单词是2个字节。并且每个时钟周期可以处理16位信息。但这提出了一个小问题。
让我们看一下内存映射:
+----+
|0000|
|0001|
+----+
|0002|
|0003|
+----+
|0004|
|0005|
+----+
| .. |
每个地址都有一个可以单独访问的字节。 但是只能在偶数地址获取单词。因此,如果我们在0000读取一个字,我们读取0000和0001处的字节。但是如果我们想要读取位置0001处的字,我们需要两次读访问。首先是0000,0001然后是0002,0003,我们只保留0001,0002。
当然这需要一些额外的时间,而且不受欢迎。所以这就是他们发明对齐的原因。因此,我们将字变量存储在字边界,将字节变量存储在字节边界。
例如,如果我们有一个带字节字段(B)和字段字段(W)(以及一个非常天真的编译器)的结构,我们得到以下内容:
+----+
|0000| B
|0001| W
+----+
|0002| W
|0003|
+----+
哪个不好玩。但是当使用单词对齐时,我们会发现:
+----+
|0000| B
|0001| -
+----+
|0002| W
|0003| W
+----+
这里牺牲了内存以获得访问速度。
你可以想象,当使用双字(4字节)或四字(8字节)时,这一点更为重要。这就是为什么对于大多数现代编译器,您可以在编译程序时选择使用哪种对齐方式。
答案 1 :(得分:12)
某些CPU体系结构需要特定的各种数据类型对齐,如果您不遵守此规则,则会抛出异常。在标准模式下,x86对基本数据类型不需要这样做,但可能会受到性能损失(请访问www.agner.org了解低级优化提示)。
但是,SSE指令集(通常用于高性能)音频/视频处理具有严格的对齐要求,如果您尝试在未对齐的数据上使用它,则会抛出异常(除非您使用,on一些处理器,更慢的未对齐版本。)
您的问题可能一个编译器希望调用者保持堆栈对齐,而另一个编译器希望 callee 对齐堆栈时必要的。
编辑:至于异常发生的原因,DLL中的例程可能希望在某些临时堆栈数据上使用SSE指令,并且因为两个不同的编译器不同意调用约定而失败
答案 2 :(得分:11)
这意味着如果您使用的变量是< 2个字节,例如char(1个字节),它与下一个变量之间将有8位未使用的“填充”。这允许某些优化具有基于变量位置的假设。
调用函数时,将参数传递给下一个函数的一种方法是将它们放在堆栈上(而不是将它们直接放入寄存器中)。这里是否使用对齐很重要,因为调用函数将变量放在堆栈上,由调用函数使用偏移读取。如果调用函数对齐变量,并且被调用函数期望它们不对齐,则被调用函数将无法找到它们。
似乎msvc编译的代码不同意变量对齐。尝试编译并关闭所有优化。
答案 3 :(得分:2)
据我所知,编译器通常不会对齐堆栈中的变量。该库可能取决于编译器不支持的一些编译器选项集。正常的解决方法是将需要对齐的变量声明为静态,但是如果你在其他人的代码中执行此操作,则需要确保它们中的变量稍后在函数中初始化而不是在声明。
// Some compilers won't align this as it's on the stack...
int __declspec(align(32)) needsToBe32Aligned = 0;
// Change to
static int __declspec(align(32)) needsToBe32Aligned;
needsToBe32Aligned = 0;
或者,找到一个编译器开关,用于对齐堆栈上的变量。显然我在这里使用的“__declspec”对齐语法可能不是编译器使用的。