保存和读回一个复杂struct内容

结构体字节对齐与内存保存读取
本文讨论了在编译器中实现交叉编译时,如何保存和读回struct内容,涉及struct中field的偏移量计算、字节对齐的规则和原因,以及32bit和64bit平台间的兼容性问题。

概述

在编译器中实现交叉编译时,需要把离线编译器输出的中间代码(IR)写入文件中,再由应用程序读取文件,读回中间代码。
在这个save和load的程序实现中,涉及到很多数据在内存中存储的基础知识和技巧。本文将尝试总结一二。

得到一个field在struct中的偏移量

当我们要存一个struct的内容,而此struct的内容包含了不定长度的数据时,比如一个表示名字的字符串name,通常我们还会保存一个表示其长度的整数field,如nameLength。

然后用偏移量来计算即将写入文件的数据大小。
bytes += mOFFSETOF(_sBINARY_STRUCT, name) + mALIGN(nameLength, 2);

偏移量的宏定义如下。
/*******************************************************************************
**
** mOFFSETOF
**
** Compute the byte offset of a field inside a structure.
**
** ARGUMENTS:
**
** s: Structure name.
** field: Field name.
*/
#define mOFFSETOF(s, field)
(
mPTR2INT32(& (((struct s *) 0)->field))
)

(struct s ) 0:把0地址当成struct s类型的指针。
((struct s ) 0)->field:对应域的变量。
((struct s ) 0)->field:取该变量的地址,其实就等于该域相对于0地址的偏移量。
mPTR2INT32(& (((struct s ) 0)->field)):将该地址(偏移量)转化为INT型数据。
ANSI C标准允许任何值为0的常量被强制转换成任何一种类型的指针,并且转换结果是一个NULL指针,因此((s
)0)的结果就是一个类型为s
的NULL指针。如果利用这个NULL指针来访问s的成员当然是非法的,但&(((s
)0)->m)的意图并非想存取s字段内容,而仅仅是计算当结构体实例的首址为((s
)0)时m字段的地址。聪明的编译器根本就不生成访问m的代码,而仅仅是根据s的内存布局和结构体实例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存的问题。

offsetof虽然同样适用于union结构,但它不能用于计算位域(bitfield)成员在数据结构中的偏移量。
typedef struct
{
unsigned int a:3;
unsigned int b:13;
unsigned int c:16;
}foo;
使用offset(foo,a)计算a在foo中的偏移量,编译器会报错。

字节对齐

当使用sizeof得到一个struct的大小时,需要意识到字节对齐的问题。字节对齐也会导致32bit和64bit平台之间存取二进制文件时出现dismatch的问题。
我们首先了解下字节对齐的概念,规则和原因。

  1. 字节对齐(byte alignment):
    现代计算机中内存空间都是按照byte划分的,从理论上讲对于任何类型的变量的访问都可以从任何地址开始。但是实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的牌坊,这就是对齐。

  2. 对齐原因:
    (1)有些CPU访问没有对齐的变量时候会发生错误。比如Motorola 68000不允许将16位的字存储到奇数地址中, 将一个16位的字写到奇数地址将引发异常。
    (2)不对数据进行对齐,会在存取效率上带来损失。比如:有些CPU从偶数地址存储int数据,读取时需要一个机器周期,从偶数地址存储,就需要2个机器周期。

  3. 对齐规则:
    实际上, 对于c中的字节组织, 有这样的对齐规则:
    (1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
    (2) 结构体每个成员相对于结构首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
    (3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
    不同CPU的对其规则可能不同, 请参考手册。为什么会有上述的限制呢? 理解了内存组织, 就会清楚了。
    CPU通过地址总线来存取内存中的数据,32位的CPU的地址总线宽度既为32位置, 标为A[0:31]。在一个总线周期内,CPU从内存读/写32位。 但是CPU只能在能够被4整除的地址进行内存访问,这是因为: 32位CPU不使用地址总线的A1和A2(比如ARM,它的A[0:1]用于字节选择, 用于逻辑控制, 而不和存储器相连,存储器连接到A[2:31])。访问内存的最小单位是字节(byte), A0和A1不使用, 那么对于地址来说, 最低两位是无效的,所以它只能识别能被4整除的地址了。 在4字节中,通过A0和A1确定某一个字节。

  4. 字节对齐对程序的影响:
    (32bit、x86、gcc)(所占字节数char:1, short:2, int:4, long:4, float:4, double:8)
    struct A
    {
    int a;
    char b;
    short c;
    };
    struct B
    {
    char b;
    int a;
    short c;
    };
    sizeof(strcut A) == 8
    sizeof(struct B) == 12

32bit和64bit机的兼容问题

To be continued.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值