什么是"堆栈对齐"?

Dan*_*anJ 44 c++ compiler-construction mingw visual-c++ data-structures

什么是堆栈对齐?为什么用它?可以通过编译器设置来控制吗?

这个问题的细节来自于尝试将ffmpeg库与msvc一起使用时遇到的问题,但我真正感兴趣的是对"堆栈对齐"的解释.

细节:

  • 当runnig我的msvc编译程序链接到avcodec我得到以下错误:"编译器没有对齐堆栈变量.Libavcodec已被错误编译",随后avcodec.dll崩溃.
  • avcodec.dll没有用msvc编译,所以我无法看到里面发生了什么.
  • 运行ffmpeg.exe并使用相同的avcodec.dll时一切正常.
  • ffmpeg.exe没有用msvc编译,它符合gcc/mingw(与avcodec.dll相同)

谢谢,

Too*_*the 115

内存中变量的对齐(历史较短).

在过去,计算机具有8位数据总线.这意味着,每个时钟周期可以处理8位信息.那很好.

然后是16位电脑.由于向下兼容性和其他问题,保留了8位字节并引入了16位字.每个单词是2个字节.并且每个时钟周期可以处理16位信息.但这提出了一个小问题.

我们来看一个内存映射:

+----+
|0000| 
|0001|
+----+
|0002|
|0003|
+----+
|0004|
|0005|
+----+
| .. |
Run Code Online (Sandbox Code Playgroud)

在每个地址都有一个可以单独访问的字节.但是单词只能在偶数地址获取.因此,如果我们在0000读取一个字,我们读取0000和0001处的字节.但是如果我们想要读取位置0001处的字,我们需要两次读访问.首先是0000,0001然后是0002,0003,我们只保留0001,0002.

当然,这需要一些额外的时间,这是不受欢迎的.所以这就是他们发明对齐的原因.因此,我们将字变量存储在字边界,将字节变量存储在字节边界.

例如,如果我们有一个带字节字段(B)和字段字段(W)(以及一个非常天真的编译器)的结构,我们得到以下结果:

+----+
|0000| B
|0001| W
+----+
|0002| W
|0003|
+----+
Run Code Online (Sandbox Code Playgroud)

哪个不好玩.但是当使用单词对齐时,我们发现:

+----+
|0000| B
|0001| -
+----+
|0002| W
|0003| W
+----+
Run Code Online (Sandbox Code Playgroud)

为了访问速度,牺牲了内存.

你可以想象,当使用双字(4字节)或四字(8字节)时,这更为重要.这就是为什么对于大多数现代编译器,您可以在编译程序时选择使用哪种对齐方式.

  • 堆栈对齐的精彩描述! (5认同)
  • @ToonKrijthe 这是为什么?在您的示例中,机器可以通过一次读取操作以 2 字节块为单位读取内存(例如 0000、0001 或 0002、0003)。因此,如果我的地址寄存器指向 0001(奇数地址而不是偶数地址),那么我可以在一次读取操作中直接从那里读取 2 个字节(即 0001 和 0002),对吧? (3认同)
  • 这很好地解释了为什么单词数组应该对齐。因为访问特定元素否则需要两次读取。但在包含一个字节和一个单词的示例中:如果您读取完整的结构,那么在这两种情况下您都必须读取这两个单词。 (2认同)
  • @ToonKrijthe **“但是单词只能在偶数地址处获取。”**为什么这必须是真的,在你的例子中内存/堆栈指针不能指向0001,然后从那里开始读取一个信息字? (2认同)
  • @User10482 去阅读 pdf 版《每个程序员都应该了解内存》,第 7 页,图 2.7。内存并不像你脑子里想象的那样,它不是存储为一行,而是一个矩阵,所以你可以一次性读取一整行,但如果你没有对齐,你需要2次读取访问。 (2认同)
  • @User10482 [可能有用](https://developer.ibm.com/articles/pa-dalign/) (2认同)

Sha*_*ert 11

IIRC,堆栈对齐是指变量在堆栈上"对齐"到特定数量的字节.因此,如果使用16位堆栈对齐,则堆栈上的每个变量将从一个字节开始,该字节是函数中当前堆栈指针的2个字节的倍数.

这意味着如果使用<2字节的变量,例如char(1字节),则它与下一个变量之间将有8位未使用的"填充".这允许基于变量位置的假设进行某些优化.

在调用函数时,将参数传递给下一个函数的一种方法是将它们放在堆栈上(而不是将它们直接放入寄存器中).这里是否使用对齐很重要,因为调用函数将变量放在堆栈上,由调用函数使用偏移读取.如果调用函数对齐变量,并且被调用函数期望它们不对齐,则被调用函数将无法找到它们.

似乎msvc编译的代码不同意变量对齐.尝试编译并关闭所有优化.


sne*_*rch 11

某些CPU体系结构需要特定的各种数据类型对齐,如果您不遵守此规则,则会抛出异常.在标准模式下,x86对基本数据类型不要求这样做,但可能会受到性能损失(请访问www.agner.org了解低级优化提示).

但是,SSE指令集(通常用于高性能)音频/视频处理具有严格的对齐要求,如果您尝试在未对齐的数据上使用它,则会抛出异常(除非您在某些处理器上使用更慢的未对齐版本) ).

你的问题是,可能是一个编译器预计呼叫者保持对齐堆栈,而其他预期的被调用,以在必要时将纸叠.

编辑:至于为什么发生异常,DLL中的例程可能想要在一些临时堆栈数据上使用SSE指令,并且因为两个不同的编译器不同意调用约定而失败.