为什么sizeof()编译器返回的值依赖?

 半路出家者 发布于 2023-02-13 10:56

如果需要,编译器可以为目标体系结构执行数据结构对齐.它可能纯粹是为了提高应用程序的运行时性能,或者在某些情况下是处理器所要求的(即如果数据未对齐,程序将无法工作).

例如,大多数(但不是全部)SSE2指令要求数据在16字节边界上对齐.简而言之,计算机内存中的所有内容都有一个地址.假设我们有一个简单的双精度数组,如下所示:

double data[256];

为了使用需要16字节对齐的SSE2指令,必须确保地址&data[0]为16的倍数.

对齐要求因架构而异.在x86_64上,建议所有大于16字节的结构在16字节边界上对齐.通常,为获得最佳性能,请按如下方式对齐数据:

在任何地址对齐8位数据

将16位数据对齐以包含在对齐的四字节字中

对齐32位数据,使其基址为四的倍数

对齐64位数据,使其基址为8的倍数

对齐80位数据,使其基址为十六的倍数

对齐128位数据,使其基址为16的倍数

有趣的是,大多数x86_64 CPU都可以使用对齐和非对齐数据.但是,如果数据未正确对齐,CPU执行代码的速度会明显变慢.

当编译器考虑到这一点时,它可能会隐式地对齐结构的成员并且会影响其大小.例如,假设我们有这样的结构:

struct A {
    char a;
    int b;
};

假设x86_64,大小int为32位或4字节.因此,建议始终使地址为4 b的倍数.但由于a字段大小只有1个字节,因此无法实现.因此,编译器会在其间添加3个字节的填充ab隐式:

struct A {
    char a;
    char __pad0[3]; /* This would be added by compiler,
                       without any field names - __pad0 is for
                       demonstration purposes */
    int b;
};

编译器如何做到这一点不仅取决于编译器和体系结构,还取决于传递给编译器的编译器设置(标志).使用特殊语言结构也会影响此行为.例如,可以要求编译器不使用如下packed属性执行任何填充:

struct A {
    char a;
    int b;
} __attribute__((packed));

在你的情况下,mingw32-gcc.exe简单地在8字节边界之间添加7个字节cd对齐d.而Ubuntu上的gcc 4.6.3只添加了3个以对齐d4字节边界.

除非您正在执行某些优化,尝试使用特殊的扩展指令集,或者对数据结构有特定要求,否则我建议您不要依赖于特定的编译器行为,并且总是假设不仅您的结构可能被填充,它可能在架构,编译器和/或不同的编译器版本之间进行不同的填充.否则,您需要使用编译器属性和设置半手动确保数据对齐和结构大小,并确保它在所有使用单元测试或甚至静态断言的目标编译器和平台上都有效.

欲了解更多信息,请查看:

维基百科上的数据对齐文章

迁移到64位英特尔®架构时的数据对齐

GCC变量属性

希望能帮助到你.祝好运!

如何最小化填充:

让所有结构成员正确对齐并同时保持结构大小合理始终是件好事.考虑这两个结构变体,重新安排成员(从现在开始假设sizeof char,short,int,long,long long分别为1,2,4,4,8):

struct A
{
    char a;
    short b;
    char c;
    int d;
};

struct B
{
    char a;
    char c;
    short b;
    int d;
};

两个结构都应该保持相同的数据,但sizeof(结构A)将是12个字节,sizeof(结构B)将是8,因为良好的成员顺序消除了隐式填充:

struct A
{
    char a;
    char __pad0[1]; // implicit compiler padding
    short b;
    char c;
    char __pad1[3]; // implicit compiler padding
    int d;
};

struct B // no implicit padding
{
    char a;
    char c;
    short b;
    int d;
};

随着成员数量的增加,重新排列结构成员可能容易出错.为了减少错误 - 在开头放最长,最后放最短:

struct B // no implicit padding
{
    int d;
    short b;
    char a;
    char c;
};

结束时的隐式填充:

根据您使用的编译器,设置,平台等,您可能会注意到编译器不仅在结构成员之前添加填充,而且在结尾处(即在最后一个成员之后)添加填充.结构如下:

struct abcd
{
    long long a;
    char b;
};

可能占用12或16个字节(最差的编译器将允许它为9个字节).这种填充可能很容易被忽视,但如果你的结构是阵列的话,这个非常重要.它将确保您后续数组单元格/元素中成员也将正确对齐.

最后和随意的想法:

如果 - 在使用结构时 - 你会遵循这些建议,它永远不会伤害(并且可能实际上保存)你:

不要依赖编译器将结构成员与适当的填充交错.

确保您的结构(如果外部数组)与其最长成员所需的边界对齐.

确保排列结构成员,以便最长放置第一个,最后一个成员最短.

确保明确填充结构(如果需要),以便在创建结构数组时,每个结构成员都具有正确的对齐方式.

确保结构的数组也正确对齐,尽管结构可能需要8字节对齐,但编译器可能会将数组对齐为4字节边界.

@Vlad Lazarenko:这是一个很好的答案,我给你100个代表点.虽然需要等到明天. (2认同)


Bathsheba.. 7

sizeoffor 返回的值structs不是任何C标准强制要求的.这取决于编译器和机器架构.

例如,在4字节边界上对齐数据成员可能是最佳的:在这种情况下,有效打包大小char c将是4个字节.

2 个回答
  • sizeoffor 返回的值structs不是任何C标准强制要求的.这取决于编译器和机器架构.

    例如,在4字节边界上对齐数据成员可能是最佳的:在这种情况下,有效打包大小char c将是4个字节.

    2023-02-13 10:58 回答
  • 如果需要,编译器可以为目标体系结构执行数据结构对齐.它可能纯粹是为了提高应用程序的运行时性能,或者在某些情况下是处理器所要求的(即如果数据未对齐,程序将无法工作).

    例如,大多数(但不是全部)SSE2指令要求数据在16字节边界上对齐.简而言之,计算机内存中的所有内容都有一个地址.假设我们有一个简单的双精度数组,如下所示:

    double data[256];
    

    为了使用需要16字节对齐的SSE2指令,必须确保地址&data[0]为16的倍数.

    对齐要求因架构而异.在x86_64上,建议所有大于16字节的结构在16字节边界上对齐.通常,为获得最佳性能,请按如下方式对齐数据:

    在任何地址对齐8位数据

    将16位数据对齐以包含在对齐的四字节字中

    对齐32位数据,使其基址为四的倍数

    对齐64位数据,使其基址为8的倍数

    对齐80位数据,使其基址为十六的倍数

    对齐128位数据,使其基址为16的倍数

    有趣的是,大多数x86_64 CPU都可以使用对齐和非对齐数据.但是,如果数据未正确对齐,CPU执行代码的速度会明显变慢.

    当编译器考虑到这一点时,它可能会隐式地对齐结构的成员并且会影响其大小.例如,假设我们有这样的结构:

    struct A {
        char a;
        int b;
    };
    

    假设x86_64,大小int为32位或4字节.因此,建议始终使地址为4 b的倍数.但由于a字段大小只有1个字节,因此无法实现.因此,编译器会在其间添加3个字节的填充ab隐式:

    struct A {
        char a;
        char __pad0[3]; /* This would be added by compiler,
                           without any field names - __pad0 is for
                           demonstration purposes */
        int b;
    };
    

    编译器如何做到这一点不仅取决于编译器和体系结构,还取决于传递给编译器的编译器设置(标志).使用特殊语言结构也会影响此行为.例如,可以要求编译器不使用如下packed属性执行任何填充:

    struct A {
        char a;
        int b;
    } __attribute__((packed));
    

    在你的情况下,mingw32-gcc.exe简单地在8字节边界之间添加7个字节cd对齐d.而Ubuntu上的gcc 4.6.3只添加了3个以对齐d4字节边界.

    除非您正在执行某些优化,尝试使用特殊的扩展指令集,或者对数据结构有特定要求,否则我建议您不要依赖于特定的编译器行为,并且总是假设不仅您的结构可能被填充,它可能在架构,编译器和/或不同的编译器版本之间进行不同的填充.否则,您需要使用编译器属性和设置半手动确保数据对齐和结构大小,并确保它在所有使用单元测试或甚至静态断言的目标编译器和平台上都有效.

    欲了解更多信息,请查看:

    维基百科上的数据对齐文章

    迁移到64位英特尔®架构时的数据对齐

    GCC变量属性

    希望能帮助到你.祝好运!

    如何最小化填充:

    让所有结构成员正确对齐并同时保持结构大小合理始终是件好事.考虑这两个结构变体,重新安排成员(从现在开始假设sizeof char,short,int,long,long long分别为1,2,4,4,8):

    struct A
    {
        char a;
        short b;
        char c;
        int d;
    };
    
    struct B
    {
        char a;
        char c;
        short b;
        int d;
    };
    

    两个结构都应该保持相同的数据,但sizeof(结构A)将是12个字节,sizeof(结构B)将是8,因为良好的成员顺序消除了隐式填充:

    struct A
    {
        char a;
        char __pad0[1]; // implicit compiler padding
        short b;
        char c;
        char __pad1[3]; // implicit compiler padding
        int d;
    };
    
    struct B // no implicit padding
    {
        char a;
        char c;
        short b;
        int d;
    };
    

    随着成员数量的增加,重新排列结构成员可能容易出错.为了减少错误 - 在开头放最长,最后放最短:

    struct B // no implicit padding
    {
        int d;
        short b;
        char a;
        char c;
    };
    

    结束时的隐式填充:

    根据您使用的编译器,设置,平台等,您可能会注意到编译器不仅在结构成员之前添加填充,而且在结尾处(即在最后一个成员之后)添加填充.结构如下:

    struct abcd
    {
        long long a;
        char b;
    };
    

    可能占用12或16个字节(最差的编译器将允许它为9个字节).这种填充可能很容易被忽视,但如果你的结构是阵列的话,这个非常重要.它将确保您后续数组单元格/元素中成员也将正确对齐.

    最后和随意的想法:

    如果 - 在使用结构时 - 你会遵循这些建议,它永远不会伤害(并且可能实际上保存)你:

    不要依赖编译器将结构成员与适当的填充交错.

    确保您的结构(如果外部数组)与其最长成员所需的边界对齐.

    确保排列结构成员,以便最长放置第一个,最后一个成员最短.

    确保明确填充结构(如果需要),以便在创建结构数组时,每个结构成员都具有正确的对齐方式.

    确保结构的数组也正确对齐,尽管结构可能需要8字节对齐,但编译器可能会将数组对齐为4字节边界.

    2023-02-13 10:58 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有