C语言——内存对齐总结

1、什么是内存对齐?

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

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

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

//内存对齐计算验证实例
#include <stdio.h> 
#include <stddef.h>

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;
}

程序输出如下图所示:
这里写图片描述


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 <stdio.h> 
#include <stddef.h>

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;
}

程序输出如下图所示:
这里写图片描述


#include <stdio.h> 
#include <stddef.h>

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;
}

程序输出如下图所示:
这里写图片描述


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

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;
}

程序输出如下图所示:
这里写图片描述


#include <stdio.h> 
#include <stddef.h>

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;
}

程序输出如图所示:
这里写图片描述


#include <stdio.h> 
#include <stddef.h>

#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语言中,结构体的内存对齐是一种优化手段,用于提高内存访问效率和减少内存浪费。结构体的内存对齐规则是根据结构体成员的类型和顺序,以及编译器的对齐方式来确定的。 根据引用中的例子,我们可以看到结构体s1和s2的成员顺序相同,但是它们的内存对齐结果却不同。结构体s1的大小为8字节,结构体s2的大小为12字节。这是因为编译器在对齐结构体时,会根据最大成员的大小来确定对齐方式。在结构体s1中,最大成员是int类型的c,大小为4字节,所以结构体s1的对齐方式是4字节对齐。而在结构体s2中,最大成员是char类型的b,大小为1字节,所以结构体s2的对齐方式是1字节对齐。 另外,根据引用的例子,我们可以看到结构体s2中嵌套了结构体s1。通过使用offsetof函数,我们可以得到结构体s2中成员a和成员c的偏移量分别为0和4字节,这表明结构体s2中的成员是按照其在结构体中的声明顺序进行排列的。同时,结构体s2的大小为12字节,这是因为结构体s2的对齐方式是最大成员char类型b的大小1字节。 总结起来,C语言结构体的内存对齐是根据结构体成员的类型和顺序,以及编译器的对齐方式来确定的。这种对齐可以提高内存访问效率和减少内存浪费。不同的结构体可能有不同的对齐方式和大小,这取决于结构体中最大成员的大小和结构体中的成员顺序。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【C语言系列】-结构体中的内存对齐](https://blog.youkuaiyun.com/m0_64332179/article/details/122682708)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [【C语言】——结构体进阶:结构体的内存对齐(超详细)](https://blog.youkuaiyun.com/luoheng1114/article/details/127106154)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值