测试环境:Win 7 64bits,VMware Workstation 12 Pro,Ubuntu 15.10 64bits,使用gcc version 5.2.1 20151010
字节对齐
# include <stdio.h>
#pragma pack(1) //14,11,11
//#pragma pack(2) //16,12,12
//#pragma pack(4) //16,12,12
//#pragma pack(8) //16,16,16
struct Test1
{
char s1;
short s2;
char s3;
short s4;
double s5;
};
struct Test2
{
char s1;
short s2;
double s3;
};
struct Test3
{
double s3;
char s1;
short s2;
};
int main(int argc, char **argv)
{
printf("%lu \n",sizeof(struct Test1));
printf("%lu \n",sizeof(struct Test2));
printf("%lu \n",sizeof(struct Test3));
return 0;
}
- 1字节对齐的时候,不留空字节,实际上就是无字节对齐
- 字节对齐的公式,p = Min(m,n),m是编译器默认的对齐字节数目,可用#pragma pack来修改;n是struct中最大的类型的字节数目(数组的话按照数组元素类型计算字节数目),计算完成后,p为对齐字节数
- 对于奇数字节对齐的情况,测试了#pragma pack(3,5,7,9,11),结果都是14,11,11,结果和1字节一致,但是编译器会有warning,不允许奇数字节对齐,实际中也不这么用
- 如果struct中还是包含struct,则第一层的struct中除了内部struct的其他类型执行上述公式,判断后,内层和外层所有的struct执行该字节对齐
- 应用上述规则后,可以计算出下面的栗子:
union tagAAAA
{
struct
{
char ucFirst;
short usSecond;
char ucThird;
}half;
int lI;
}number;
struct tagBBBB
{
char ucFirst;
short usSecond;
char ucThird;
short usForth;
}half;
struct tagCCCC
{
struct
{
char ucFirst;
short usSecond;
char ucThird;
}half;
int lI;
};
// 在字节对齐为1下,sizeof(union tagAAAA)、sizeof(struct tagBBBB)、sizeof(struct tagCCCC)是4,6,8
// 在字节对齐为2下,sizeof(union tagAAAA)、sizeof(struct tagBBBB)、sizeof(struct tagCCCC)是6,8,10
//在字节对齐为4下,sizeof(union tagAAAA)、sizeof(struct tagBBBB)、sizeof(struct tagCCCC)是8,8,12
- 在定义数据结构时(尤其是涉及协议和IPC通讯的时候),没有特殊理由的话,都定义成四字节对齐;这样做可能浪费几个字节,但是不会出问题;
- 除了数据结构总长度是四字节对齐外,每一个部分也要保证它是四字节对齐的(可以按照四字节、两字节、一字节的顺序排放数据结构中的各个域);
- 对于无法定义成四字节对齐的数据结构,如以太网II、HDLC等,则将它们强行定义成一字节对齐,以规避这个问题;
- 对于MIPS CPU,在必须面对非四字节对齐情况时,采用编译选项方式加以解决;
结构体位域
//下述结构共占3字节
struct _Record_Struct
{
unsigned char Env_Alarm_ID :4;
unsigned char Para1 :2;
unsigned char state;
unsigned char avail:1;
} * Env_Alarm_Record ;
//下述结构共占4字节
struct _Record_Struct1
{
unsigned char Env_Alarm_ID :4;
unsigned int Para1 :2;
unsigned char state;
unsigned char avail:1;
};
//下述结构共占2字节
struct _Record_Struct2
{
unsigned char Env_Alarm_ID :4;
unsigned char Para1 :2;
unsigned char avail:1;
unsigned char state;
};
//下述结构共占4字节
struct _Record_Struct3
{
unsigned char Env_Alarm_ID :4;
unsigned char Para1 :5;
unsigned char avail:7;
unsigned char state;
};
- 结构体位域,只能使用char,int,long等可以提升为整数的类型,附加unsigned
- 不同的类型位域不会排在同一个字节中
- 同一个类型的位域,一旦相加超过8bits,也会另开1个新的字节填充
- 位域在赋值运算的时候,遵循截断原则
//以下程序在小端序的情况下输出的结果是 01 26 00 00
//超过位数按照截断处理,注意是16进制输出
#pragma pack(4)/*四字节对齐*/
int main(int argc, char* argv[])
{
unsigned char puc[4];
struct tagPIM
{
unsigned char ucPim1;
unsigned char ucData0:1;
unsigned char ucData1:2;
unsigned char ucData2:3;
}*pstPimData;
pstPimData = (struct tagPIM *)puc;
memset(puc, 0, 4);
pstPimData->ucPim1 = 1;
pstPimData->ucData0 = 2;
pstPimData->ucData1 = 3;
pstPimData->ucData2 = 4;
printf("%02X %02X %02X %02X\n", puc[0], puc[1], puc[2], puc[3]);
return 0;
}
#pragma pack()/*恢复缺省对齐方式*/
指针运算
struct{
long lNum; //4
char *pcName; //4
short sDate; //2
char cHa[2]; //2
short sBa[6]; //12
}*p;
//1字节对齐情况下
p = 0x100000;
p + 0x1 = 0x100018 //18 = 24(10)
(unsigned long)p + 0x1 = 0x100001
(unsigned long *)p + 0x1 = 0x100004
(char *)p + 0x1 = 0x100001
- 指针++,指针+1,指针+0x1,含义都是指针向后移动,移动的字节数,等于指针的类型的字节数
字节序问题
- 由于历史的原因,业界存在两种字节序标准:BigEndian和LittleEndian,Power PC是大头,X86是小头,有些CPU可以通过寄存器设置支持不同的字节序,例如MIPS;
- 字节序,就是字节的顺序,指的是大于一个字节的数据在内存中的存储方式,字节序和CPU型号有关。Big endian:Intel X86 CPU, Little endian:IBM Power PC
- 所谓大头就是高位在低字节,低位在高字节;小头则与此相反,以0x345678为例,大头内存从低到高的存放次序为00,34,56,78,小头内存从低到高的存放次序为78,56,34,00;(上面的数值统一为16进制表示形式)
- 机器大端模式(字数据的高字节存储在内存的低地址中,低字节存放在高地址中)和小端模式则相反
- 内存总是从低到高排序,指针也总是从内存的低处向高处移动,p++,大端的机器数据的高位在内存的低位,数据的低位在内存的高位,比如aabbccdd,aa在内存的低位,dd在内存的高位,指向它的指针,最开始是aa;小端机器则相反
ULONG ATM_UNI_GetParaULONGValByOID(UCHAR *Type );
{
*Type=0x12;
}
ULONG ATM_UNI_MAPIPTable_DeleteHandler (VOID* pMsgRcv, VOID** ppMsgSnd)
{
ULONG ulType=0x456789ab;
ulErrCode = ATM_UNI_GetParaULONGValByOID(&ulType );
printf(”%x”,ulType);
}
//在小端字节序下打印输出的值是多少? 45678912
//在大端字节序下打印输出的值是多少? 126789ab
- 字节序问题广泛存在于设备与设备之间、单板与单板之间、单板与底层芯片之间,只要两个处理单元的字节序不同,这个问题就存在,为了解决不同字节序的处理单元之间的通信问题,业界定义了主机序和网络序的概念,网络序主要用于信息传递,一般不用于计算,其字节顺序与大头一致;
- 在编码时要时刻注意大小端问题,在器件选择时也要尽量选择主机序与网络序一致的芯片,同一设备的不同单板使用相同的字节序,并优先选择支持大头的芯片,这样,即使不能彻底解决问题,也可以彻底规避问题。
1byte内8bits的顺序
- 上节所指的是字节的排序
- 对于每个字节内部的位的顺序,因为不能对位取地址,所以无法判断绝对的内存地址,下面做了一些实验:
# include <stdio.h>
# include <string.h>
union bits8_tag
{
char whol;//8 bits
struct
{
char c0:2;
char c1:2;
char c2:2;
char c3:2;
}byte;//1个8 bits
}value;
union bytes8_tag
{
unsigned int whol;// 32bits
struct
{
char c0;
char c1;
char c2;
char c3;
}byte1;//4个8 bits
}value1;
int main(int argc, char **argv)
{
printf("%lu,%lu \n",sizeof(union bits8_tag),sizeof(union bytes8_tag));
memset(&value,0,sizeof(union bits8_tag));
printf("%d,%x,%x,%x,%x \n",value.whol,value.byte.c0,value.byte.c1,value.byte.c2,value.byte.c3);
value.whol = 1;
printf("%d,%x,%x,%x,%x \n",value.whol,value.byte.c0,value.byte.c1,value.byte.c2,value.byte.c3);
memset(&value1,0,sizeof(union bytes8_tag));
printf("%d,%d,%d,%d,%d \n",value1.whol,value1.byte1.c0,value1.byte1.c1,value1.byte1.c2,value1.byte1.c3);
value1.whol = 1;
printf("%d,%d,%d,%d,%d \n",value1.whol,value1.byte1.c0,value1.byte1.c1,value1.byte1.c2,value1.byte1.c3);
//
memset(&value,0,sizeof(union bits8_tag));
printf("%d,%x,%x,%x,%x \n",value.whol,value.byte.c0,value.byte.c1,value.byte.c2,value.byte.c3);
value.byte.c3 = 1;
printf("%d,%x,%x,%x,%x \n",value.whol,value.byte.c0,value.byte.c1,value.byte.c2,value.byte.c3);
memset(&value1,0,sizeof(union bytes8_tag));
printf("%d,%d,%d,%d,%d \n",value1.whol,value1.byte1.c0,value1.byte1.c1,value1.byte1.c2,value1.byte1.c3);
value1.byte1.c3 = 1;
printf("%d,%d,%d,%d,%d \n",value1.whol,value1.byte1.c0,value1.byte1.c1,value1.byte1.c2,value1.byte1.c3);
return 0;
}
//output
1,4
0,0,0,0,0
1,1,0,0,0
0,0,0,0,0
1,1,0,0,0
0,0,0,0,0
64,0,0,0,1 //2^6
0,0,0,0,0
16777216,0,0,0,1 //2^24
每个字节内的bit位排序,无论大小端机器,都是从低地址到高地址填充,指针指向的都是低地址,所以C中结构体的位域,会出现定义挨着,但是在内存中不挨着的情况,比如:
struct trap
{
unsigned short a:4;
unsigned short b:4;
unsigned short c:4;
//实际上 unsigned short :4;
};
//则转换成小端序为:
struct trap
{
unsigned short :4;
unsigned short c:4;
unsigned short b:4;
unsigned short a:4;
};