C++数据类型对齐、对齐规则

来源:互联网 发布:淘宝上理肤泉是正品吗 编辑:程序博客网 时间:2024/06/04 19:29

C++元素对齐探讨

探讨内容与目标

探讨C++元素的对齐方式,并以sizeof()类型返回值,测试是否理解正确。
重点留意后面的结果分析,有经验总结哦!!!

操作环境

测试环境为Windows 10,Visual Studio 2015.

一些基本知识

  • 各元素类型的sizeof,以及其相应的对齐大小需要掌握。
  • data alignment 数据对齐
  • data padding 数据填充
  • 区分3个概念:结构体对齐大小,基本元素对齐大小,预设对齐大小

结构体对齐大小,是align(A)之后得到的大小,是本次讨论重点研究的对象,也是sizeof()以及padding的最终因素。本次讨论一般设为x

基本元素对齐大小,就是int之类的默认对齐大小,一般而言,就是基本元素的字节数。它们以一个整体出现的话,就不受#pragma pack()指令的影响。如果,基本元素在结构体内的话,受#pragma pack()影响。本次讨论,谈及基本元素对齐大小都是指的前者。本次讨论一般设为y

预设对齐大小,就是#pragma pack()指令的预设值,一般为1,2,4,8,16。本次讨论一般设为z

本次研究的主题,其实质就是x = f(y1, ... ,yn, z)的函数关系。

  • 一些函数介绍
    1. align() //计算元素类型对齐大小

align()用于计算某个元素类型的对齐大小x,注意不是元素。
比如:

struct A{    char a;    int b;    short c;};cout << alignof(A) << endl;   //ok    4A a;cout<<alignof(a)<<endl;    //error
  1. #pragma pack() //设置预设对齐大小
    #pragma pack()是编译器的预处理命令
    可用的预设对齐大小是1、2、4、8(默认)、16;
    相应的设置方法

    • 项目->属性->配置属性->c/c++->代码生成->结构成员对齐->进行设置
    • 调用#pragma pack(n)。eg:#pragma pack(4); //设置预设的对齐大小为4
#pragma pack()      //设置预设对齐大小为默认值, 一般为8#pragma pack(4)      //设置预设对齐大小为4
  1. offsetof(type, var) //求type内的var的起始地址相对于type的起始地址的偏移量
struct A{    char a;    int b;    short c;};//计算结构体元素a的起始地址相对于A的起始地址的偏移量cout << offsetof(A, a) << endl;   //ok    0cout << offsetof(A, b) << endl;   //ok    4

测试代码

#define _CRT_SECURE_NO_WARNINGS#include <iostream>using namespace std;struct A1{    char c;};struct A2{    char c1;    char c2;    int a;};struct A3{    char c1;    int a;    char c2;};struct A4{};struct A5{    char c;    double d;    char c1[7];};//设定预设对齐大小为2#pragma pack(2)struct A6{    char a;    int b;    //short c;};struct A7{    char b;};#pragma pack(4)struct A8{    A6 c1;    A7 d1;};//恢复默认预设大小--8#pragma pack()struct A9{    char c;    struct A9_inner    {        short ss;    }a9in;    double d;};struct A10{    char c;    struct A10_inner    {        short ss;        double dd;    }a10in;    double d;};//即使声明预设对齐大小为2,int位域还是进行32位扩展,而不是16位#pragma pack(2)struct A11{    short s;    char c;    int a1 : 1;    int a2 : 4;    int a3 : 7;};#pragma pack()struct A12{    short s;    char c;    long long a1 : 1;    long long a2 : 4;    long long a3 : 7;};//位域超过一个整体时,首次超过的那个位域字段需要考虑对齐struct A13{    unsigned a : 19;    unsigned b : 11;    //19+11+4 = 34 > 32  因此,c应该在下一个int里面    unsigned c : 4;    //同理,4 + 29 = 33 > 32 存在偏移    unsigned d : 29;    char index;};class Base{};class C1{    char c;    Base b;};class C2{    char c;    Base b;    int a;};class C3 :public Base{    char c;};class Base1{    char c;    int a;};class C4 :public Base1{    char c1;    double d;};class C41{    Base1 b;    char c1;    double d;};class C5 :public Base1{    char c1;    int a;    double d;};class C51{    char c1;    Base1 b;    int a;    double d;};class Base2{public:    virtual ~Base2(){ }};class C6{    char c;    Base2 b;};class C7{    char c;    Base2 b;    int a;};class C8 :public Base2{    char c;};class Base3{    char c;public:    virtual ~Base3()    {    }};class C9 :public Base3{    char c1;public:    virtual void print(){ }};class Base4{public:    virtual void print() = 0;};class C10 :public Base4{    char c;public:    virtual void print()    {    }};class Base5{    char c;public:    virtual void print() = 0;};class C11 :public Base5{    char c1;public:    virtual void print()    {    }};//测试开始标记static void printStart(){    cout << "--------------------- test ---------------------\n";}//测试退出标记static void printEndLine(){    cout << "--------------------- end  ---------------------\n";}//测试基本元素的字节数和对齐大小static void testBaseElement(){    printStart();    cout << "char: " << sizeof(char) << "---" << alignof(char) << endl;    cout << "int: " << sizeof(int) << "---" << alignof(int) << endl;    cout << "short: " << sizeof(short) << "---" << alignof(short) << endl;    cout << "long: " << sizeof(long) << "---" << alignof(long) << endl;#pragma pack(2)    //基本类型为一个整体时,不受预设对齐大小的约束    cout << "long long: " << sizeof(long long) << "---" << alignof(long long) << endl;#pragma pack()    cout << "float: " << sizeof(float) << "---" << alignof(float) << endl;    cout << "double: " << sizeof(double) << "---" << alignof(double) << endl;    cout << "long double: " << sizeof(long double) << "---" << alignof(long double) << endl;    void* p = NULL;    cout << "pointer: " << sizeof(p) << "---" << alignof(char*) << endl;    printEndLine();}//测试struct相关的字节数和对齐大小static void testOnlyWithStruct(){    printStart();    cout << "struct A1: " << sizeof(A1) << "---" << alignof(A1) << endl;    cout << "struct A2: " << sizeof(A2) << "---" << alignof(A2) << endl;    cout << "struct A3: " << sizeof(A3) << "---" << alignof(A3) << endl;    cout << "struct A4: " << sizeof(A4) << "---" << alignof(A4) << endl;    cout << "struct A5: " << sizeof(A5) << "---" << alignof(A5) << endl;    cout << "struct A6: " << sizeof(A6) << "---" << alignof(A6) << endl;    cout << "struct A6.b: " << sizeof(A6::b) << "---" << offsetof(A6, b) << endl;    cout << "struct A7: " << sizeof(A7) << "---" << alignof(A7) << endl;    cout << "struct A8: " << sizeof(A8) << "---" << alignof(A8) << endl;    cout << "struct A9: " << sizeof(A9) << "---" << alignof(A9) << endl;    cout << "struct A10: " << sizeof(A10) << "---" << alignof(A10) << endl;    cout << "struct A11: " << sizeof(A11) << "---" << alignof(A11) << endl;    cout << "struct A12: " << sizeof(A12) << "---" << alignof(A12) << endl;    cout << "struct A13: " << sizeof(A13) << "---" << alignof(A13) << endl;    printEndLine();}//测试不存在虚函数的类的字节数和对齐大小static void testWithClassNonVirtual(){    cout << "class Base: " << sizeof(Base) << "---" << alignof(Base) << endl;    cout << "class C1: " << sizeof(C1) << "---" << alignof(C1) << endl;    cout << "class C2: " << sizeof(C2) << "---" << alignof(C2) << endl;    cout << "class C3: " << sizeof(C3) << "---" << alignof(C3) << endl;    cout << "class Base1: " << sizeof(Base1) << "---" << alignof(Base1) << endl;    cout << "class C4: " << sizeof(C4) << "---" << alignof(C4) << endl;    cout << "class C41: " << sizeof(C41) << "---" << alignof(C41) << endl;    cout << "class C5: " << sizeof(C5) << "---" << alignof(C5) << endl;    cout << "class C51: " << sizeof(C51) << "---" << alignof(C51) << endl;}//测试不存在虚函数的类的字节数和对齐大小static void testWithClassVirtual(){    cout << "class Base2: " << sizeof(Base2) << "---" << alignof(Base2) << endl;    cout << "class C6: " << sizeof(C6) << "---" << alignof(C6) << endl;    cout << "class C7: " << sizeof(C7) << "---" << alignof(C7) << endl;    cout << "class C8: " << sizeof(C8) << "---" << alignof(C8) << endl;    cout << "class Base3: " << sizeof(Base3) << "---" << alignof(Base3) << endl;    cout << "class C9: " << sizeof(C9) << "---" << alignof(C9) << endl;    //alignof(Base4),alignof(Base5)  会报错, 不能实例化抽象类    cout << "class Base4: " << sizeof(Base4) << "---"  << endl;    cout << "class C10: " << sizeof(C10) << "---" << alignof(C10) << endl;    cout << "class Base5: " << sizeof(Base5) << "---"  << endl;    cout << "class C11: " << sizeof(C11) << "---" << alignof(C11) << endl;}//测试类相关的字节数和对齐大小,主要涉及继承和虚函数static void testWithClass(){    printStart();    testWithClassNonVirtual();    cout << "\n------------------------------------------------\n" << endl;    testWithClassVirtual();    printEndLine();}struct D1{    int size;    //使用了非标准扩展;结构/联合中的零大小数组    //这是C99的柔性数组Flexible Array,也就是变长数组    char data[0];    //或者char data[];  };struct D2{    //使用了非标准扩展;结构/联合中的零大小数组    char data[0];};//不能直接定义一个数组大小为0的数组//char data[0];//测试柔性数组static void testOther(){    printStart();    cout << "class D1: " << sizeof(D1) << "---" << alignof(D1) << endl;    cout << "class D2: " << sizeof(D2) << "---" << alignof(D2) << endl;    printEndLine();}int main(){    freopen("out64.txt", "w", stdout);    testBaseElement();    cout << endl;    testOnlyWithStruct();    cout << endl;    testWithClass();    cout << endl;    testOther();    return 0;}

测试结果

  • 32位编译器结果
--------------------- test ---------------------char: 1---1int: 4---4short: 2---2long: 4---4long long: 8---8float: 4---4double: 8---8long double: 8---8pointer: 8---8--------------------- end  ------------------------------------------ test ---------------------struct A1: 1---1struct A2: 8---4struct A3: 12---4struct A4: 1---1struct A5: 24---8struct A6: 6---2struct A6.b: 4---2struct A7: 1---1struct A8: 8---2struct A9: 16---8struct A10: 32---8struct A11: 8---2struct A12: 16---8struct A13: 16---4--------------------- end  ------------------------------------------ test ---------------------class Base: 1---1class C1: 2---1class C2: 8---4class C3: 1---1class Base1: 8---4class C4: 24---8class C41: 24---8class C5: 24---8class C51: 24---8------------------------------------------------class Base2: 8---8class C6: 16---8class C7: 24---8class C8: 16---8class Base3: 16---8class C9: 24---8class Base4: 8---class C10: 16---8class Base5: 16---class C11: 24---8--------------------- end  ------------------------------------------ test ---------------------class D1: 4---4class D2: 1---1--------------------- end  ---------------------
  • 64位编译器
--------------------- test ---------------------char: 1---1int: 4---4short: 2---2long: 4---4long long: 8---8float: 4---4double: 8---8long double: 8---8pointer: 8---8--------------------- end  ------------------------------------------ test ---------------------struct A1: 1---1struct A2: 8---4struct A3: 12---4struct A4: 1---1struct A5: 24---8struct A6: 6---2struct A6.b: 4---2struct A7: 1---1struct A8: 8---2struct A9: 16---8struct A10: 32---8struct A11: 8---2struct A12: 16---8struct A13: 16---4--------------------- end  ------------------------------------------ test ---------------------class Base: 1---1class C1: 2---1class C2: 8---4class C3: 1---1class Base1: 8---4class C4: 24---8class C41: 24---8class C5: 24---8class C51: 24---8------------------------------------------------class Base2: 8---8class C6: 16---8class C7: 24---8class C8: 16---8class Base3: 16---8class C9: 24---8class Base4: 8---class C10: 16---8class Base5: 16---class C11: 24---8--------------------- end  ------------------------------------------ test ---------------------class D1: 4---4class D2: 1---1--------------------- end  ---------------------

结果分析

分析维度

  1. 32位编译和64位编译器
  2. 编译器自己的对齐命令 #pragma pack()的影响
  3. C关注于结构体,C++专注于类继承和虚函数
  4. 位域的影响
  5. 内置结构体(或者类)对象的影响。

经验性总结

  • 对于32位编译器和64位编译器
    除了指针大小和对齐方式不同,其他的都与32位遵循一样的规则
  • #pragma pack()
    再次NOTE:这里只是预设的对齐大小,结构体不一定按这个大小进行对齐。
    结论1
    假设,无#pragma pack()情况下某结构体的对齐大小为x1;
    设,存在#pragma pack(z)指令,某结构体的对齐大小为x; 则 x = min(x1, z);

证明:
已知,无#pragma()情况下某结构体的对齐大小为x1;预设对齐大小为z
当x1<=z, x = x1; //案例A1 –也就是说预设对齐大小,不起任何作用,平时经常遇到的就是这个情况
当z

//  align(A)  = max(y1, y2, y3) = 4struct A{    char a;    //y1 = 1    int b;     //y2 = 4    short c;   //y3 = 2};

对于其中,含有内嵌结构,则也类似

//易知: align(A6) = 4  align(A7) = 1struct A6{    char a;    int b;    short c;};struct A7{    char b;};// align(A8) = max(y1, y2) = 4struct A8{    A6 c1;    //y1 =  4    A7 d1;    //y2 =  1};
  • 结论2:某个元素a(不管是结构体还是普通元素)的sizeof()大小一定是align(a)的整数倍。
    注意很多人经常有个错误的言论,就是结构体的大小一定是数组中的最长字段的整数倍。
//注意这里sizeof(A)=6, 不是4(= sizeof(int))的倍数,但确实是2 (=align(A))的倍数。#pragma pack(2)struct A{    char a;    int b;};
  • 偏移量offset
    结论3:结构体的内部元素的偏移量一定是min(其基本元素对齐大小, 预设对齐大小)的整数倍。
    结构体的内部元素的偏移量是其基本元素对齐大小的整数倍。//这是错的
    因此,算偏移量的时候,都是将地址按min(其基本元素对齐大小, 预设对齐大小)大小,想象成一段一段的。(这种做法比较慢,但是适合不熟悉对齐规则的童鞋)
struct A{    char a;    int b;    //b的偏移量为4,不是1.  4是4的倍数};//----------------------------#pragma pack(2)struct A{    char a;    int b;    //b的偏移量为2};
  • 字符填充padding
    为了保证结论2,有时候需要做必要的填充,
struct A{    int a;    char b;    //b的偏移量为4  4是1的倍数};//表面上字符的大小是5,但不是4(=align(A))的倍数,需要做字符填充,类似如下结构体struct A{    int a;    char b;    //b的偏移量为4  4是1的倍数    char c[3];};
  • 关于位域的影响
    结论4:不受结构体的真正对齐大小影响,将相应的位数按前面的类型说明符补齐。比如测试案例A11, A12
    对于位域之和超过基本元素对齐大小的时候。从第一个开始超过的位域进行偏移,确保它的地址是min(其基本元素对齐大小, 预设对齐大小)大小。 比如测试案例A13

然后把它当成一个整的类型看就行。

struct A{    short s;    int a1 : 4;    //补齐int后面的28位,完全等效于一个int整体  };
  • C++相关部分 (类跟结构等效)
    结论5:对于一个空类,其默认sizeof()为1,align()为1;对于一个包含虚函数的空类,其默认sizeof()为4,align()为4
    //详见测试案例Base, Base2

结论6:对于继承空基类的话,基类默认不占用空间;如果是包含成员对象的话,则该对象的大小是1。
其他部分的讨论,看看测试案例,看看C部分就行。
//详见测试案例C1, C3

参考资料

  1. 失传的C结构体打包技艺   里面主要介绍关于C语言方面的对齐知识
  2. 如何理解struct的内存对齐  虽然里面有些错的,但还是有参考价值
  3. Wiki Data Structure Alignment 维基关于对齐的解释,里面的术语很专业
  4. msdn #pragma pack()介绍
  5. c/c++数据对齐

写作之外

如果有什么好的建议,或者有什么测试案例可以添加的欢迎留言交流。如果这篇写作对你有所帮助,请点帮忙点个赞,让我知道我的写作确实帮助到别人。如果没有帮助的话,请忽视前一句。欢迎指出不足。