深入理解字节对齐

基础知识了解

一、操作系统位数、CPU位数、指令集

1.操作系统–32-bit(x86)和64-bit(x64)

(1)位数

  每个时钟周期内,处理器处理二进制代码数,即“0”和“1”的个数。

(2)64-bit

  优点:①可以进行更大范围的整数运算;②可以支持更大的内存。

2.处理器CPU位数

  CPU位数=CPU中寄存器的位数=CPU能够一次并行处理的数据宽度(位数)=数据总线宽度;
  //现在的计算机处理器一般都是64位,这是硬件的事。

3.CPU指令集

  CPU为了实现其功能设计了指令集,即是CPU的全部指令,这是机器语言。计算机的所有功能都是基于CPU的指令集。指令集和CPU的位数是有联系的。

  指令集的作用, 就是告诉程序员/编译器, 汇编一定要有格式. 支持什么指令, 指令带什么限制条件, 用什么操作数, 用什么地址, 都是指令集规范的内容, 要是写错了, 就无法翻译成机器码.

  指令集规范汇编, 汇编可以翻译成机器码, 机器码告诉CPU每个周期去做什么. 因此, CPU指令集是描述CPU能实现什么功能的一个集合, 就是描述"CPU能使用哪些机器码"的集合"。X86是指令集的代号。
  那么指令集在CPU里就代表: 只有CPU指令集范围内的指令可以被成功的译码, 并送往CPU流水线后端去执行.

4.寄存器

  寄存器是CPU内部用来创建和储存CPU运算结果和其它运算结果的地方。

5.关系

  操作系统位数 = 其所依赖的指令集位数 <= CPU位数。

6.计算机字长(机器字长)

  计算机字长(机器字长)取决于数据总线的宽度,通常就是CPU一次能处理的数据的位数(CPU位数)。

7.总结

  在标准c++中,int的定义长度要依靠你的机器的字长,也就是说,如果你的机器是32位的,int的长度为32位,如果你的机器是64位的,那么int的标准长度就是64位,比如16位机上,sizeof(int) = 2,而32位机上sizeof(int) = 4;32位机上sizeof(long) = 4,而64位机上sizeof(long) = 8。

  但是在32位机器和64机器中int类型都占用4个字节,因为一般编译器可以根据自身硬件来选择合适的大小,但是需要满足约束:short和int型至少为16位,long型至少为32位,并且short型长度不能超过int型,而int型不能超过long型

  这即是说各个类型的变量长度是由编译器来决定的,而当前主流的编译器中一般是32位机器和64位机器中int型都是4个字节(例如,GCC)。

理解字节对齐

二、字节对齐

1.什么是字节对齐?

  现代计算机中,内存空间按照字节划分,理论上可以从任何起始地址访问任意类型的变量。但实际中在访问特定类型变量时经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序一个接一个地存放,这就是对齐。

  变量存的起始地址必须具备某些特性----“对齐”,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除。


  对齐跟数据在内存中的位置有关。为了使得CPU能快速对变量进行访问,变量存的起始地址必须具备某些特性,即“对齐”,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除。====>>>>cpu类型,编译器类型。

2.为什么要字节对齐

①首先,不同硬件平台对存储空间的处理上存在不同;

  某些平台对特定类型的数据只能从特定地址开始存取,而不允许其在内存中任意存放。例如,Motorola 68000 处理器不允许16位的字存放在奇地址,否则会触发异常,因此在这种架构下编程必须保证字节对齐。

②根本原因在于CPU访问数据的效率问题;

  以32位机为例,它每次取32个位,也就是4个字节。以int型数据为例,如果它在内存中存放的位置按4字节对齐,也就是说1个int的数据全部落在计算机一次取数的区间内,那么只需要取一次就可以了。如图2-1。如果不对齐,很不巧,这个int数据刚好跨越了取数的边界,这样就需要取两次才能把这个int的数据全部取到,如图2-2,这样效率也就降低了。
在这里插入图片描述

③其次节约空间。

  应该辩证地看:合理对齐,则节约空间;否则浪费空间。如下例:
在这里插入图片描述
  结构体TEST1中包含一个4字节的int数据,一个1字节char数据和一个2字节short数据;TEST2也一样。按理说TEST1和TEST2的大小应该都是7字节。之所以出现上述结果,就是因为编译器要对数据成员在空间上进行对齐。

3.如何对齐

3.1 标准类型(基本数据类型):

  基本类型包括char、int、float、double、short、long等基本数据类型。
  对齐要求:起始地址为其长度的整数倍即可。如,int类型的变量起始地址要求为4的整数倍。
思考:int究竟占多少个字节?
  **操作系统:**每个时钟周期内,处理器处理二进制代码数,即“0”和“1”的个数。如64-bit,32-bit
  **CPU位数:**CPU中寄存器的位数=CPU能够一次并行处理的数据宽度(位数)=数据总线宽度;64-bit,32-bit。
  **关系:**操作系统位数 = 其所依赖的指令集位数 <= CPU位数。

int的定义长度要依靠机器的字长,由编译器结合自身硬件选择。

约束条件: short和int型至少为16位,long型至少为32位,并且short型长度不能超过int型,而int型不能超过long型。这即是说各个类型的变量长度是由编译器来决定的,而当前主流的编译器中一般是32位机器和64位机器中int型都是4个字节。

  理论上,int代表机器自然长度,即CPU位数,在32位CPU,应该占4字节,64位CPU,应该占8字节。
  实际上,由编译器和CPU共同决定,主流的编译器编译程序将int解释为4字节。
在这里插入图片描述

3.2 数组:

  按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。

3.3 结构体

  成员可以为:基本类型,复合类型(基本类型、数组、结构、联合、函数指针)
  结构体中每个数据类型都要对齐,对齐值位其成员中自身对齐值最大的那个值。
在这里插入图片描述在这里插入图片描述

3.4 联合体

  联合体的内存除了取最大成员内存外,还要保证是所有成员类型size的最小公倍数。
在这里插入图片描述
  
   当然只取最大的int数组的大小12没错,但是double是8字节的,而此时联合体已经按int的4字节对齐了,所以还要额外多加4字节的内存来保证8的倍数。所以最后结果是16。

  所以联合体的内存除了取最大成员内存外,还要保证是所有成员类型size的最小公倍数

3.5 指定对齐方式

   #pragma pack(n)   //设置n字节对齐
  #pragma pack()   //取消自定义字节对齐方式

  以n和结构体中最长的成员的长度中较小者为其值。

  __attribute((aligned (n))) //设置n字节对齐
  attribute ((packed))   //设置1字节对齐
  (gcc特性)

  让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
在这里插入图片描述
  微软的  __alignof( type )
  如:
    typedef __declspec(align(32)) struct { int a; } S;
    _alignof(S) 等于 32

总结:
  ①基本类型:自身对齐
  ②数组:
  ③联合:成员最大对齐值,公倍数;
  ④结构体:其成员中自身对齐值最大的那个值。
  ⑤指定对齐方式:
    #pragma pack (n)和pragma pack ();
    attribute((aligned(n)))和__attribute__((packed))。
  ⑥数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值};注意_attribute__。

4.字节序和网络序

4.1 字节序

(1) what–
  概念:字节的高低位存放顺序。
  单字节与多字节:
    单字节:按bit存储,通常处理方式相同;
    多字节:多个字节存储有顺序之分。
在这里插入图片描述
(2) how–
  大字节序(Big-Endian,大端序):最高有效字节在最前面的方式,即数据的低字节保存在内存的高地址中,而数据的高字节保存在内存的低地址中。(左图)
  小字节序(Little-Endian,小端序):最低有效字节在最前面的方式,数据的低字节保存在内存的低地址中,而数据的高字节保存在内存的高地址中。(右图)
在这里插入图片描述
(3) why–
  编写网络程序时,主机之间通过网络相互通信,不同主机之间可能采用不同的方法,而且网络字节序和主机字节序也可能不同。
在这里插入图片描述在这里插入图片描述

4.2 网络序

  网络传输一般采用大字节序,也称为网络字节序或网络序。IP协议中定义大字节序为网络字节序。

  对于可移植的代码来说,将接收的网络数据转换成主机的字节序是必须的,一般会有成对的函数用于把网络数据转换成相应的主机字节序或反之。

转换函数:
  Htonl、htons用于主机序转换到网络序;
  ntohl、ntohs用于网络序转换到本机序。
  
注意:
  对于单字符或小于单字符的几个bit数据,是不必转换的,因为在机器存储和网络发送的一个字符内的bit位存储顺序是一致的。

5.字节对齐的隐患

  空间换时间。

6.字节对齐应用

  本地
  网络传输

代码验证:
  环境:
  操作系统:x86_64-redhat-linux;编译器:gcc
  操作系统:win7-64位;编译器:vc

  • 79
    点赞
  • 196
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
C语言中,结构体的字节对齐是为了优化内存访问速度和对齐要求而进行的一种对齐方式。结构体的字节对齐确保结构体中的成员按照一定的规则进行排列,以便于处理器高效地访问内存。 结构体的字节对齐规则通常由编译器根据特定的对齐选项和目标平台的要求来确定。在C语言中,可以使用`#pragma pack`指令或者编译器提供的特定选项来控制结构体的字节对齐方式。 默认情况下,大多数编译器会按照特定的对齐规则进行字节对齐。这些规则通常是根据基本数据类型的大小来确定的。例如,常见的对齐规则是按照4字节对齐(即结构体成员的偏移量必须是4的倍数)或者8字节对齐。 以下是一个示例,展示了如何使用`#pragma pack`指令来设置结构体的字节对齐方式: ```c #pragma pack(push, 1) // 以1字节对齐 struct MyStruct { char c; int i; double d; }; #pragma pack(pop) // 恢复默认的对齐方式 int main() { printf("sizeof(MyStruct) = %zu\n", sizeof(struct MyStruct)); return 0; } ``` 在上面的示例中,`#pragma pack(push, 1)`指令将当前的对齐方式推入一个栈中,并将对齐方式设置为1字节对齐。然后定义了一个包含不同类型成员的结构体。最后,使用`#pragma pack(pop)`指令将对齐方式恢复为默认值。 请注意,修改结构体的字节对齐方式可能会导致内存浪费或者访问错误,因此在修改字节对齐方式时要特别小心。建议仅在必要时进行修改,并确保了解目标平台的字节对齐要求。 希望这能回答你的问题!如果还有疑问,请随时提问。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值