字节对齐&位域&字节序

测试环境: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;
};
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
结构体字节对齐引起的访问越界问题通常出现在以下情况下: 1. 结构体中存在字节对齐的成员变量,访问该成员变量时,可能会访问到结构体之外的内存空间。例如,如果某个成员变量的长度是4字节,但该结构体的字节对齐方式是8字节,那么访问该成员变量时,可能会访问到结构体之外的4字节内存空间,从而导致访问越界。 2. 结构体中存在数组类型的成员变量,访问该数组时,可能会访问到数组之外的内存空间。例如,如果一个数组的长度是10,但该结构体的字节对齐方式是16字节,那么访问该数组时,可能会访问到数组之外的6字节内存空间,从而导致访问越界。 为了避免结构体字节对齐引起的访问越界问题,可以采取以下措施: 1. 使用编译器提供的或自定义的对齐方式,以确保结构体中成员变量的对齐方式符合要求。 2. 避免在结构体中使用数组类型的成员变量,或者使用动态内存分配等方法来管理数组,以确保数组访问不会越界。 3. 避免在结构体中使用位域类型的成员变量,或者使用位运算等方法来确保位域访问不会越界。 4. 在编写代码时,注意检查结构体中的成员变量访问是否越界,可以使用断言等方法来检查访问边界。 总之,结构体字节对齐引起的访问越界问题需要引起重视,需要在编写代码时注意结构体成员变量的对齐方式和访问边界,以确保程序的正确性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值