提问者:小点点

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


根据这个答案:

最小对齐是(在给定平台上)不会发生崩溃的对齐。

对于GCC8,有两个函数可以获得最小和首选的对齐:

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

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

让我们看看下面的代码:

#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?

问题出在哪里,这里? 给定的最小对齐的定义是错误的吗? 还是有什么我不明白的?


共1个答案

匿名用户

最小对齐是(在给定平台上)不会发生崩溃的对齐。

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

你在否认前件。

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

C++标准是这么说的:

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

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

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

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

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

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

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

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

然而,i386的后继体系结构引入了需要对齐的向量扩展。

总之:GCC文档对“最小对齐”的定义与Starynkevitch的不同。

相关问题