这个问题以前对个别人提醒过, 现在发现falcon项目里还是有这样的使用, 在此贴个贴提醒一下大家.
现像:
通过一块buffer传递一个结构体, 消费者通过将这个char型的指针直接转换成了结构体的指针使用.
结果将可能是不正确的.
原因:
在很多系统中, 结构体的指针是要4字节对齐的. 当这个char型的指针不是4字节对齐时, 强制转换出来的结构体指针用起来是有问题的.
下面的测试代码是在fh8735平台上测试的.
static void testpoint2struct()
{
typedef struct teststruct_ {
int a;
}teststruct_t;
char buffer[32];
char *point = NULL;
teststruct_t *pointstruct = NULL;
memset(buffer, 0x99, sizeof(buffer));
bufferdump(buffer, sizeof(buffer), NULL); // 打印buffer的内容
// 指针对齐的情况下,结果是正确的
point = buffer;
while((unsigned int)point % 4) point ++;
pointstruct = (teststruct_t *)point;
pointstruct->a = 0x12345678;
iPrintf("point = %p, a = %x/n", pointstruct, pointstruct->a);
bufferdump(buffer, sizeof(buffer), NULL); // 打印buffer的内容
// 指针不对齐的情况下,结果是不正确的
point = buffer;
while((unsigned int)point % 4 == 0) point ++;
pointstruct = (teststruct_t *)point;
pointstruct->a = 0x12345678;
iPrintf("point = %p, a = %x/n", pointstruct, pointstruct->a);
bufferdump(buffer, sizeof(buffer), NULL); // 打印buffer的内容
}
输出:
buffer start from 0x40bffe44, 32 bytes:
0x0000: 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
0x0010: 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
000000.030: point = 0x40bffe44, a = 12345678
buffer start from 0x40bffe44, 32 bytes:
0x0000: 78 56 34 12 99 99 99 99 99 99 99 99 99 99 99 99
0x0010: 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
000000.060: point = 0x40bffe45, a = 34567812
buffer start from 0x40bffe44, 32 bytes:
0x0000: 12 78 56 34 99 99 99 99 99 99 99 99 99 99 99 99
0x0010: 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
在此提醒大家:
不要轻易将一个char型的指针强制转换成为结构体的指针.
另外, 网络通信的注意点
在网络通信中, 尽量不要直接进行结构体-->buffer或buffer-->结构体的转换或考贝.
在网络通信中, 尽量使用网络字节序传输内容.
原因有二:
1, 网络通信双方的主机, 结构体定义时, 可能对齐方式不统一, 导致双方的结构体大小不一致.
2, 双方主机的主机字节序可能不一样, 也许一方是大字节序,而另一方是小字节序.
所以,即使是short型这么简单的数据,也应该用htons和ntohs来转换, 或者手动转换数据.
关于非对齐访问,这里补充一下。
这个问题主要是和CPU的特性相关。
仅基于对该问题的讨论,CPU大致可以分为三类。
先解释下什么是对齐访问(读或写):
访问一个16位的内存变量(short),要求地址必须是2的倍数。或者说地址总线A0必须为0。
访问一个32位的内存变量(int、long),要求地址必须是4的倍数。或者说地址总线A0、A1必须为0。
1)不带MMU,并且不支持非对齐访问的。诸如fh8735里面的CPU core:ARC600;ST5105的CPU core:ST20。
在这种情况下,一个不对齐的访问,不会导致系统异常,但是读到的数据可能不是你想要的,写的数据也可能不是你想写的。
因为不会导致系统异常,所以错误常常不容易发现。在这种环境下,对我们的编程人员提出了更高的要求。
2)带MMU,并且不支持非对齐访问的。诸如CNOVA9700的CPU core:SPARC v8;ST7109的CPU core:SH4。
在这种情况下,一个不对齐的访问,会触发一个Excepiton即访问违例,异常被OS捕获后,会导致系统halt或者当前任务suspend。
因而这种错误很容易被发现,对编程人员的要求也较低。只要多做测试就能发现bug进而fix掉。
3)带MMU,并且支持非对齐访问的。诸如Pentium、AMD的CPU core:i386。
在这种情况下,一条不对齐的指令,CPU会拆分成多个(次)总线访问:
比如语句:
*(int *)0xc0000003 = 0x12345678;
在实际执行的时候等效于如下4条语句:
*(char *)0xc0000003 = 0x78;
*(char *)0xc0000004 = 0x56;
*(char *)0xc0000005 = 0x34;
*(char *)0xc0000006 = 0x12;
这个操作全部由CPU硬件自动完成,对程序(员)来说是完全透明(我们说透明的意思是:从软件的角度看不到这个转化的存在)的。
我们知道,在32位总线情况下,对齐的访问4个字节和访问一个字节时间上是一样的。即不对齐访问是4倍的时间开销。
虽然这样写的程序效率要比对齐访问低的多,但是在该平台下不会报错。故而对编程人员的没有过多要求。
我们大多数程序员最开始接触的都是这种高级(昂贵)的CPU,非对齐访问的程序因为总能正常工作,结果纵容我们写出不好的程序、故而很容易养成坏的习惯。
当转到嵌入式开发的时候,这些坏习惯就会让我们付出代价。
canary项目的开发人员在移植eMule、BitTorrent到C2平台上的时候就碰到了这样的例子。
这些程序在PC上跑的很正确,但是重新编译后放到C2平台上就crash了。原因也在于此:C2的CPU core:MIPS32正属于上面的第2类型。
同时也说明,这些软件在跨平台、可移植性上是不够好的
FFMPGE的解码需要传入的也必须是对齐的音视频数据
否则有一定机率会产生异常