Struct pointer compatibility

2019-01-19 13:45发布

Suppose we have two structs:

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

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

Is it safe to cast from Struct2 * to Struct1 * ? What does the ANSI spec says about this? I know that some compilers have the option to reorder structs fields to optimize memory usage, which might render the two structs incompatible. Is there any way to be sure this code will be valid, regardless of the compiler flag?

Thank you!

6条回答
贼婆χ
2楼-- · 2019-01-19 14:27

No, the standard does't allow this; accessing the elements of a Struct2 object through a Struct1 pointer is undefined behavior. Struct1 and Struct2 are not compatible types (as defined in 6.2.7) and may be padded differently, and accessing them via the wrong pointer also violates aliasing rules.

The only way something like this is guaranteed to work is when Struct1 is included in Struct2 as its initial member (6.7.2.1.15 in the standard), as in unwind's answer.

查看更多
别忘想泡老子
3楼-- · 2019-01-19 14:28

Yes, it is ok to do that!

A sample program is as follows.

#include <stdio.h>

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;
}
查看更多
甜甜的少女心
4楼-- · 2019-01-19 14:36

struct pointers types always have the same representation in C.

(C99, 6.2.5p27) "All pointers to structure types shall have the same representation and alignment requirements as each other."

And members in structure types are always in order in C.

(C99, 6.7.2.1p5) "a structure is a type consisting of a sequence of members, whose storage is allocated in an ordered sequence"

查看更多
干净又极端
5楼-- · 2019-01-19 14:43

The language specification contains the following guarantee

6.5.2.3 Structure and union members
6 One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.

This only applies to type-punning through unions. However, this essentially guarantees that the initial portions of these struct types will have identical memory layout, including padding.

The above does not necessarily allow one to do the same by casting unrelated pointer types. Doing so might constitute a violation of aliasing rules

6.5 Expressions
7 An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
— a character type.

The only question here is whether accessing

((Struct1 *) struct2_ptr)->a_short

constitutes access to the whole Struct2 object (in which case it is a violation of 6.5/7 and it is undefined), or merely access to a short object (in which case it might be perfectly defined).

It general, it might be a good idea to stick to the following rule: type-punning is allowed through unions but not through pointers. Don't do it through pointers, even if you are dealing with two struct types with a common initial subsequence of members.

查看更多
倾城 Initia
6楼-- · 2019-01-19 14:47

It is safe, as far as I know.

But it's far better, if possible, to do:

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

Then you've even told the compiler that Struct2 starts with an instance of Struct1, and since a pointer to a struct always points at its first member, you're safe to treat a Struct2 * as a Struct1 *.

查看更多
在下西门庆
7楼-- · 2019-01-19 14:47

It will most probably work. But you are very correct in asking how you can be sure this code will be valid. So: somewhere in your program (at startup maybe) embed a bunch of ASSERT statements which make sure that offsetof( Struct1.a_short ) is equal to offsetof( Struct2.a_short ) etc. Besides, some programmer other than you might one day modify one of these structures but not the other, so better safe than sorry.

查看更多
登录 后发表回答