结构体的字节对齐(跨语言传参时尤为重要)
在项目开发时,结构体作为一个数据结构,非常适合用语存储某一设备或某一类事务的信息,自然的,将其用作参数也是必然的事。然而在将结构体作为参数生成DLL后,用其他语言调用时,则会有一些问题。比如在用C#调用C++的DLL时,结构体的大小就会有不一样,导到调用出问题。
基于以前的BCB开发的产品,需要增加一些接口以实现新的功能,于是将相应的信息封装到了结构体中,然后传出。但是当我用C#调用时,发现怎么调都不对,后来用了C#调用C++DLL传递结构体数组的终极解决方案所说的,用最蛮力的办法,开了一个大的byte数组去接收(数组最好能基于DLL的结构体估算一下大小),数据接收成功了,也分析得到了想要的数据。之后再细加分析,发现原来BCB导出DLL时调用的结构体大小和我们用C#的Marsh弄出来的大小有差异,C#的小了2个字节,于是我就强行补了2个字节进去,最终算是达到了目的。
不过想想就这样,还是觉得有问题。因为这个接口最终可能会发布,供第三方调用。要是他们用其他的语言呢?第三方可不一定会知道是这个原因的,所以想着有没有办法可以解决。经过测试分析,发现是结构体的字节对齐问题。以下结构体是基于win7 32下BCB编写与测试的。
struct Info { int OrderNO; float CpuPercent; char UniqueCode[33]; }; void __fastcall TForm1::btn1Click(TObject *Sender) { int len=sizeof(Info); ShowMessage(len); }
测试Info的长度是44,可是我们自己一个一个变量计算时,发现并不是。OrderNO是int为4字节,CpuPercent是float为4字节,UniqueCode为char数组共33个字节,合起来是41个字节。那多出来的3个字节是哪多出来的呢?
经过查找资料,提到说是字节对齐的问题。所谓字节对齐,就是每个变量在内存中的以多少个字节作为数据块存储。现在测出来是44个字节,那只能是1、2、4、11的倍数,11的倍数显然不可能,那会不会是4的倍数呢?
若以4字节对齐,那么OrderNO是4个字节,正好可以存储。CpuPercent也是4个字节,也正好可以存储。而UniqueCode是33字节,以4字节为块进行存储,那么需要9个块。而9个块则有4*9=36个字节,比char数组分配的33个字节多了3个字节。这么说来,这多出来的3个字节就是这里多出来的。那又要如何去证实想法呢?
有了!我们不妨把UniqueCode变成36的char数组,看是不是也是44个字节。结构体修改如下。
struct Info { int OrderNO; char UniqueCode[36]; float CpuPercent; };
经测试,新的Info结构体确实是44个字节。那么依此类推,如果UniqueCode开辟的数组为33、34、35、36时,其Info结构的字节大小都是44字节。经过测试,确实是如此。所以最终证明,这多出来的3个字节,其实是因为数据块是以4字节来分配的。
那为什么又要以4字节来分配呢?难道是因为结构体中的变量第一个是整型,所以用第一个作为字节对齐的标准吗?这么想了之后,还是得证明。那不妨把UniqueCode和OrderNO的顺序对换一下,新的结构体如下。
struct Info { char UniqueCode[33]; int OrderNO; float CpuPercent; };
经测试,Info结构体还是44个字节。奇怪了,难道不是以第一个类型作为参照吗?那会是以什么?难道是以数组中最多字节的数据类型作为参照?那有什么数据类型是比int的字节要多的?有了,double类型,果断改之。
struct Info { char UniqueCode[33]; double OrderNO; float CpuPercent; };
经测试,Info结构体大小为56个字节,正好是double所占8字节的倍数。我们分析分析,UniqueCode是33字节,按8字节对齐,需要5个块,即5*8=40字节。OrderNO是8个字节。CpuPercent虽然是float为4个字节,但要按8字节对齐,所以还是占了8字节。合起来40+8+8=56字节,这么说BCB编译器是默认以结构体中所占字节最多的为字节对齐的。
这时可能有人要问了,那UniqueCode是char数组,不是33个字节,要比double的8个字节要大吗?为什么不以UniqueCode作为字节对齐的参照?是的,我也曾有这种疑惑。不过再想想,问题就不难了。int、float、double类型是一个整体,如果要把数据给完整的保留,那么就得分配连续的空间来存储。而char数组虽说为数组,但其本质上是以char的1字节存储的,所以在结构体中占字节最多时,char数组只是以1字节计算的。若是还觉得怀疑,可 将CpuPercent改成数组,比如。
struct Info { char UniqueCode[33]; double OrderNO; float CpuPercent[4]; };
测出的长度为64,UniqueCode点了40字节和OrderNO占了8字节,CpuPercent占了16字节。因为CpuPercent每个都是float为4字节,一个8字节的数组块可以存上两个float。所以会发现,BCB是以结构体中的基本数据类型所占字节最多的作为字节对齐参照的。
那有没有什么方式可以让空间就如我们所写的那样占字节呢。比如
struct Info { int OrderNO; float CpuPercent; char UniqueCode[33]; };
我希望Info所占的字节数是OrderNO为4个,CpuPercent为4个,UniqueCode为33个,合起来是4+4+33=41个。答案是有的。只要我给结构体强制指定一个字节对齐的字节数即可,代码如下。
#pragma pack(push,1) struct Info { int OrderNO; float CpuPercent; char UniqueCode[33]; }; #pragma pack(pop)
该结构体测试后,大小为41字节,正是我们所要的。其中#pragma pack(push,1)中的1是表示对齐字节的方式,但必须是系统所能存储的,比如3就会报错。#pragma pack(pop)是表示结束了。
当结构体的字节如我们所定义各个变量所计算的字节之和时,那么对于跨语言调用,就可以消除不同语言不同编译器调用结构体参数时大小不同的障碍,很多问题也就可以随之解决了。
转载请注明出处http://blog.csdn.net/xxdddail/article/details/11808253