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
所以嵌入式是真的难啊!
我们这几天的调试还遇到了一些坑,过几天总结一下再来说说~