结构指针兼容性

假设我们有两个结构:

typedef struct Struct1 { short a_short; int id; } Struct1; typedef struct Struct2 { short a_short; int id; short another_short; } Struct2; 

Struct2 *Struct1 *是否安全? ANSI规范对此有何看法? 我知道有些编译器可以选择重新排序结构字段以优化内存使用,这可能会使两个结构不兼容。 有没有办法确定这段代码是否有效,无论编译器标志如何?

谢谢!

struct指针类型在C中始终具有相同的表示forms。

(C99,6.2.5p27)“所有指向结构类型的指针应具有相同的表示和对齐要求。”

并且结构类型中的成员总是在C中按顺序排列。

(C99,6.7.2.1p5)“结构是由一系列成员组成的类型,其存储按有序顺序分配”

据我所知,这是安全的。

但如果可能的话,做得更好:

 typedef struct { Struct1 struct1; short another_short; } Struct2; 

然后你甚至告诉编译器Struct2Struct2的实例开始,并且因为指向结构的指针总是指向它的第一个成员,所以将Struct2 *视为Struct1 *是安全的。

它很可能会奏效。 但是在询问如何确保此代码有效时,您是非常正确的。 所以:你的程序中的某个地方(在启动时可能)嵌入了一堆ASSERT语句,以确保offsetof( Struct1.a_short )等于offsetof( Struct2.a_short )等。此外,除了你以外的一些程序员可能有一天会修改一个这些结构而不是其他结构,所以比抱歉更安全。

语言规范包含以下保证

6.5.2.3结构和工会成员
6为了简化联合的使用,我们做了一个特别的保证:如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查它们中任何一个的共同初始部分,可以看到完整类型的联合声明。 如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列。

这仅适用于通过工会打字。 但是,这基本上保证了这些结构类型的初始部分将具有相同的内存布局,包括填充。

以上并不一定允许通过转换不相关的指针类型来做同样的事情。 这样做可能会违反别名规则

6.5表达式
7对象的存储值只能由具有以下类型之一的左值表达式访问:
– 与对象的有效类型兼容的类型,
– 与对象的有效类型兼容的类型的限定版本,
– 对应于对象的有效类型的有符号或无符号类型,
– 对应于对象有效类型的限定版本的有符号或无符号类型,
– 聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),或者
– 一个字符类型。

这里唯一的问题是访问

 ((Struct1 *) struct2_ptr)->a_short 

构成对整个Struct2对象的访问(在这种情况下,它违反了6.5 / 7并且未定义),或仅仅访问一个short对象(在这种情况下它可能是完美定义的)。

一般来说,坚持以下规则可能是一个好主意:通过工会允许类型惩罚,但不允许通过指针。 不要通过指针来做,即使你正在处理两个具有成员的共同初始子序列的struct类型。

不,标准不允许这样做; 通过Struct1指针访问Struct2对象的元素是未定义的行为。 Struct1和Struct2不是兼容类型(如6.2.7中所定义),可能以不同的方式填充,并且通过错误的指针访问它们也会违反别名规则。

保证这样做的唯一方法是当Struct1作为其初始成员(标准中的6.7.2.1.15)包含在Struct2中时,就像在unwind的答案中一样 。

是的,可以这样做!

示例程序如下。

 #include  typedef struct Struct1 { short a_short; int id; } Struct1; typedef struct Struct2 { short a_short; int id; short another_short; } Struct2; int main(void) { Struct2 s2 = {1, 2, 3}; Struct1 *ptr = &s2; void *vp = &s2; Struct1 *s1ptr = (Struct1 *)vp; printf("%d, %d \n", ptr->a_short, ptr->id); printf("%d, %d \n", s1ptr->a_short, s1ptr->id); return 0; }