vlambda博客
学习文章列表

C语言结构体对齐的陷阱

在通信协议的开发过程中,有时会利用结构体构造报文某些字段,这就有可能会涉及到结构体对齐的问题。如果稍有不慎,会因结构体使用不当导致功能受到严重影响。


以下面一段代码为例:

#include <stdio.h>


#define PKT_TIMESTAMP_LEN 8


typedef struct pktIdentifyTlv_s

{

    unsigned char type;              /*type = 0xfe*/

    unsigned char len[2];            /*len = 0x09*/

    unsigned char reserved;          /*四字节对齐*/

    unsigned char operID[4];         

    

    unsigned char identify[2];       /*报文标识*/

    unsigned char highTransID[2];    /*四字节对齐*/

} pktIdentifyTlv_t;


typedef struct pktTimeStampData_s

{

    unsigned char timeStamp1[PKT_TIMESTAMP_LEN];     /*T1*/

    unsigned char timeStamp2[PKT_TIMESTAMP_LEN];     /*T2*/

    unsigned char timeStamp3[PKT_TIMESTAMP_LEN];     /*T3*/

    unsigned char timeStamp4[PKT_TIMESTAMP_LEN];     /*T4*/

}pktTimeStampData_t;


typedef struct cfmCommonHeader_s

{

    unsigned char mdLevel_ver;       /*MD等级-前3位(0-7),MD版本-0*/

    unsigned char opcode;            /*详见Y.1731协议*/

    unsigned char flags;             /*标记(0)*/

    unsigned char firstTLVoffset;    /*第一个TLV的偏移(4)*/

} cfmCommonHeader_t;


typedef struct dmmPdu_s

{

    cfmCommonHeader_t stCfmComHeader; /*CFM COMMON头*/

    pktTimeStampData_t stTimeStampData;  /*时间戳信息*/

    pktIdentifyTlv_t stIdentifyTlv;      

    unsigned char endTlv;

}dmmPdu_t;


typedef struct counterData_s

{

    long TxFCf;  /*远端发送帧计算*/

    long RxFCf;  /*远端接收帧计算*/

    long TxFCb;  /*LMR帧传输时本地计数器TxFCl的数值*/

}counterData_t;


typedef struct lmmPdu_s

{

    cfmCommonHeader_t stCfmComHeader; /*CFM COMMON头*/

    counterData_t stCounterData;      /*计数器信息*/

    pktIdentifyTlv_t stIdentifyTlv;      

    unsigned char endTlv;

}lmmPdu_t;


int main()

{

int lmm = sizeof(lmmPdu_t);

int dmm = sizeof(dmmPdu_t);

int com_head = sizeof(cfmCommonHeader_t);

int lmm_cnt = sizeof(counterData_t);

int dmm_cnt = sizeof(pktTimeStampData_t);

int id_tlv = sizeof(pktIdentifyTlv_t);

printf("\r\nlmm=%d, dmm=%d\r\n", lmm, dmm);

printf("\r\ncom_head=%d, id_tlv=%d\r\n", com_head, id_tlv);

printf("\r\nlmm_cnt=%d, dmm_cnt=%d\r\n", lmm_cnt, dmm_cnt);


return 0;

}


上述例程在windows XP上C Free 5.0中的运行结果为:


从结果中可以看到,结构体lmmPdu_t按照成员的最大宽度long型进行对齐(按照4字节对齐,编译器对结构体进行了padding),而dmmPdu_t由于成员都是char型(按照1字节对齐,编译器没有对结构体进行padding)。


如果想对lmm报文的stIdentifyTlv字段进行改写,应该从lmmPdu的头部向后偏移24。通过(lmm - 5)计算偏移量,对stIdentifyTlv字段的填充是错误的。


而对于改写dmm报文的stIdentifyTlv字段,可以从dmmPdu的头部向后偏移44,亦可以通过(dmm - 5)计算偏移量,二者的效果是相同的。