当前位置: 首页 > news >正文

结构体对齐和结构体相关宏

1、结构体的对齐访问

1.1、通过示例理解结构体对齐是什么?

(1)结构体中元素的内存对齐示例:

#include <stdio.h>// 定义一个包含不同数据类型成员的结构体
struct MyStruct
{char a;     // 1 字节对齐int b;      // 4 字节对齐short c;    // 2 字节对齐char d;     // 1 字节对齐
};int main()
{printf("Size of struct MyStruct: %d bytes\r\n", (int)sizeof(struct MyStruct));  struct MyStruct s;// 输出结构体中各成员的地址,观察其对齐情况printf("Address of a: %p\n", &s.a);printf("Address of b: %p\n", &s.b);printf("Address of c: %p\n", &s.c);printf("Address of d: %p\n", &s.d);return 0;
}

执行结果:

Size of struct MyStruct: 12 bytes
Address of a: 000000000061FE14
Address of b: 000000000061FE18
Address of c: 000000000061FE1C
Address of d: 000000000061FE1E

(2)示例说明

  • 结构体 MyStruct 包含了不同数据类型成员,它们具有不同的对齐要求。char 类型通常为 1 字节对齐,int 类型一般为 4 字节对齐,short 类型一般为 2 字节对齐。
  • 当编译器为结构体分配内存时,会按照一定的规则(通常是基于系统架构和编译器的对齐策略)进行对齐。
    • 例如,在大部分常见的系统中,成员 a 占用 1 字节,之后为了满足 int 类型 4 字节对齐的要求,编译器会在 ab 之间插入 3 个字节的填充,使得 b 的地址满足 4 字节对齐的条件。
    • 同样地,b 占用 4 字节后,c 需要满足 2 字节对齐,此时如果 b 结束的地址没有满足 2 字节对齐,编译器会在 bc 之间插入填充字节。
    • 最后,d 后面也可能会根据整个结构体对齐要求进行填充,以确保结构体的整体大小满足最大成员对齐要求,示例中整个结构体的大小可能大于各成员大小之和,这是因为填充字节的存在。

1.2、结构体为什么需要内存对齐

(1)结构体中元素对齐访问主要原因是为了配合硬件,也就是说硬件本身有物理上的限制,如果对齐访问会提高效率,否则会大大降低效率。

(2)对比对齐访问和不对齐访问:对齐访问牺牲了内存空间,换取了速度性能;而非对齐访问牺牲了访问速度性能,换取了内存空间的完全利用。

1.3、结构体对齐的规则

(1)编译器本身可以设置内存对齐的规则,在32位芯片的编译器中,一般编译器默认对齐方式是4字节对齐。

(2)结构体对齐的关键,编译器为4字节对齐时:

  • 结构体整体本身起始位置在4字节对齐处,结构体对齐后的大小必须4的倍数。
  • 结构体中每个元素本身都必须对其存放,而每个元素本身都有自己的对齐规则。
    • char是1字节对齐
    • short是2字节对齐
    • int是4字节对齐
    • float是4字节对齐
    • double是8字节对齐
  • 不同数据类型的对齐字节数不同:
    • 因为对齐地址可以把相邻的4字节中的数据一次性取出,从而提高访问效率。
    • double 在 32 位编译器中是 8 字节对齐,这主要是为了内存访问效率和浮点运算单元的要求,而非仅仅因为 32 位处理器一次读取 4 字节。
  • 编译器考虑结构体存放时,以满足以上要求的最少内存需要来排布元素。

1.4、gcc支持但不推荐的对齐指令

(1)对齐指令:   

  • #pragma pack(n) :从当前位置开始,将当前文件的对齐方式设置为n字节对齐,n的常见取值为 1、2、4、8 等。
  • #pragma pack(): 将对齐方式恢复到编译器的默认对齐方式 。

(2)示例代码,设置1字节对齐:

#include <stdio.h>#pragma pack(1)
// 定义一个包含不同数据类型成员的结构体
struct MyStruct
{char a;     // 1 字节int b;      // 4 字节short c;    // 2 字节char d;     // 1 字节
};
#pragma pack()int main()
{// Size of struct MyStruct: 8 bytesprintf("Size of struct MyStruct: %d bytes\r\n", (int)sizeof(struct MyStruct));return 0;
}

(3)#pragma是用来指挥编译器,或者说设置编译器的对齐方式的。编译器的默认对齐方式是4,但是有时候我不希望对齐方式是4,而希望是别的(譬如希望1字节对齐,也可能希望是8,甚至可能希望128字节对齐)。

(4)我们需要#prgama pack(n)开头,以#pragma pack()结尾,定义一个区间,这个区间内的对齐字节数就是n。

(5)#prgma pack的方式在很多C环境下都是支持的,但是gcc虽然也可以不过不建议使用。

1.5、gcc推荐的对齐指令

(1)对齐指令:

  • __attribute((packed)) / __attribute__((packed))  
    • 设置为1字节对齐。
    • 指定该数据类型(通常是结构体或联合体)的成员以最紧凑的方式存储,即每个成员紧跟在前一个成员之后,不进行任何填充对齐,从而可以尽可能地节省内存空间。
    • 作用的范围只有加了这个东西的这一个类型。
    • __attribute((packed))__attribute__((packed)) 在功能上是相同的,现代代码中通常使用__attribute__((packed))这种写法,因为它更符合规范。
    • 作用于变量无效。
  • __attribute__((aligned(n)))
    • 用于指定变量、结构体或类型的内存对齐方式。
    • __attribute__((aligned(n)))作用于结构体时,作用是让整个结构体变量整体进行n字节对齐,而不是结构体内各元素也要n字节对齐。
    • 既可以作用于类型,也可作用于变量。
      char c __attribute__((aligned(4))); // 指定变量 c 按 4 字节对齐

(2)__attribute__((packed))使用示例:

#include <stdio.h>// 定义一个包含不同数据类型成员的结构体
struct MyStruct1
{char a;     // 1 字节int b;      // 4 字节short c;    // 2 字节char d;     // 1 字节
}__attribute__((packed));struct __attribute__((packed)) MyStruct2
{char a;     // 1 字节int b;      // 4 字节short c;    // 2 字节char d;     // 1 字节
};int main()
{// Size of struct MyStruct1: 8 bytesprintf("Size of struct MyStruct1: %d bytes\r\n", (int)sizeof(struct MyStruct1));// Size of struct MyStruct1: 8 bytesprintf("Size of struct MyStruct2: %d bytes\r\n", (int)sizeof(struct MyStruct2));return 0;
}

2、offsetof宏与container_of宏

2.1、offsetof宏

(1)offsetof宏的作用是:用宏来计算结构体中某个元素和结构体首地址的偏移量,其实质是通过编译器来计算的。

(2)使用示例:

#include <stdio.h>struct mystruct
{char a;int b;short c;
};// offsetof宏的原理:TYPE为结构体类型,MEMBER为结构体元素
// ((TYPE *)0): 虚拟一个type类型结构体指针,指向首地址为0的结构体变量。
// 实际上这个结构体变量可能不存在,但是只要我不去解引用这个指针就不会出错。// ((TYPE *)0)->MEMBER:然后用->的方式来访问结构体元素;
// &((TYPE *)0)->MEMBER):再然后通过取地址符获得结构体元素的地址;// 因为虚拟的结构体指针变量指向的结构体的首地址为0,
// 所以获得的 结构体元素的地址 就等效为 结构体元素相对于整个结构体变量首地址的偏移量。
#define offsetof(TYPE, MEMBER)    ((int)&((TYPE *)0)->MEMBER)int main(void)
{struct mystruct s1;s1.b =12;// 手工计算结构体元素b的地址int *p1 = (int *)((char *)&s1 + 4);printf("*p1 = %d.\n", *p1);// 通过offsetof宏计算元素b的地址int *p2 = (int *)((int)&s1 + offsetof(struct mystruct, b));printf("*p2 = %d.\n", *p2);// 计算结构体元素相对于整个结构体变量首地址的偏移量int offsetof_a = offsetof(struct mystruct, a);printf("offsetof_a = %d.\n", offsetof_a);int offsetof_b = offsetof(struct mystruct, b);printf("offsetof_b = %d.\n", offsetof_b);int offsetof_c = offsetof(struct mystruct, c);printf("offsetof_c = %d.\n", offsetof_c);return 0;
}

2.2、container_of宏

(1)作用:知道一个结构体中某个元素的指针,反推这个结构体变量的指针。

(2)有了container_of宏,我们可以从一个元素的指针得到整个结构体变量的指针,继而得到结构体中其他元素的指针。

(3)typeof关键字的作用是:由变量名得到变量的数据类型。

int a;  typeof(a)  b;      // 定义了一个和a数据类型相同的变量b。

(4)使用示例

#include <stdio.h>struct mystruct
{char a;int b;short c;
};// 宏说明: TYPE是结构体类型,MEMBER是结构体中一个元素的元素名;
// 宏返回的是MEMBER元素相对于整个结构体变量的首地址的偏移量,类型是int.
#define offsetof(TYPE, MEMBER)    ((int)&((TYPE *)0)->MEMBER)// 宏说明:ptr是指向结构体元素member的指针,type是结构体类型,member是结构体中一个元素的元素名;
// 宏返回的就是指向结构体变量的指针,类型是(type *).
#define container_of(ptr, type, member)  ({                \const typeof(((type *)0)->member) * __mptr = (ptr);    \(type *)((char *)__mptr - offsetof(type, member));  })int main(void)
{struct mystruct s1;struct mystruct *pS = NULL;short *p = &s1.c;                 // 指向结构体中成员c的指针// 一般情况获取结构体变量的地址printf("s1的地址等于:%p.\n", &s1);// 已知结构体成员的指针,计算结构体变量的指针。pS = container_of(p, struct mystruct, c);printf("pS等于:%p.\n", pS);return 0;
}

(5)宏原理说明:

// 宏说明: TYPE是结构体类型,MEMBER是结构体中一个元素的元素名;
// 宏返回的是MEMBER元素相对于整个结构体变量的首地址的偏移量,类型是int.
#define offsetof(TYPE, MEMBER)    ((int)&((TYPE *)0)->MEMBER)// 宏说明:ptr是指向结构体元素member的指针,type是结构体类型,member是结构体中一个元素的元素名;
// 宏返回的就是指向结构体变量的指针,类型是(type *).
#define container_of(ptr, type, member)  ({                \const typeof(((type *)0)->member) * __mptr = (ptr);    \(type *)((char *)__mptr - offsetof(type, member));  })
  • ((type *)0): 虚拟一个type类型结构体指针,指向首地址为0的结构体变量。
  • (((type *)0)->member): 通过->访问结构体成员member。
  • typeof(((type *)0)->member): 通过tyeof得到结构体成员member的数据类型。
  • const typeof(((type *)0)->member) * __mptr: 定义指向member类型的指针变量__member。const表示指针指向的内容不能被修改,因为是虚拟的结构体变量,解引用会内存非法访问。
  • const typeof(((type *)0)->member) * __mptr = (ptr);  :结构体成员变量指针赋值。
  • (type *)((char *)__mptr - offsetof(type, member));  :结构体成员变量的地址 减去 该成员相对于整个结构体变量的首地址的偏移量,得到结构体的地址。

相关文章:

  • 零基础开始的网工之路第十六天------Linux安全管理
  • HTML实战:爱心图的实现
  • 如何用命令行将 PDF 表格转换为 HTML 表格
  • wsl2 docker重启后没了
  • 国芯思辰| 霍尔电流传感器AH811为蓄电池负载检测系统安全护航
  • 前端开源JavaScrip库
  • 界面开发框架DevExpress XAF实践:集成.NET Aspire后如何实现自定义遥测?
  • 如何使用.Net Reactor 批量加密 DLL
  • Axure RP11安装、激活、汉化
  • springcloud openfeign 请求报错 java.net.UnknownHostException:
  • Axure项目实战:驾驶舱(数据一张图)制作教程
  • 同为.net/C#的跨平台运行时的mono和.net Core有什么区别?
  • 2025年- H56-Lc164--200.岛屿数量(图论,深搜)--Java版
  • 深入了解 C# 异步编程库 AsyncEx
  • CppCon 2014 学习第2天:Using Web Services in C++
  • 【Java Web】速通JavaScript
  • Flutte ListView 列表组件
  • OpenCV CUDA模块结构分析与形状描述符------计算指定阶数的矩(Moments)所需的总数量函数:numMoments
  • 小程序 - 视图与逻辑
  • React从基础入门到高级实战:React 生态与工具 - React Query:异步状态管理
  • 电子元器件商城网站建设/汕头seo推广外包
  • 石家庄建站程序/做网站推广一般多少钱
  • 网站建设报价 东莞/吉林网站seo
  • 做电影网站需要多大空间/湖南seo优化首选
  • 需要注册的企业网站/小程序搭建教程
  • 那个网站报道过鸟巢建设/十大免费无代码开发软件