C 数据对齐:在哪里可以读取?可以改变吗?

C 数据对齐:在哪里可以读取?可以改变吗?,c,optimization,memory,C,Optimization,Memory,这是一本关于内存中基本类型的数据对齐的书 例如,Microsoft Windows对任何K字节的基本对象都提出了更严格的对齐要求 K=2、4或8,必须具有K的倍数的地址。特别是,它要求 双倍的或长的是8的倍数。这一要求提高了服务器端的内存性能 浪费空间的费用。Linux约定,其中8字节值与4字节值对齐 在内存不足、内存接口不可用的时候,边界可能对i386有好处 只有4个字节宽。对于现代处理器,微软的调整是一个更好的设计决策。数据类型 long double,gcc为其生成分配12字节的IA32代

这是一本关于内存中基本类型的数据对齐的书

例如,Microsoft Windows对任何K字节的基本对象都提出了更严格的对齐要求 K=2、4或8,必须具有K的倍数的地址。特别是,它要求 双倍的或长的是8的倍数。这一要求提高了服务器端的内存性能 浪费空间的费用。Linux约定,其中8字节值与4字节值对齐 在内存不足、内存接口不可用的时候,边界可能对i386有好处 只有4个字节宽。对于现代处理器,微软的调整是一个更好的设计决策。数据类型 long double,gcc为其生成分配12字节的IA32代码(即使实际数据类型为 仅需要10个字节)对Windows和Linux都有4个字节的对齐要求

问题是:

  • 是什么强加了数据对齐、操作系统还是编译器
  • 我能换一下还是修好了

  • 一般来说,编译器强制执行对齐。无论何时声明基元类型(例如,
    double
    ),编译器都会自动将其与堆栈上的8个字节对齐

    此外,内存分配通常也与最大的基元类型对齐,因此您可以安全地执行以下操作:

    double *ptr = (double*)malloc(size);
    
    不必担心对齐问题

    因此,一般来说,如果你的编程习惯很好,你就不必担心对齐问题。使某个对象不对齐的一种方法是执行以下操作:

    char *ch_ptr = (char*)malloc(size);
    
    double *d_ptr = (double*)(ch_ptr + 1);
    

    有一些例外情况:当您开始使用SSE和矢量化时,事情会变得有点混乱,因为
    malloc
    不再保证16字节对齐


    要覆盖某个对象的对齐,MSVC有一个允许此操作的修改器。它用来增加某些东西的对齐度。虽然我不确定它是否可以减少基本类型的对齐。它明确表示不能使用此修改器减少对齐


    编辑:

    我在GCC上找到了说明
    malloc()
    对齐的文档:

    GNU系统中malloc或realloc返回的块地址 始终是8的倍数(在64位系统上为16)

    资料来源:


    是的,GCC现在至少可以对齐8个字节。

    x86 CPU的对齐要求非常宽松。大多数数据可以在未对齐的位置存储和访问,这可能会降低性能。当您开始开发多处理器软件时,事情变得更加复杂,因为对齐对于原子性和观察到的事件顺序变得非常重要(从内存中写入,这可能不完全正确)

    编译器通常可以被指示以不同于默认对齐方式的方式对齐变量。有编译器选项和特定于编译器的特殊关键字(例如
    #pragma pack
    和其他)

    既不能由应用程序程序员(操作系统已经编译)也不能由操作系统开发人员(当然,除非他们同意破坏兼容性)更改完善的操作系统API


    因此,您可以更改某些内容,但不能更改所有内容。

    我不知道microsoft从何处获得信息,但结果是 gcc(4.6.1目标:x86_64-linux-gnu,标准模式,除-Wall外无任何标志)有很大不同:

    #include <stdio.h>
    
    struct lll {
        long l; 
        long long ll;
        };      
    
    struct lld {
        long l; 
        long double ld;
        };      
    
    struct lll lll1, lll2[2];
    struct lld lld1, lld2[2];
    
    int main(void)
    {   
    printf("lll1=%u, lll2=%u\n"
        , (unsigned) sizeof lll1
        , (unsigned) sizeof lll2
        );      
    
    printf("lld=%u, lld2=%u\n"
        , (unsigned) sizeof lld1
        , (unsigned) sizeof lld2
        );      
    
    return 0;
    }
    
    这可能是FUD(来自实际设法将未对齐的INT放入MBR的公司…)。但这也可能是由于作者没有得到很好的信息


    回答这个问题:是硬件施加了对齐限制。编译器只需要实现它们。

    谢谢。Linux性能如何?如果已知与4的倍数对齐不再是最佳的,那么我可以改变它吗?据我所知,Linux上最新版本的GCC也与原语类型的大小对齐。因此,我认为旧的4字节对齐方式基本上已经过时了。Windows API使用或产生了大量的结构,如果使用不正确的打包/对齐版本调用这些API,那就太糟糕了。很有趣,因为这本书是2010年出版的。这些东西是否记录在GCC文档中,或者通常不需要对它们进行smth。我不知道是否有记录。但无论如何,GCC(静默地)进行对齐是非常有意义的。在我尝试过的机器上,对
    double
    的未对齐访问的性能损失是巨大的。(请注意,我实际上没有查看任何GCC生成的程序集来确认8字节对齐,但我从未注意到Windows与Linux之间的性能下降,因此我假设没有)。请指定可以使用
    #pragma pack
    的编译器。Borland,Microsoft,Open Watcom和GNU C/C++编译器支持#pragma pack。您还可以使用gcc选项-mstrict align-mno strict align来控制其行为。如果说更多的是处理器建议(或在某些情况下/体系结构强制)良好的数据对齐,编译器只是努力尊重它(我要说的是,操作系统的参与只是因为它是程序所用数据结构的消费者/生产者)。结论:烧掉这本书。这是胡说八道。引用的文本是胡说八道。两种平台在x86(32位)上都使用不超过4字节的对齐方式x86_64上的
    double
    long
    的8字节对齐。如果您使用的是一些需要特定对齐/打包的固定API(例如,一些使用或生成结构的Windows API函数),您不能使用任意对齐/打包。因此,问题不仅在于硬件。这是windows的问题。unix/linux的方式是:用户代码和系统代码(以及API)共享相同的对齐要求。(好像它们是由同一个compi编译的。)
    ./a.out
    lll1=16, lll2=32
    lld=32, lld2=64