什么是堆栈对齐?为什么用它?可以通过编译器设置来控制吗?
这个问题的细节来自于尝试将ffmpeg库与msvc一起使用时遇到的问题,但我真正感兴趣的是对"堆栈对齐"的解释.
细节:
当runnig我的msvc编译程序链接到avcodec我得到以下错误:"编译器没有对齐堆栈变量.Libavcodec已被错误编译",随后avcodec.dll崩溃.
avcodec.dll没有用msvc编译,所以我无法看到里面发生了什么.
运行ffmpeg.exe并使用相同的avcodec.dll时一切正常.
ffmpeg.exe没有用msvc编译,它符合gcc/mingw(与avcodec.dll相同)
谢谢,
担
内存中变量的对齐(历史较短).
在过去,计算机具有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字节)时,这更为重要.这就是为什么对于大多数现代编译器,您可以在编译程序时选择使用哪种对齐方式.
IIRC,堆栈对齐是指变量在堆栈上"对齐"到特定数量的字节.因此,如果使用16位堆栈对齐,则堆栈上的每个变量将从一个字节开始,该字节是函数中当前堆栈指针的2个字节的倍数.
这意味着如果使用<2字节的变量,例如char(1字节),则它与下一个变量之间将有8位未使用的"填充".这允许基于变量位置的假设进行某些优化.
在调用函数时,将参数传递给下一个函数的一种方法是将它们放在堆栈上(而不是将它们直接放入寄存器中).这里是否使用对齐很重要,因为调用函数将变量放在堆栈上,由调用函数使用偏移读取.如果调用函数对齐变量,并且被调用函数期望它们不对齐,则被调用函数将无法找到它们.
似乎msvc编译的代码不同意变量对齐.尝试编译并关闭所有优化.
某些CPU体系结构需要特定的各种数据类型对齐,如果您不遵守此规则,则会抛出异常.在标准模式下,x86对基本数据类型不要求这样做,但可能会受到性能损失(请访问www.agner.org了解低级优化提示).
但是,SSE指令集(通常用于高性能)音频/视频处理具有严格的对齐要求,如果您尝试在未对齐的数据上使用它,则会抛出异常(除非您在某些处理器上使用更慢的未对齐版本) ).
你的问题是,可能是一个编译器预计呼叫者保持对齐堆栈,而其他预期的被调用,以在必要时将纸叠.
编辑:至于为什么发生异常,DLL中的例程可能想要在一些临时堆栈数据上使用SSE指令,并且因为两个不同的编译器不同意调用约定而失败.