转换struct指针

thi*_*his 3 c struct pointers c11

假设使用c11编译代码并启用严格别名.

我不是在寻找一种不同的方法,我想关注这个具体的问题,如果它有效或者为什么不能.

(如果我无意中犯了一些无关的错误,请告诉我,我会修复它)

c11标准说:

6.2.5.28所有指向结构类型的指针应具有相同的表示和对齐要求.

6.7.2.1.6结构是由一系列成员组成的类型,其存储按有序顺序分配

这意味着结构A和B中指针大小和指针对齐方式相同.

#include <stdio.h>
#include <stdlib.h>

struct S1
{
    int i ;
} ;

struct S2
{
    float f ;
} ;

struct A
{
    struct S1* p ;
} ;


struct B
{
    struct S2* p ;
} ;


int main( void )
{
Run Code Online (Sandbox Code Playgroud)

结构A和B具有指向结构S1和S2的指针,结构A和B保证具有相同的大小和对齐.

我们有一个struct B成员指针是struct S2指针,但指向一些struct S1,它使用void*cast实现.

struct S1 s1 = { 0 } ;

struct B* b = malloc( sizeof( *b ) ) ;
b->p = ( void* ) &s1 ;
Run Code Online (Sandbox Code Playgroud)

没关系,我们可以存储指针,只要我们实际上不使用指针.但是我们想要.我们可以将指针强制转换为struct S1.

( ( struct S1* )(b->p) )->i = 123 ;    //redundant brackets for emphasis

printf("%d\n" , s1.i ) ;
Run Code Online (Sandbox Code Playgroud)

并正确使用它.

到目前为止,我没有看到任何问题,因为指针被转换为正确的类型.

但是我们可以将整个结构B转换为结构A吗?它们在大小和对齐方面是相同的,虽然标准可能会抱怨(?),编译器是否会产生未定义的行为?

( ( struct A* )b)->p->i = 666 ;

printf("%d\n" , s1.i ) ;
Run Code Online (Sandbox Code Playgroud)

我知道解决方案是使用union(或使用void并随时正确地转换),因为标准允许使用最后用于存储值的成员.

6.5.2.3.3(95)如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将重新解释为6.2.6中描述的新类型中的对象表示(有时称为"类型双关"的过程).这可能是陷阱表示.

但是,我想避免这种情况:

struct C
{
    union
    {
        struct S1* p1 ;
        struct S2* p2 ;
    } ;
} ;

struct C* c = malloc( sizeof( *c ) ) ;

c->p2 = ( void* )&s1 ;

c->p1->i = 444 ;

printf("%d\n" , s1.i ) ;

return 0 ;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码没有文字.

小智 6

你在此之前所描述的内容:

但是我们可以将整个结构B转换为结构A吗?

这一切都是正确的,但不幸的是,这个问题的答案是否定的.如果两个结构包含"公共初始序列",即它们的前几个成员具有相同类型,则只允许通过指向不兼容类型的指针访问结构.由于你的结构没有(即,第一个成员是不同类型的),S1通过指针访问类型的对象是不合法的,S2反之亦然.特别是,这样做违反了严格的别名规则.

从C99,6.5.7:

对象的存储值只能由具有以下类型之一的左值表达式访问:76)

- 与对象的有效类型兼容的类型,

- 与对象的有效类型兼容的类型的限定版本,

- 对应于对象的有效类型的有符号或无符号类型,

- 对应于对象有效类型的限定版本的有符号或无符号类型,

- 聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),或者

- 角色类型.