C语言中的字节对齐以及其相关处理

首先,我们来了解下一些基本原理:

一、什么是字节对齐
一个基本类型的变量在内存中占用n个字节,则该变量的起始地址必须能够被n整除,即: 存放起始地址 % n = 0,那么,就成该变量是字节对齐的;对于结构体、联合体而言,这个n取其所有基本类型的成员中占用空间字节数最大的那个;
内存空间是以字节为基本单位进行划分的,从理论上讲,似乎对任何类型的变量的访问都可以从任何地址处开始,但实际情况是在访问特定类型变量的时候经常是从特定的内存地址处开始访问,这就需要各种类型的数据只能按照一定的规则在空间上排列,而不是顺序的一个接一个地排放;究其原因,是为了使不同架构的CPU可以提高访问内存的速度,就规定了对于特定类型的数据只能从特定的内存位置处开始访问;所以,各种类型的数据只能按照相应的规则在内存空间上排放,而不能顺序地、连续地、一个一个地排放;这就是内存对齐;

二、为什么需要字节对齐

由于各种硬件平台对存储空间的处理上有很大的不同;一些平台对某些特定类型的数据只能从某个特定内存地址处开始访问;比如:有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程时就必须保证字节对齐;其它平台可能没有这种情况,但最常见的是,如果不按照适合其平台要求对数据进行对齐,会在存取效率上带来损失;比如,有些平台每次读取数据都是从偶地址处开始,如果一个int(假设为32位系统)型数据从偶地址处开始存放,那么只需要一个读指令周期就可以完全读出这个32bit的int型数据,相反,如果这个32bit的int型数据是从奇地址处开始存放,那么就需要两个读指令周期才能完全读出这个32bit的int数据,并且还需要对这两次读出的结果的高低字节进行重新拼凑才能得到正确的32bit数据;这个时候,CPU的读取效率明显下降;

三、字节对齐规则
预处理指令#pragma pack(align_value)用于指定对齐值,而预处理指令#pragma pack()用于取消上次设定的对齐值,恢复默认对齐值;
字节对齐是针对基本类型变量的;基本类型变量有:char、unsigned char、short、unsigned short、int、unsigned int、long、unsigned long、long long、unsigned long long、float、double,等等;所以,对于结构体的对齐也只能按照其成员变量中的基本类型来对齐了;
有四个概念需要理解:
A、数据类型自身的对齐值:
   是指对该数据类型使用sizeof()操作符进行操作所得到的大小(单位,字节);比如,对于[unsigned] char类型的数据,其自身对齐值为1字节;对于[unsigned] short类型的数据,其自身对齐值是2字节;对于[unsigned] int、[unsigned] long、[unsigned] long long、float、double等数据类型,其自身对齐值是4字节;
B、结构体、联合体、类的自身对齐值:
   是指其所有基本类型的成员中,自身对齐值最大的那个值;如果这些复合类型中有嵌套类型或复合类型的变量,则需要把这些嵌套的类型或复合类型的变量拆解成基本类型的成员之后再对齐;
C、指定对齐值:
   是指使用预处理指令#pragma pack(align_value)指定的对齐值align_value;
D、数据成员、结构体和类的有效对齐值:
   是指其自身对齐值和指定对齐值中较小的那个值;
其中,有效对齐值是最终用来决定数据存放地址方式的值,最重要;设定有效对齐值为N,就表示"对齐在N字节上",也就是说,该数据的"存放起始地址%N=0";

因此,每个类型的数据的有效对齐值就是其自身对齐值(通常是这个类型的大小)和指定对齐值(不指定则取默认值)中较小的那个值,并且结构体自身对齐值是其所有成员中自身对齐值最大的那个值;

字节对齐的细节与编译器的实现有关,但一般来说,结构体需要满足以下几个准则:
1).从结构体外部来看,结构体变量的首地址能够被其最宽基本成员的大小整除;从结构体内部来看,它的第一个数据成员的地址相对于整个结构体首地址的偏移量为0,也就是说,结构体的第一个数据成员存放在偏移量为0的地方;
2).结构体中的每个数据成员的有效对齐值都取其自身对齐值和指定对齐值中的较小的那个对齐值;或者说是,结构体中的每个数据成员相对于结构体首地址的偏移量都是该数据成员大小和指定对齐值中较小的那个值(或有效对齐值)的整数倍,如有需要,编译器会在数据成员之间加上填充字节;
3).如果结构体中还有嵌套的结构体或结构体变量,那么就要把这些嵌套进去的结构体或结构体变量拆成基本类型成员,并取其最长的基本类型成员的对齐方式;
4).结构体整体的有效对齐值必须为其最宽基本类型成员大小的整数倍;或者说是,结构体整体的大小为结构体中最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节;换句话说是,结构体整体的有效对齐值按照结构体中最宽基本类型成员的大小和指定对齐值中较小的那个值进行;

特别注意:如果指定对齐值大于自身对齐值,则指定对齐值无效;

然后,我们来看一下C编译器对字节对齐的相关处理

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。

在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储(成员之间可能有插入的空字节),第一个成员的地址和整个结构的地址相同。

C编译器缺省的结构成员自然对界条件为“N字节对齐”,N即该成员数据类型的长度。如int型成员的自然对界条件为4字节对齐,而double类型的结构成员的自然对界条件为8字节对齐。若该成员的起始偏移不位于该成员的“默认自然对界条件”上,则在前一个节面后面添加适当个数的空字节。

C编译器缺省的结构整体的自然对界条件为:该结构所有成员中要求的最大自然对界条件。若结构体各成员长度之和不为“结构整体自然对界条件的整数倍,则在最后一个成员后填充空字节。

例子1(分析结构各成员的默认字节对界条界条件和结构整体的默认字节对界条件):

struct Test
{
char x1; // 成员x1为char型(其起始地址必须1字节对界),其偏移地址为0 

char x2; // 成员x2为char型(其起始地址必须1字节对界,其偏移地址为1 

float x3; // 成员x3为float型(其起始地址必须4字节对界),编译器在x2和x3之间填充了两个空字节,其偏移地址为4 

char x4; // 成员x4为char型(其起始地址必须1字节对界),其偏移地址为8
};

因为Test结构体中,最大的成员为flaot x3,因些此结构体的自然对界条件为4字节对齐。则结构体长度就为12字节,内存布局为1100 1111 1000。

例子2:

#include <stdio.h>
//#pragma pack(2)
typedef struct
{
  int aa1; //4个字节对齐 1111
  char bb1;//1个字节对齐 1
  short cc1;//2个字节对齐 011
  char dd1; //1个字节对齐 1
  } testlength1;
int length1 = sizeof(testlength1); //4个字节对齐,占用字节1111 1011 1000,length = 12

typedef struct
{
  char bb2;//1个字节对齐 1
  int aa2; //4个字节对齐 01111
  short cc2;//2个字节对齐 11
  char dd2; //1个字节对齐 1
  } testlength2;
int length2 = sizeof(testlength2); //4个字节对齐,占用字节1000  1111 1110,length = 12

typedef struct
{
  char bb3; //1个字节对齐 1
  char dd3; //1个字节对齐 1
  int aa3; //4个字节对齐 001111
  short cc23//2个字节对齐 11

  } testlength3;
int length3 = sizeof(testlength3); //4个字节对齐,占用字节1100 1111 1100,length = 12

typedef struct
{
  char bb4; //1个字节对齐 1
  char dd4; //1个字节对齐 1
  short cc4;//2个字节对齐 11
  int aa4; //4个字节对齐 1111
  } testlength4;
int length4 = sizeof(testlength4); //4个字节对齐,占用字节1111 1111,length = 8

int main(void)
{
  printf("length1 = %d.\n",length1);
  printf("length2 = %d.\n",length2);
  printf("length3 = %d.\n",length3);
  printf("length4 = %d.\n",length4);
  return 0;
}

改变缺省的对界条件(指定对界)
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

这时,对齐规则为:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

 

因此,当使用伪指令#pragma pack (2)时,Test结构体的大小为8,内存布局为11 11 11 10。

需要注意一点,当结构体中包含一个子结构体时,子结构中的成员按照#pragma pack指定的数值和子结构最大数据成员长度中,比较小的那个进行进行对齐。

例3:

#pragma pack(8)
struct s1{
short a;
long b;
};

struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()

sizeof(s2)的结果为24。S1的内存布局为1100 1111,S2的内存布局为1000 1100 1111 0000 1111 1111。

例4:

#include <stdio.h>
#pragma pack(2)
typedef struct
{
  int aa1; //2个字节对齐 1111
  char bb1;//1个字节对齐 1
  short cc1;//2个字节对齐 011
  char dd1; //1个字节对齐 1
  } testlength1;
int length1 = sizeof(testlength1); //2个字节对齐,占用字节11 11 10 11 10,length = 10

typedef struct
{
  char bb2;//1个字节对齐 1
  int aa2; //2个字节对齐 01111
  short cc2;//2个字节对齐 11
  char dd2; //1个字节对齐 1
  } testlength2;
int length2 = sizeof(testlength2); //2个字节对齐,占用字节10 11 11 11 10,length = 10

typedef struct
{
  char bb3; //1个字节对齐 1
  char dd3; //1个字节对齐 1
  int aa3; //2个字节对齐 11 11
  short cc23//2个字节对齐 11

  } testlength3;
int length3 = sizeof(testlength3); //2个字节对齐,占用字节11 11 11 11,length = 8

typedef struct
{
  char bb4; //1个字节对齐 1
  char dd4; //1个字节对齐 1
  short cc4;//2个字节对齐 11
  int aa4; //2个字节对齐 11 11
  } testlength4;
int length4 = sizeof(testlength4); //2个字节对齐,占用字节11 11 11 11,length = 8

int main(void)
{
  printf("length1 = %d.\n",length1);
  printf("length2 = %d.\n",length2);
  printf("length3 = %d.\n",length3);
  printf("length4 = %d.\n",length4);
  return 0;
}

另外,还有如下的一种方式:

· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。

时间: 2024-10-08 01:31:42

C语言中的字节对齐以及其相关处理的相关文章

C语言:内存字节对齐详解[转载]

一.什么是对齐,以及为什么要对齐: 1. 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐. 2. 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同.一些平台对某些特定类型的数据只能从某些特定地址开始存取.其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐

C++中的字节对齐

本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一.小鱼)相关研究.学习内容所做的笔记,欢迎广大朋友指正! 字节对齐 1. 基本概念字节对齐:计算机存储系统中以Byte为单位存储数据,不同数据类型所占的空间不同,如:整型(int)数据占4个字节,字符型(char)数据占一个字节,短整型(short)数据占两个字节,等等.计算机为了高速的读写数据,默认情况下将数据存放在某个地址的起始位置,如:整型数据(int)默认存储在地址能被4整除的起始位置,字

c/c++中的字节对齐

参加了很多面试,遇到字节对齐的问题不是1次2次,但一直没有彻底弄明白是什么意思,清明节刚好闲下来,彻底研究了一下,得到下面的结论,希望对以后的面试和工作有作用: 第一种结论: 首先提出几个概念 ①基本类型:像int,char,float,double之类的基本类型 ②复合类型:结构体,类,联合体之类的类型,由基本类型构成 ③数据类型的宽度: 用sizeof (type)计算出来的宽度,一般int为4 Bytes,char为1 Byte... ④有效对齐模数N: 编译器默认对齐模数为M,M可以用#

C++中的字节对齐分析

struct A { int a; char b; short c; }; struct B { char a; int b; short c; }; #pragma pack(2) struct C { char a; int b; short c; }; #pragma pack(1) struct D { int a; char b; short c; }; int _tmain(int argc, _TCHAR* argv[]) { cout << sizeof(A) <<

bmp图像存储中的字节对齐问题

1. bmp数据对齐问题. 假设所读取的bmp图片位数是24,图像高度和宽度分别为998像素和726像素,每个像素占3个字节,即每行像素占3*726个字节,不是4的整数倍,首先需要对每行字节进行补零操作.假设文件头和信息头分别为bfh和bih,则每行所补的字节数为: offset_bytes = 4 - (bih.biWidth * bih.biBitCount/8)%4 补齐后每行所占的字节数为: row_length = 4*((bih.biWidth * bih.biBitCount +

ACE的CDR中的字节对齐问题

大家应该都知道计算机中间都有字节对齐问题.CPU访问内存的时候,如果从特定的地址开始访问一般可以加快速度,比如在32位机器上,如果一个32位的整数被放在能被32模除等于0的地址上,只需要访问一次,而如果不在,可能要访问两次.但是这样就要求一些数据从特定的地址开始,而不是顺序排放(中间会有一些空余的地址),这就是字节对齐. 而ACE CDR的估计也是为了加快速度,从而在CDR编码上默认也使用了字节对齐.所以在ACE的CDR编解码过程中,传入的参数地址最好是能符合字节对齐规则,否则可能会编解码错误.

C语言中的内存对齐

由下面的测试代码可以总结出两点:(1)内存对齐是针对结构体而言的    (2)编译时char类型的申请的空间由其后面的数据类型决定. #include<stdio.h> enum Season { Spring,Summer,Autumn }; enum ENUM Enum = Autumn; union UNION { int a; char b; double c; }; union UNION Union; struct STRUCT { char b; //4字节 int a; //4

C语言 大小端 字节对齐

参考:http://www.cnblogs.com/graphics/archive/2011/04/22/2010662.html 1. 大端序:数据的高位字节存放在地址的低端,低位字节存放在地址的高端 2. 小端序:数据的高位字节存放在地址的高端,低位字节存放在地址的低端 记忆方式: 大端序是按照数字的书写顺序进行存储的,小端序是颠倒书写顺序进行存储的. 结构体对齐问题:http://www.cnblogs.com/clover-toeic/p/3853132.html

c语言中的字节数关系、

转载自:传送门 16位编译器 char :1个字节 char*(即指针变量): 2个字节 short int : 2个字节 int:  2个字节 unsigned int : 2个字节 float:  4个字节 double:   8个字节 long:   4个字节 long long:  8个字节 unsigned long:  4个字节 32位编译器 char :1个字节 char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节.同理64位编译器)