不同类型和大小的指针的联合
Union of different types and sizes of pointers
typedef struct { char ch; int num; } st_t;
typedef union { char *pch; st_t *pst; } un_t;
st_t st;
st.ch = 's';
un_t un = { &st.ch };
*un.pch = 'u';
printf("%c\n", un.pst->ch); // expect: print the letter 'u'
据我所知,结构体第一个成员的地址和结构体本身的地址是一样的,所以un
可以同时指向st
和st.ch
同时访问 pst
和 pch
。但是,C99 标准似乎从未明确表示 the sizes of different types of pointers are identical。然后,我担心代码会不会被破坏,例如违反严格的别名规则,成为未定义的行为等?
虽然这样的构造并不严格符合指针大小的标准,但实际上最常见的实现(即 msvc,gcc/linux)确实对非函数指针使用相同的表示形式。
因此,在这些实现中,它应该可以如您所愿地工作。
行:
un_t un = {&st.ch};
*un.pch = 'u';
将结构的成员 ch 的地址分配给联合的成员 pch,然后使用该指针向该地址写入一个字符。这是完全正确的。
后面一行有问题:
printf("%c\n", un.pst->ch);
除了上次存储的成员之外的联合成员被读取。
下面可能是陷阱1表示2:
un.pst
1(引自:ISO/IEC 9899:201x 6.2.6.1 总则 5)
某些对象表示不需要表示对象类型的值。如果存储
对象的值具有这样的表示,并由执行以下操作的左值表达式读取
没有字符类型,行为未定义。如果产生这样的表示
通过一个通过左值表达式修改对象的全部或任何部分的副作用
没有字符类型,行为未定义。 50)这样的表示被称为
陷阱表示。
2(引自:ISO/IEC 9899:201x 6.5.2.3 结构和联合成员 3 脚注 95)
如果用于读取联合对象内容的成员与上次用于读取的成员不同
在对象中存储一个值,该值的对象表示的适当部分被重新解释
作为 6.2.6 中描述的新类型的对象表示(有时称为“类型”的过程
双关语”)。这可能是陷阱表示。
C 标准试图定义所有符合要求的实现所需的特性、特性和保证;它不会努力描述 99% 的实现所共有的大部分有用特性和特性,但这些特性不是必需的。
在绝大多数实现中,所有数据指针都使用相同的表示,因此给定 thingType *foo=xxx; void *vfoo = foo;
,两者都 foo
和 vfoo
将保持相同的位模式。在咄咄逼人的日子之前
优化器,这样的实现将非常有用地允许任何类型的数据
使用 void**
访问指针,因此可以编写如下函数:
int realloc_if_possible(void **p, int newsize)
{
void *temp = realloc(*p, newsize);
if (!temp) return -1;
*p = temp;
return 0;
}
但由于 C 标准不要求编译器在以下情况下识别别名
向函数传递除 void* 以外的任何类型指针的地址,
许多现代编译器不再支持这种结构,除非所有基于类型的
禁用别名优化。
typedef struct { char ch; int num; } st_t;
typedef union { char *pch; st_t *pst; } un_t;
st_t st;
st.ch = 's';
un_t un = { &st.ch };
*un.pch = 'u';
printf("%c\n", un.pst->ch); // expect: print the letter 'u'
据我所知,结构体第一个成员的地址和结构体本身的地址是一样的,所以un
可以同时指向st
和st.ch
同时访问 pst
和 pch
。但是,C99 标准似乎从未明确表示 the sizes of different types of pointers are identical。然后,我担心代码会不会被破坏,例如违反严格的别名规则,成为未定义的行为等?
虽然这样的构造并不严格符合指针大小的标准,但实际上最常见的实现(即 msvc,gcc/linux)确实对非函数指针使用相同的表示形式。
因此,在这些实现中,它应该可以如您所愿地工作。
行:
un_t un = {&st.ch};
*un.pch = 'u';
将结构的成员 ch 的地址分配给联合的成员 pch,然后使用该指针向该地址写入一个字符。这是完全正确的。
后面一行有问题:
printf("%c\n", un.pst->ch);
除了上次存储的成员之外的联合成员被读取。
下面可能是陷阱1表示2:
un.pst
1(引自:ISO/IEC 9899:201x 6.2.6.1 总则 5)
某些对象表示不需要表示对象类型的值。如果存储
对象的值具有这样的表示,并由执行以下操作的左值表达式读取
没有字符类型,行为未定义。如果产生这样的表示
通过一个通过左值表达式修改对象的全部或任何部分的副作用
没有字符类型,行为未定义。 50)这样的表示被称为
陷阱表示。
2(引自:ISO/IEC 9899:201x 6.5.2.3 结构和联合成员 3 脚注 95)
如果用于读取联合对象内容的成员与上次用于读取的成员不同
在对象中存储一个值,该值的对象表示的适当部分被重新解释
作为 6.2.6 中描述的新类型的对象表示(有时称为“类型”的过程
双关语”)。这可能是陷阱表示。
C 标准试图定义所有符合要求的实现所需的特性、特性和保证;它不会努力描述 99% 的实现所共有的大部分有用特性和特性,但这些特性不是必需的。
在绝大多数实现中,所有数据指针都使用相同的表示,因此给定 thingType *foo=xxx; void *vfoo = foo;
,两者都 foo
和 vfoo
将保持相同的位模式。在咄咄逼人的日子之前
优化器,这样的实现将非常有用地允许任何类型的数据
使用 void**
访问指针,因此可以编写如下函数:
int realloc_if_possible(void **p, int newsize)
{
void *temp = realloc(*p, newsize);
if (!temp) return -1;
*p = temp;
return 0;
}
但由于 C 标准不要求编译器在以下情况下识别别名 向函数传递除 void* 以外的任何类型指针的地址, 许多现代编译器不再支持这种结构,除非所有基于类型的 禁用别名优化。