字节对齐

Context

去年我们接手了一个G3PLC的项目,这也算是首次正正规规的做一次嵌入式了,也是我第一次在自己的电脑上安装并使用keil,有点小兴奋。

一开始我们心里还是蛮害怕的,因为协议很多,我们嵌入式基础很弱,一直担心最后这个项目无法完成。不过,到了今年6月,我们竟然真的写完了!

俗话说得好,写程序花20%的时间,调试花80%的时间~ 当然这是他们嵌入式行业的俗话~

于是我们开始了紧张的调试,一开始还蛮顺利,遇到了问题基本上可以非常快的定位到问题点,然后也可以非常快的解决。

直到…

StackTrace

先来看两个结构体

adp.h:

typedef struct _ROUTINGTABLE_
{
    uSHORT    DestinationAddress; //16
    uSHORT    NextHopAddress; //16
    uSHORT    RouteCost; //16
    unsigned    HopCount:4;
    unsigned    WeakLinkCount:4;
    uSHORT    ValidTime;//16
}_ROUTINGTABLE,*_pROUTINGTABLE;

mmu.h:

typedef struct _ROUTINGTABLEITEM_
{
    uSHORT    destination; //2
    uSHORT nextHop; //2
    uSHORT cost; //2
    unsigned hopCount:4; //0.5
    unsigned weakLinkCount:4; //0.5
    uSHORT    ValidTime; //2
}_ROUTINGTABLEITEM, *_pROUTINGTABLEITEM;

这两个结构体除了名字不一样,别的都是一样的,每个变量的位置,变量的类型,所以我们觉得他们就是一样的

然后就出现了奇怪的现象~

我们设置了ValidTime之后,数据变了,变得我们不认识了!

我们上一行把ValidTime设置成300(0x012c)之后下一行用16进制打印强制转换另一个类型的ValidTime,却变成了0x2c00

我们上一行把ValidTime设置成30(0x001e)之后下一行用16进制打印强制转换另一个类型的ValidTime,却变成了0x1e00

是编译器欺负人?还是单片机欺负人?

按理说两个类型是完全一样的,那么我传入的指针,被强制转换之后,每个变量所对应的地址也是一样的,那内容应该也是一样的呀

Solve

折腾了很长时间,最后我就强行把类型统一了,使用同一个struct。竟然就好了!

于是我们发现在adp.h定义这一串struct之前有 #pragma pack(1)

他的功能是字节对齐,1就代表着1字节对齐。

在Keil官网上,对pack这个预编译函数有介绍
编译器默认是8字节对齐,所以只要不屑pack(1),那就是8字节对齐的。官网这里的demo我觉得有点问题,所以我就用另一个例子来说明。

以下内容夹杂猜想

adp.h:

#pragma pack(1)
......

typedef struct _ROUTINGTABLE_
{
    uSHORT    DestinationAddress; //16
    uSHORT    NextHopAddress; //16
    uSHORT    RouteCost; //16
    unsigned    HopCount:4;
    unsigned    WeakLinkCount:4;
    uSHORT    ValidTime;//16
}_ROUTINGTABLE,*_pROUTINGTABLE;

这里按照1个字节对齐,在内存里就是这样的

|-----------0-----------|
 -----------------------
|     Destination_H     |
|     Destination_L     |
|    NextHopAddress_H   |
|    NextHopAddress_L   |
|      RouteCost_H      |
|      RouteCost_L      |
|HopCount,WeakLinkCount |
|      ValidTime_H      |
|      ValidTime_L      |

sizeof(_ROUTINGTABLE)==9,这个应该是理想情况,但是如果没有pack(1)

mmu.h:

typedef struct _ROUTINGTABLEITEM_
{
    uSHORT    destination; //2
    uSHORT nextHop; //2
    uSHORT cost; //2
    unsigned hopCount:4; //0.5
    unsigned weakLinkCount:4; //0.5
    uSHORT    ValidTime; //2
}_ROUTINGTABLEITEM, *_pROUTINGTABLEITEM;

这里我觉得应该是按照2个字节对齐(虽然官网说的是8),在内存里就是这样的

|     0    |    1    |
 --------------------
|   dstH   |  dstL   |
|   NextH  |  NextL  |
|   cstH   |  cstL   |
|hopC\weakC|    *    |
|  ValTimH | ValTimL |

sizeof(_ROUTINGTABLEITEM)==10,于是字节错位了!

我们使用_ROUTINGTABLEITEM这个结构体对validTime赋值,会操作结构体内存的8、9位字节

使用错误案例validTime=300(0x012c),short在内存中存放为0x2c,0x01(倒着的~)

于是结构体为dstH,dstL,NxtH,NxtL,cstH,cstL,hop,weak,0x00,0x2c,0x01

但是使用_ROUTINGTABLE这个结构体去读取validTime的时候,会读取结构体内存的7、8位字节

也就是dst[7]=00,dst[8]=0x2c,于是validTime变成了0x2c00,超大的数字!

One more thing

这里bit field对齐夹杂了我的部分猜测,因为我查了很多很多很多资料,也没有找到比特操作的明确说明~

所以我就参考了这个牛逼的网页

假设bit是自动按序排列的,只要几个bit field大小总和小于等于一个字节,就使用一个字节空间,
而如果超过一个字节,那就需要把超的哪个bit field赶到下一个字节中去。

End

所以嵌入式是真的难啊!

我们这几天的调试还遇到了一些坑,过几天总结一下再来说说~