类型穿孔或将底层位从一种类型重新解释为另一种类型是臭名昭著的,因为具有不可预测和/或不可移植的行为.

例如:

union {
    unsigned u;
    float f;
} c = {.u = 10};
float f = c.f;

不可移植,这将取决于float的代表性.


union {
    unsigned char c[2];
    unsigned short s;
} c = {.c = {1, 2}};
short s = c.s;

不可移植,这将取决于CHAR_BIT的值和系统的字节顺序/字节顺序.


但是,如果定义了所有<stdint.h>种类型,以下任何一种是否具有标准保证/可移植的行为:

union {
    uint8_t b[2];
    uint16_t w;
} c = {.b = {0x18, 0x18}};
assert(c.w == 0x1818);

或者相反:

union {
    uint8_t b[2];
    uint16_t w;
} c = {.w = 0x1818};
assert(c.b[0] == 0x18 && c.b[1] == 0x18);

或者如果我扩展类型的大小:

union {
    uint16_t w[2];
    uint32_t l;
} c = {.w = {0x1818, 0x1818}};
assert(c.l == 0x18181818);

在上面的例子中,字节顺序并不重要,因为这个数字是"循环的",并且在大/小字节顺序中具有相同的表示,或者在任何其他深奥的字节顺序中.保证类型正好是它们指定的位宽,并且没有trap 表示或填充位.

由于这些原因,没有logical理由使type—pun具有不可移植的行为或返回assert()中指定的值以外的任何值,但是C标准是否作出了同样的保证explicitly?第truly章是便携式的吗?

C标准指出读取一个非活动的union成员将"重新解释"比特为新类型,但这是否转化为具有可移植行为的上述示例?或者,是否有某种奇怪的方法,某些技术上符合C99的实现可以编译,但不能产生预期的结果?

推荐答案

类型双关指的是将一种类型的representation重新解释为另一种类型.如果类型被保证具有相同或充分定义的表示,那么类型双关可能是可移植的.

这一点在6.2.5 Footnote 39(强调我)中得到了明确的证实:

相同的表示和对齐要求意味着可替换性,如函数的参数、函数的返回值和members of unions.

整数

对于有符号整数类型[...]作为值位的每一位应具有与对应的无符号类型的object representation位中的相同位相同的值(如果有符号类型中有M个值位且无符号类型中有N个值位,则M ≤ N).如果符号位为零,则不应影响结果值.

这意味着任何小于或等于相应signed类型的最大正值的类型的正unsigned值在类型穿孔时将具有相同的值,反之亦然,因为表示中的所有相应位必须对最终值具有相同的效果.

这是明确保证的:

符号位为零的有符号整数类型的有效(非trap )对象表示是对应无符号类型的有效对象表示,并且应表示相同的值.

此外:

对于任何整数类型,所有位为零的对象表示应是该类型中值零的表示.

所有位为0的任何值的值都为0.因此,任何全0整数类型的任何部分都可以被类型穿孔为任何较小的整数,或者多个所有位为0的较小整数(包括填充位,如果有的话)可以被类型穿孔为较大的整数,并且值为will still be 0.

This partially addresses the examples in the question, as we know the fixed-width types have no padding bits, so those examples shall work with values of 0.

固定宽度整数(如果定义)

对于相同的N,键入intN_tuintN_t将等同于在值if上加上2^(N-1),而intN_t的值是负数.反之,等于从值if减go 2^(N-1),则uintN_t的值大于最大值intN_t.

typedef名称intN_t指定宽度为N、无填充位和2的补数表示的带符号整数类型.因此,int8_t表示宽度正好为8位的有符号整数类型.

这个要求保证了没有填充位,并且由于它们具有相同的总位数,intN_t must be one中的value位数比uintN_t中的value位数少.

将有恰好一个符号位.

并且由于intN_t中的所有15个值位必须具有与uintN_t表示中的相应位相同的值,并且对于所有固定宽度类型都需要2的补数,通过消除the sign bit in 100 must correspond to the value bit with value 103 in the 101.的过程,因此,它们之间的类型双关必须具有如上所述的可移植行为.

指针

倒数6.2.5:

指向void的指针应具有与指向字符类型的指针相同的表示和对齐要求.同样,指向兼容类型的合格或不合格版本的指针应具有相同的表示和对齐要求.所有指向 struct 类型的指针应具有彼此相同的表示和对齐要求.所有指向联合类型的指针应具有彼此相同的表示和对齐要求.指向其他类型的指针不必具有相同的表示或对齐要求.

这意味着人们可以安全地在void *char *之间,或在任何两个struct指针之间,或在任何两个union指针之间,或在任何两个指针之间进行类型双关,以兼容(例如,相同类型的有符号和无符号版本)类型.虽然可以将任何对象指针类型转换为void *char *,但这样做需要显式强制转换,而不是类型双关语.

struct

struct 和其他 struct 或类型之间的字体穿孔通常是不可移植的,因为在 struct 构件之间插入了未指定数量的填充物.但也有一些例外:

倒数6.5.2.3:

类型双关在一个 struct 和该 struct 的第一个成员之间,或者在一个联合和该联合中的任何成员之间是可移植的,前提是用最后存储的联合成员对成员进行类型双关的行为具有可移植的行为.

指向联合对象的指针,经过适当的转换,指向它的每个成员(或者如果成员是位字段,则指向它所在的单元),反之亦然.

指向 struct 对象的指针,经过适当转换,指向其初始成员(或者如果该成员是位字段,则指向其所在的单元),反之亦然.

此外:

一个特殊的保证是为了简化联合的使用:如果一个联合包含多个共享公共初始序列的 struct (见下文),并且如果联合对象当前包含这些 struct 中的一个,则允许判断其中任何一个 struct 的公共初始部分,只要有一个联合的完整类型的声明可见.如果对应的成员对于一个或多个初始成员的序列具有兼容的类型(对于位字段,宽度相同),则两个 struct 共享一个公共的初始序列.

这意味着,如果你有几个独立的 struct 类型,但它们的所有第一个成员都是兼容的类型,并且顺序相同,那么它们的匹配成员可以被类型punned/accessed,只要在访问的范围内,union被完全声明并且两者都可见.C Standard个例子:

下面是一个有效的片段:

union {
   struct {
       int alltypes;
   } n;
   struct {
       int type;
       int intnode;
   } ni;
   struct {
       int type;
       double doublenode;
   } nf;
} u;
u.nf.type = 1;
u.nf.doublenode = 3.14;
/* ... */
if (u.n.alltypes == 1)
   if (sin(u.nf.doublenode) == 0.0)
       /* ... */

下面是一个无效的片段(因为联合类型在函数f中不可见):

struct t1 { int m; };
struct t2 { int m; };
int f(struct t1 *p1, struct t2 *p2)
{
   if (p1->m < 0)
   p2->m = -p2->m;
   return p1->m;
}
int g()
{
   union {
       struct t1 s1;
       struct t2 s2;
   } u;
   /* ... */
   return f(&u.s1, &u.s2);
}

较小固定宽度类型的数组与较大固定宽度类型的数组之间的联合

除了位字段,对象由contiguous个由一个或多个字节组成的序列组成,其数量、顺序和编码要么是明确指定的,要么是实现定义的.

这意味着,如果没有填充位(固定宽度类型就是这种情况),两个连续类型之间的类型双关到两倍大的类型将保证具有连接其对象表示的位的效果.Contiguous意味着内存中的原始字节之间不可能有"垃圾".

对于无符号整数类型[...]这种类型的物体应能够 使用纯二进制表示来表示从0到2^N − 1的值;

但是,pure binary notation是否能保证对象表示中的值位按大小顺序排列呢?

纯二进制表示法定义为:

整数的positional representation,使用二进制数字0和1,其中由successive bits表示的值是相加的,从1开始,并乘以连续的2的整数幂,可能除了最高位置的位.(改编自美国国家信息处理系统词典)一个字节包含CHAR_BIT位,而unsigned char类型的值范围从0到2CHAR_BIT − 1.

这明确地提到了representation, successive bits, and position.这意味着纯二进制符号中的位是从最低到最高排序的.如果这个were not the case,那么在表示中的位的确切位置将是毫无意义的,并且定义将不会提及位置或位是连续的,每个值位存在并且对应于0和N之间的每个2的幂.然而,这个定义指定位是连续的和有序的.

为什么要求有符号整数位与无符号值中的相应位具有相同的值,如果这将是多余的?最有可能的是,为了确保填充和/或符号位的放置不会相对于相应的符号类型"偏移"值位.

考虑到上述情况,将固定数量的有序位的相同副本连接到新的固定数量的有序位中,每次必须产生相同的值.可以提出一种情况,即任何不演示在这种情况下预期行为的实现都将违反pure binary notation.的定义,

C++相关问答推荐

有什么方法可以检测SunOS上的SparcWorks吗?

try 使用sigqueue函数将指向 struct 体的指针数据传递到信号处理程序,使用siginfo_t struct 体从一个进程传递到另一个进程

ATmega328P USART发送字符重复打印

ATTiny1606定时器TCA 0中断未触发

在函数中使用复合文字来初始化C语言中的变量

以下声明和定义之间的区别

对于C中给定数组中的每个查询,如何正确编码以输出给定索引范围(1到N)中所有数字的总和?

有什么方法可以将字符串与我们 Select 的子字符串分开吗?喜欢:SIN(LOG(10))

在进程之间重定向输出和输入流的问题

具有正确标头的C struct 定义问题

有没有办法减少C语言中线程的堆大小?

浮点正零何时不全为零?

如果类型是新的,offsetof是否与typeof一起工作?

在git补丁中自动添加C的宏

哪些C++功能可以在外部C块中使用

c如何传递对 struct 数组的引用,而不是设置 struct 的副本

我错误地修复了一个错误,想了解原因

(GNU+Linux) 多个线程同时调用malloc()

UEFI 应用程序中的计时器回调仅在 AMI BIOS 中挂起

如何确定 C 程序中的可用堆内存