58

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

这个问题的细节取自尝试将 ffmpeg 库与 msvc 一起使用时面临的问题,但是我真正感兴趣的是对什么是“堆栈对齐”的解释。

细节:

  • 当运行链接到 avcodec 的 msvc 编译程序时,我收到以下错误:“编译器未对齐堆栈变量。Libavcodec 已被错误编译”,然后是 avcodec.dll 中的崩溃。
  • avcodec.dll 不是用 msvc 编译的,所以我看不到里面发生了什么。
  • 运行 ffmpeg.exe 并使用相同的 avcodec.dll 时一切正常。
  • ffmpeg.exe不是用msvc编译的,是用gcc/mingw编译的(和avcodec.dll一样)

谢谢,

4

4 回答 4

145

内存中变量的对齐(简短的历史)。

过去计算机有一个 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 字节)时,这一点更为重要。这就是为什么对于大多数现代编译器,您可以在编译程序时选择使用哪种对齐方式。

于 2009-03-23T07:37:35.173 回答
12

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

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

您的问题可能是一个编译器希望调用者保持堆栈对齐,而另一个编译器希望被调用者在必要时对齐堆栈。

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

于 2009-03-23T07:45:58.710 回答
11

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

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

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

似乎 msvc 编译的代码在变量对齐方面存在分歧。尝试在关闭所有优化的情况下进行编译。

于 2009-03-23T07:41:20.157 回答
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”对齐语法可能不是您的编译器使用的。

于 2009-03-23T07:53:42.357 回答