C语言——内存对齐总结

1、什么是内存对齐?

将程序中的每个数据单元安排在适当的位置上(这是编译器干的事)

编译器将程序中的每个数据单元安排在合适的位置上,C语言的灵活性允许程序员干预内存对齐,如果想要了解更加底层的秘密,就不应该继续对内存对齐保持透明了

以下所有程序的运行环境都是windows 7,Dev-C++ IDE,编译器版本是TDM-GCC 4.9.2 64-bit Debug

//内存对齐计算验证实例
#include  
#include 

typedef struct test{
    char a;
    int b;
    double c;
    char d;
}Test;

int main(int argc, char ** argv)
{
    Test T; 

    int offset_a = offsetof(Test, a);
    int offset_b = offsetof(Test, b);
    int offset_c = offsetof(Test, c);
    int offset_d = offsetof(Test, d);

    printf("The length of char type is %u\n", sizeof(char));
    printf("The length of int type is %u\n", sizeof(int));
    printf("The length of double type is %u\n\n", sizeof(double));

    printf("The length of the whole struct is %u\n\n", sizeof(T));

    printf("The offset of first data member is %d\n", offset_a);
    printf("The offset of second data member is %d\n", offset_b);
    printf("The offset of third data member is %d\n", offset_c);
    printf("The offset of fourth data member is %d\n\n", offset_d);

    printf("The begin address of first data member of struct test is %p\n", &(T.a));
    printf("The begin address of second data member of struct test is %p\n", &(T.b));
    printf("The begin address of third data member of struct test is %p\n", &(T.c));
    printf("The begin address of fourth data member of struct test is %p\n\n", &(T.d));

    return 0;
}

程序输出如下图所示:
C语言——内存对齐总结_第1张图片


2、需要内存对齐的原因

  • 不是所有的硬件平台都能访问任意地址上的任意数据(某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常)
  • 可以在相当大的程度上提高程序性能(以空间换时间

3、内存对齐的主要应用范围

对于内存对齐问题,主要存在于struct和union等复合结构在内存中的分布情况中(或者说在这两种复合结构中内存对齐的影响比较明显)


4、内存对齐的规则

  • 原则1:对于struct和union来说,其第一个数据成员要放在offset==0的地方。如果第一个数据成员为某个复合结构的子成员,则要根据子成员的类型存放在对应的整数倍的地址上
  • 原则2:结构体成员按自身长度自然对齐(所谓自然对齐,指的是该成员的起始位置的内存地址必须是它自身长度的整数倍)。如果结构体作为成员,则要找到这个结构体中的最大元素,然后从这个最大成员长度的整数倍地址开始存储
  • 原则3:结构体的总大小为结构体的有效对齐值的整数倍

结构体的有效对齐值的确定:

  • 当未明确指定时,以结构体中最长成员的长度为其有效对齐值
  • 当用#pragma pack(n)指定时,以n和结构体中最长成员的长度中较小者为其有效对齐值
  • 当用__attribute__ ((__packed__))指定长度时,强制按照此值为结构体的有效对齐值

5、内存对齐带来的问题

编译器为了满足内存对齐要求,会在各个数据成员之间留下额外的内存空间,这会造成很小的浪费。但是这种浪费是可以容忍的


6、补充说明

  • 内存对齐对于32位和64位平台有些许不同(因为32位的cpu一次最多能处理32bits的信息,64位的cpu一次最多能处理64bits的信息)
  • 使用sizeof运算符可以得到struct和union的确切大小(其中包含了因内存对齐所产生的内存碎片)
  • 可以使用offsetof宏来得到一个数据成员在结构体中的位置
  • 编译器会对内存的分布进行优化,所以理论计算值和实际输出值有时会有些许不同
  • wnidows的默认对齐数为8,Linux的默认对齐数是4(这两个数对offset地址的选取有影响)
  • 结构体中数据成员的顺序可能会影响整个结构体所占内存的大小
  • 无论#pragma pack__attribute__如何指定,结构内部数据成员的自对齐仍然按照其自身的长度来对齐

#include  
#include 

typedef struct test{
    int a; //原则1
    double b; //原则2
    float c; //原则3
}Test;

int main(int argc, char ** argv)
{
    Test T; 

    int offset_a = offsetof(Test, a);
    int offset_b = offsetof(Test, b);
    int offset_c = offsetof(Test, c);

    printf("The length of int type is %u\n", sizeof(int));
    printf("The length of double type is %u\n", sizeof(double));
    printf("The length of float type is %u\n\n", sizeof(float));

    printf("The length of the whole struct is %u\n\n", sizeof(T));

    printf("The offset of first data member is %d\n", offset_a);
    printf("The offset of second data member is %d\n", offset_b);
    printf("The offset of third data member is %d\n\n", offset_c);

    printf("The begin address of first data member of struct test is %p\n", &(T.a));
    printf("The begin address of second data member of struct test is %p\n", &(T.b));
    printf("The begin address of third data member of struct test is %p\n\n", &(T.c));

    return 0;
}

程序输出如下图所示:
C语言——内存对齐总结_第2张图片


#include  
#include 

typedef struct test{
    int a;
    double b;
    float c;
}Test;

typedef struct test1{
    char a[2];
    int b;
    double c;
    short d;
    Test e;
}Test1;

int main(int argc, char ** argv)
{
    Test1 T;    

    int offset_a = offsetof(Test1, a);
    int offset_b = offsetof(Test1, b);
    int offset_c = offsetof(Test1, c);
    int offset_d = offsetof(Test1, d);
    int offset_e = offsetof(Test1, e);

    printf("The length of char type is %u\n", sizeof(char));
    printf("The length of int type is %u\n", sizeof(int));
    printf("The length of double type is %u\n", sizeof(double));
    printf("The length of short type is %u\n", sizeof(short));
    printf("The length of Test type is %u\n\n", sizeof(Test));

    printf("The length of the whole struct is %u\n\n", sizeof(T));

    printf("The offset of first data member is %d\n", offset_a);
    printf("The offset of second data member is %d\n", offset_b);
    printf("The offset of third data member is %d\n", offset_c);
    printf("The offset of fourth data member is %d\n", offset_d);
    printf("The offset of fifth data member is %d\n\n", offset_e);

    printf("The begin address of first data member of struct test is %p\n", &(T.a));
    printf("The begin address of second data member of struct test is %p\n", &(T.b));
    printf("The begin address of third data member of struct test is %p\n", &(T.c));
    printf("The begin address of third data member of struct test is %p\n", &(T.d));
    printf("The begin address of third data member of struct test is %p\n\n", &(T.e));

    return 0;
}

程序输出如下图所示:
C语言——内存对齐总结_第3张图片


//不同的编译器会对内存的分布进行优化,例如有的编译器可能会将该程序优化成下一个程序
//这是属于编译器的问题,这里不做详细的讨论
//尽量在保持代码清晰的情况下,自己手动将该程序优化为下一个程序那样
//如果是单纯的做题,不用考虑编译器的优化问题,一切按照理论进行计算
#include  
#include 

typedef struct test{
    char a;
    int b;
    char c;
}Test;


int main(int argc, char ** argv)
{
    Test T; 

    int offset_a = offsetof(Test, a);
    int offset_b = offsetof(Test, b);
    int offset_c = offsetof(Test, c);


    printf("The length of char type is %u\n", sizeof(char));
    printf("The length of int type is %u\n\n", sizeof(int));


    printf("The length of the whole struct is %u\n\n", sizeof(T));

    printf("The offset of first data member is %d\n", offset_a);
    printf("The offset of second data member is %d\n", offset_b);
    printf("The offset of third data member is %d\n\n", offset_c);

    printf("The begin address of first data member of struct test is %p\n", &(T.a));
    printf("The begin address of second data member of struct test is %p\n", &(T.b));
    printf("The begin address of third data member of struct test is %p\n\n", &(T.c));

    return 0;
}

程序输出如下图所示:
C语言——内存对齐总结_第4张图片


#include  
#include 

typedef struct test{
    char a;
    char b;
    int c;
}Test;


int main(int argc, char ** argv)
{
    Test T; 

    int offset_a = offsetof(Test, a);
    int offset_b = offsetof(Test, b);
    int offset_c = offsetof(Test, c);


    printf("The length of char type is %u\n", sizeof(char));
    printf("The length of int type is %u\n\n", sizeof(int));


    printf("The length of the whole struct is %u\n\n", sizeof(T));

    printf("The offset of first data member is %d\n", offset_a);
    printf("The offset of second data member is %d\n", offset_b);
    printf("The offset of third data member is %d\n\n", offset_c);

    printf("The begin address of first data member of struct test is %p\n", &(T.a));
    printf("The begin address of second data member of struct test is %p\n", &(T.b));
    printf("The begin address of third data member of struct test is %p\n\n", &(T.c));

    return 0;
}

程序输出如图所示:
C语言——内存对齐总结_第5张图片


#include  
#include 

#pragma pack(2)

typedef struct test{
    char a;
    int b;
    char c;
}Test;


int main(int argc, char ** argv)
{
    Test T; 

    int offset_a = offsetof(Test, a);
    int offset_b = offsetof(Test, b);
    int offset_c = offsetof(Test, c);


    printf("The length of char type is %u\n", sizeof(char));
    printf("The length of int type is %u\n\n", sizeof(int));


    printf("The length of the whole struct is %u\n\n", sizeof(T));

    printf("The offset of first data member is %d\n", offset_a);
    printf("The offset of second data member is %d\n", offset_b);
    printf("The offset of third data member is %d\n\n", offset_c);

    printf("The begin address of first data member of struct test is %p\n", &(T.a));
    printf("The begin address of second data member of struct test is %p\n", &(T.b));
    printf("The begin address of third data member of struct test is %p\n\n", &(T.c));

    return 0;
}

程序输出如下图所示:
C语言——内存对齐总结_第6张图片

你可能感兴趣的:(C语言)