结构体字节对齐问题探究

简介: 结构体字节对齐问题探究

结构体字节对齐


在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。


从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是内存对齐。


32、64位数据类型占用字节


首先我们要知道操作系统存储各个类型的数据所要各自占用的字节,可以参考我整理的另一篇文章,里面也有内存对其原理的简单介绍:

32、64位数据类型占用字节以及内存对齐原理


内存对齐


内存对齐原因


我们知道内存的最小单元是一个字节,当cpu从内存中读取数据的时候,是一个一个字节读取,所以内存对我们来说应该是入下图这样:



但是实际上cpu将内存当成多个块,每次从内存中读取一个块,这个块的大小可能是2、4、8、16等,实际他是这样的



内存对齐是操作系统为了提高访问内存的策略。操作系统在访问内存的时候,每次读取一定长度(这个长度是操作系统默认的对齐数,或者默认对齐数的整数倍)。如果没有对齐,访问一个变量可能会产生二次访问的问题。


至此大家应该能够简单明白,为什么要内存对齐?


它提高存取数据的速度。


比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。


还有某些平台只能在特定的地址处访问特定类型的数据,否则抛出硬件异常给操作系统。


如何内存对齐


对于标准数据类型,它的地址只要是它的长度的整数倍。


例如int,short,char…


对于非标准数据类型,比如结构体,要遵循一下对齐原则:


  1. 数组成员对齐规则。第一个数组成员应该放在offset为0的地方,以后每个数组成员应该放在offset为min(当前成员的大小,#pargama pack(n))整数倍的地方开始(比如int在32位机器为4字节,#pargama pack(2),那么从2的倍数地方开始存储)。


  1. 结构体总的大小,也就是sizeof的结果,必须是min(结构体内部最大成员,#pargama pack(n))的整数倍,不足要补齐。


  1. 结构体做为成员的对齐规则。如果一个结构体B里嵌套另一个结构体A,还是以最大成员类型的大小对齐,但是结构体A的起点为A内部最大成员的整数倍的地方。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。


手动设置对齐模数:


#pragma pack(show)



显示当前packing alignment的字节数,以warning message的形式被显示。


#pragma pack(push)


将当前指定的packing alignment数组进行压栈操作,这里的栈是the internal compiler stack,同事设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数组压栈。


#pragma pack(pop) 


从internal compiler stack中删除最顶端的reaord; 如果没有指定n,则当前栈顶record即为新的packing alignement数值;如果指定了n,则n成为新的packing alignment值


#pragma pack(n)


指定packing的数值,以字节为单位,缺省数值是8,合法的数值分别是1,2,4,8,16。


案例:


看下面两个结构体:


Student


typedef struct _STUDENT {
  int a;
  char b;
  double c;
  float d;
}Student;


a从偏移量0位置开始存储


b从4位置开始存储


c从8位置开始存储


d从12位置开存储


所以Student内部对齐之后的大小为20 ,整体对齐,整体为最大类型的整数倍 也就是8的整数倍 为24



Student2


typedef struct _STUDENT2 {
  char a;
  Student b;
  double c;
}Student2;


a从偏移量为0位置开始


b从偏移量为Student内部最大成员整数倍开始,也就是8开始


c从8的整数倍地方开始,也就是32开始


所以结构体Sutdnet2内部对齐之后的大小为:40 , 由于结构体中最大成员为8,必须为8的整数倍 所以大小为40



全部测试代码



#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#pragma pack(4)
typedef struct _STUDENT {
  int a;
  char b;
  double c;
  float d;
}Student;
typedef struct _STUDENT2 {
  char a;
  Student b;
  double c;
}Student2;
void test() {
  //Student
  /*a从偏移量0位置开始存储
  b从4位置开始存储
  c从8位置开始存储
  d从12位置开存储
  所以Student内部对齐之后的大小为20 ,整体对齐,整体为最大类型的整数倍 也就是8的整数倍 为24*/
  printf("sizeof Student:%d\n", sizeof(Student));
  //Student2 
  /*a从偏移量为0位置开始 
  b从偏移量为Student内部最大成员整数倍开始,也就是8开始
  c从8的整数倍地方开始,也就是32开始
  所以结构体Sutdnet2内部对齐之后的大小为:40 , 由于结构体中最大成员为8,必须为8的整数倍 所以大小为40*/
  printf("sizeof Student2:%d\n", sizeof(Student2));
}
int main()
{
  test();
  return 0;
}


相关文章
|
7月前
|
编译器 Linux C语言
详解结构体内存对齐及结构体如何实现位段~
详解结构体内存对齐及结构体如何实现位段~
|
6月前
|
编译器 C语言
C语言深度理解之——结构体内存对齐
C语言深度理解之——结构体内存对齐
57 1
|
5月前
|
存储 程序员 编译器
|
7月前
|
存储 网络协议 编译器
【C语言】自定义类型:结构体深入解析(三)结构体实现位段最终篇
【C语言】自定义类型:结构体深入解析(三)结构体实现位段最终篇
|
存储 C语言 C++
【C语言】一篇让你彻底吃透(结构体与结构体位段)(下)
【C语言】一篇让你彻底吃透(结构体与结构体位段)(下)
110 0
|
编译器 C++
C进阶:结构体的内存对齐
C进阶:结构体的内存对齐
89 0
|
存储 编译器 C语言
深度理解C语言六——结构体内存对齐和结构体所占内存空间的大小
深度理解C语言六——结构体内存对齐和结构体所占内存空间的大小
161 0
深度理解C语言六——结构体内存对齐和结构体所占内存空间的大小
|
存储 编译器 C语言
【C语言】一篇让你彻底吃透(结构体与结构体位段)(上)
【C语言】一篇让你彻底吃透(结构体与结构体位段)(上)
79 0
|
编译器 C++
自定义类型之结构体的基础和进阶(有关位段、结构体自引用、嵌套、内存对齐、修改对齐数、结构体的传参、和offsetof宏的使用)
一、结构体基础知识 二、结构体的进阶(有关结构体的自引用,嵌套,内存对齐和内存设计) (一、)首先是结构体的嵌套 (二、)结构体的自引用 (三、)结构体的内存对齐(如何计算结构体的所占内存大小) (四、)如何修改默认对齐数 三、offsetof的意思 四、结构体的传参 五、位段的使用和注意 总结:
|
编译器 程序员 Linux
关于c语言结构体偏移的一点思考(一)
关于c语言结构体偏移的一点思考(一)
128 0