为什么我可以将变量存储在不是其最小对齐方式的倍数的地址?



根据这个答案:

最小对齐是(在给定的平台上(不会崩溃的对齐方式。

在 GCC 8 中,有两个函数可以获得最小对齐和首选对齐方式:

  • 提供最小对齐方式的标准alignof运算符
  • 提供首选对齐方式的 GNU__alignof__函数

对于double,在 i386 架构上,最小对齐方式为 4 字节,首选对齐方式为 8 字节。因此,如果我正确理解了上面引用的答案,即将double存储在不是 4 的倍数的地址的应用程序,程序应该崩溃。

让我们看一下下面的代码:

#include <iostream>
void f(void* ptr) {
double* ptr_double = (double*) ptr;
ptr_double[0] = 3.5;
std::cout << ptr_double[0] << std::endl;
std::cout << &ptr_double[0] << std::endl;
}
int main()
{
alignas(__alignof__(double)) char arr[9];
f(arr+1);
return 0;
}

但是,如果我使用-m32选项编译它,它运行良好,并得到以下结果:

3.5
0xffe41571

我们可以看到我的double未对齐,但程序运行没有任何问题。

上面引用的下一句话是:

在 x86-64 上,它是一个字节。

在某些方面,这似乎是真的,因为我的代码有效。但是,在这种情况下,为什么alignof返回 4?

问题出在哪里?最小对齐的给定定义是错误的吗?还是有什么我没有得到的东西?

最小对齐是(在给定的平台上(不会崩溃的对齐方式。

因此,如果我正确理解了上面引用的答案,即在不是 4 的倍数的地址存储双精度的应用程序,程序应该崩溃。

你否认前因。

仅仅因为符合对齐不会造成崩溃,并不意味着未对齐会导致崩溃。


这是C++标准所说的:

[expr.alignof] alignof 表达式生成其操作数类型的对齐要求。

[basic.align] 对象类型具有对齐要求([basic.fundamental], [basic.compound](,这些要求限制了可以分配该类型的对象的地址。 对齐是实现定义的整数值,表示可以分配给定对象的连续地址之间的字节数。 对象类型对该类型的每个对象施加对齐要求;可以使用对齐说明符请求更严格的对齐。

就C++语言而言,没有未对齐的对象,因此它没有指定有关其行为的任何内容。您正在做的是访问一个不存在的对象,并且程序的行为是未定义的。


某些 CPU 体系结构,尤其是您使用1的体系结构,在使用未对齐的内存地址时不会崩溃。这样的操作或多或少地慢。

但是,在这种情况下,为什么对齐返回 4?

因为语言实现选择了这样。大概是因为它比使用 1 或 2 快,但不比使用 8 快。

1这是 80386 的程序员参考手册所说的:

请注意,单词不需要在偶数地址对齐,双字不需要在可被四整除的地址处对齐。这允许数据结构(例如,包含混合字节、字和双字项的记录(的最大灵活性和内存利用率。当在具有 32 位总线的配置中使用时,处理器和内存之间的实际数据传输以双字为单位进行,从可被 4 整除的地址开始;但是,处理器将未对齐的单词或双单词的请求转换为内存接口可接受的适当请求序列。这种未对齐的数据传输需要额外的内存周期,从而降低性能。为了获得最佳性能,数据结构(包括堆栈(的设计方式应尽可能使字操作数在偶数地址对齐,双字操作数在可被四整除的地址对齐。由于 CPU 中的指令预取和排队,因此不需要指令在字或双字边界上对齐。(但是,如果控制传输的目标地址能被 4 整除,则速度会略有提高。

但是,i386的后续架构引入了确实需要对齐的矢量扩展。


结论:GCC文档对"最小对齐"的含义的定义与Starynkevitch不同。

最新更新