沿4字节边界对齐

时间:2009-08-06 09:51:44

标签: c++ cpu alignment internals

我最近考虑过对齐......这是我们通常不必考虑的事情,但我已经意识到某些处理器要求对象沿着4字节边界对齐。这究竟是什么意思,以及哪些特定系统具有对齐要求?

假设我有一个任意指针:

unsigned char* ptr

现在,我正在尝试从内存位置检索double值:

double d = **((double*)ptr);

这会导致问题吗?

9 个答案:

答案 0 :(得分:20)

它肯定会在某些系统上造成问题。

例如,在基于ARM的系统上,您无法寻址未与4字节边界对齐的32位字。这样做会导致访问冲突异常。在x86上,您可以访问这些非对齐数据,但性能会受到一点影响,因为必须从内存中取出两个单词而不是一个单词。

答案 1 :(得分:13)

以下是Intel x86/x64 Reference Manual关于路线的说法:

  

4.1.1单词,双字,四字和双四字的对齐

     

单词,双字和四字   不需要在内存中对齐   自然界限。自然   单词边界,双字,   和四字是偶数   地址,地址可以整除   四,并均匀地解决   分别可以被8整除。   但是,要提高性能   程序,数据结构(特别是   堆栈)应该在自然对齐   边界尽可能。该   原因是处理器   需要两次内存访问才能生成   未对齐的内存访问;对齐   访问只需要一个内存   访问。单词或双字操作数   跨越4字节边界或   跨越的四字操作数   考虑8字节边界   未对齐,需要两个单独的   用于访问的存储器总线周期。

     

一些操作说明   双四字需要记忆   操作数要在自然界上对齐   边界。生成这些指令   一般保护例外(#GP)   如果指定了未对齐的操作数。   双重的自然边界   四字是均匀的任何地址   可被16整除。其他说明   以双四字运行   允许不对齐访问(没有   产生一般保护   例外)。但是,额外的内存   需要总线周期才能访问   来自内存的未对齐数据。

不要忘记,参考手册是负责任的开发人员和工程师的最终信息来源,因此如果您正在处理诸如Intel CPU之类的文档,请查阅参考手册中有关该问题的内容。

答案 2 :(得分:4)

对齐会影响结构的布局。考虑这个结构:

struct S {
  char a;
  long b;
};

在32位CPU上,此结构的布局通常为:

a _ _ _ b b b b

要求是32位值必须在32位边界上对齐。如果结构改变如下:

struct S {
  char a;
  short b;
  long c;
};

布局将是:

a _ b b c c c c

16位值在16位边界上对齐。

有时,如果要将结构与数据格式匹配,可能需要打包结构。通过使用编译器选项或#pragma,您可以删除多余的空格:

a b b b b
a b b c c c c

但是,访问压缩结构的未对齐成员在现代CPU上通常要慢得多,或者甚至可能导致异常。

答案 3 :(得分:4)

是的,这可能会导致许多问题。 C ++标准实际上并不能保证它能够正常工作。你不能随意在指针类型之间进行投射。

当您将char指针强制转换为双指针时,它使用reinterpret_cast,它应用实现定义的映射。您无法保证结果指针将包含相同的位模式,或者它将指向相同的地址,或者其他任何东西。在更实际的术语中,您也无法保证您正在阅读的值正确对齐。如果数据是作为一系列字符编写的,那么它们将使用char的对齐要求。

至于什么对齐意味着,基本上只是值的起始地址应该可以被对齐大小整除。例如,地址16在1,2,4,8和16字节边界上对齐,因此在典型的CPU上,这些大小的值可以存储在那里。

地址6未在4字节边界上对齐,因此我们不应在那里存储4字节值。

值得注意的是,即使在不强制执行或需要对齐的CPU上,您通常仍会因访问未对齐的值而显着减速。

答案 4 :(得分:3)

是的,这可能会导致问题。

4对齐只是意味着指针在被视为数字地址时是4的倍数。如果指针不是所需对齐的倍数,则它是未对齐的。编译器对某些类型设置对齐限制有两个原因:

  1. 因为硬件无法从未对齐的指针加载该数据类型(至少不使用编译器要为加载和存储发出的指令)。
  2. 因为硬件会从对齐的指针中更快地加载数据类型。
  3. 如果您遇到情况(1),并且double是4对齐的,并且您使用char *指针尝试不是4对齐的代码,那么您很可能会获得硬件陷阱。有些硬件不会陷阱。它只是加载一个无意义的值并继续。但是,C ++标准没有定义会发生什么(未定义的行为),因此这段代码可能会使您的计算机着火。

    在x86上,你永远不会遇到(1),因为标准加载指令可以处理未对齐的指针。在ARM上,没有未对齐的加载,如果你尝试加载,那么程序崩溃(如果你很幸运。有些ARM默默地失败)。

    回到你的例子,问题是为什么你用一个不是4对齐的char *来尝试这个。如果您通过double *在那里成功写了一个双精度数,那么您将能够阅读它。因此,如果你最初有一个“正确”指针加倍,你投射到char *并且你现在正在反击,你不必担心对齐。

    但是你说任意char *,所以我猜这不是你所拥有的。如果您从包含序列化双精度的文件中读取一大块数据,那么您必须确保满足您平台的对齐要求才能执行此操作。如果你有8个字节代表某种文件格式的double,那么你不能只是在任何偏移处将它读入char *缓冲区,然后转换为double *

    最简单的方法是确保将文件数据读入合适的结构中。内存分配始终与任何类型的最大对齐要求保持一致,这些要求的大小足以包含这些内容。因此,如果您分配一个足够大的缓冲区来包含一个double,那么该缓冲区的开头具有double所需的任何对齐方式。那么你可以将表示double的8个字节读入缓冲区的开头,转换(或者使用union)并读取double。

    或者,您可以这样做:

    double readUnalignedDouble(char *un_ptr) {
        double d;
        // either of these
        std::memcpy(&d, un_ptr, sizeof(d));
        std::copy(un_ptr, un_ptr + sizeof(d), reinterpret_cast<char *>(&d));
        return d;
    }
    

    这保证有效(假设un_ptr确实指向平台的有效双重表示的字节),因为double是POD,因此可以逐字节复制。如果要加载很多双打,它可能不是最快的解决方案。

    如果你正在读取一个文件,那么实际上还有更多的内容,如果你担心具有非IEEE双重表示的平台,或9位字节,或其他一些不寻常的属性,可能会有存储的double表示中的非值位。但是你实际上并没有问过文件,我只是把它作为一个例子来做,而且无论如何这些平台比你要问的问题要少得多,这对于具有对齐要求的双倍来说。

    最后,没有什么可以做对齐,你也有严格的别名来担心你是否通过一个与char *不兼容的指针的转换得到double *。但是,别名在char *本身和其他任何内容之间有效。

答案 5 :(得分:2)

在x86上它总是会运行,当然在对齐时效率更高。

但如果你是MULTITHREADING,那么请注意读写撕裂。使用64位值,您需要一台x64机器来为线程之间提供原子读写 如果你说在另一个线程中读取值在0x00000000.FFFFFFFF和0x00000001.00000000之间递增时,那么另一个线程理论上可能读取0或1FFFFFFFF,特别是IF SAY值STRADDLED A CACHE-LINE boundary。
我推荐Duffy的“Windows上的并发编程”,因为它很好地讨论了内存模型,甚至在dot-net执行GC时也提到了多处理器上的对齐问题。你想远离安腾!

答案 6 :(得分:2)

SPARC(Solaris机器)是另一种架构(至少在过去的某些时候),如果您尝试使用未对齐的值,它将阻塞(发出SIGBUS错误)。

马丁约克的附录,malloc也与最大可能的类型对齐,即它对所有东西都是安全的,比如'new'。事实上,经常'新'只使用malloc。

答案 7 :(得分:1)

对称要求的一个例子是使用向量化(SIMD)指令时。 (它可以在没有对齐的情况下使用,但如果你使用一种需要对齐的指令,它会更快。)

答案 8 :(得分:1)

强制内存对齐在基于RISC的体系结构(如MIPS)中更为常见 这些类型的处理器AFAIK的主要思想实际上是速度问题 RISC方法论的全部内容是拥有一组简单快速的指令(通常每条指令一个存储周期)。这并不一定意味着它具有比CISC处理器更少的指令,更多的是它具有更简单,更快速的指令 许多MIPS处理器,虽然8字节可寻址将是字对齐的(通常是32位但不总是),然后掩盖相应的位。
这个想法是,这比执行未对齐的加载更快地执行对齐的加载+位掩码。 通常(当然这实际上取决于芯片组),执行未对齐的负载会产生总线错误,因此RISC处理器将提供“未对齐的加载/存储”指令,但这通常比相应的对齐加载/存储慢得多。

当然,这仍然没有回答他们为什么这样做的问题,即记忆词对齐给你的优势是什么? 我不是硬件专家,我相信这里有人可以给出更好的答案,但我最好的两个猜测是: 1.当字对齐时从高速缓存中取出可以快得多,因为许多高速缓存被组织成高速缓存行(8到512字节之间),而高速缓存通常比RAM贵得多,你想要做得最多它 2.访问每个存储器地址可能要快得多,因为它允许您通读“突发模式”(即在需要之前获取下一个连续地址)

请注意,对于非对齐商店,以上都不是绝对不可能的,我猜测(虽然我不知道)很多都归结为硬件设计选择和成本