别名等效的有符号和无符号类型的别名

时间:2014-11-24 16:40:06

标签: c++ c language-lawyer

C和C ++标准都允许相同整数类型的有符号和无符号变体互相别名。例如,unsigned int*int*可能是别名。但这不是整个故事,因为它们显然具有不同的可表示值范围。我有以下假设:

  • 如果通过unsigned int读取int*,则该值必须在int范围内,否则会发生整数溢出,并且行为未定义。这是对的吗?
  • 如果通过int读取unsigned int*,则负值就会好像被转换为​​unsigned int一样。这是对的吗?
  • 如果该值在intunsigned int的范围内,则通过任一类型的指针访问它都是完全定义的,并给出相同的值。这是对的吗?

此外,兼容但不等同的整数类型呢?

  • intlong具有相同范围,对齐等的系统上,可以int*long*别名吗? (我假设没有。)
  • 可以char16_t*uint_least16_t*别名吗?我怀疑这在C和C ++之间有所不同。在C中,char16_tuint_least16_t的typedef(正确?)。在C ++中,char16_t是它自己的基本类型,与uint_least16_t兼容。与C不同,C ++似乎没有异常允许兼容但不同的类型别名。

4 个答案:

答案 0 :(得分:4)

  

如果通过unsigned int读取int*,则该值必须为   在int范围内或整数溢出发生和   行为未定义。这是对的吗?

为什么不定义?没有整数溢出,因为没有进行转换或计算。我们采用unsigned int对象的对象表示,并通过int查看它。以unsigned int对象的值转换为int的值的方式是完全实现定义的。

  

如果通过int读取unsigned int*,则负值换行   就好像它们被转换为unsigned int一样。这是对的吗?

取决于代表性。有两个补码和等效填充,是的。虽然没有签名幅度 - 从intunsigned的演员总是通过一致来定义:

  

如果目的地类型为unsigned,则结果值为。{1}   最小无符号整数与源整数一致(模数    2 n 其中n是用于表示无符号类型的位数)。 [注意:在二进制补码表示中,这个   转换是概念性的,位模式没有变化(如果   没有截断)。 - 结束说明]

现在考虑

10000000 00000001  // -1 in signed magnitude for 16-bit int

如果被解释为unsigned,那肯定是 2 15 +1 。演员阵容会产生 2 16 -1

  

如果该值在int和unsigned int的范围内,   通过任一类型的指针访问它是完全定义的   给出相同的值。这是对的吗?

再次,使用两个补码和等效填充,是的。我们可以使用-0

  

intlong具有相同范围的系统上,对齐,   等,可以int*long*别名吗? (我假设没有。)

没有。它们是独立的类型。

  

可以char16_t*uint_least16_t*别名吗?

技术上没有,但这似乎是对标准的一个不必要的限制。

  

类型char16_tchar32_t表示具有相同的不同类型   大小,签名和对齐为uint_least16_t和   uint_least32_t中的<cstdint>分别称为基础   类型。

因此,实际上应该没有任何风险(因为不应该有任何填充)。

答案 1 :(得分:3)

  

如果通过int读取unsigned int*,则负值会回滚,就像它们被转换为unsigned int一样。这是对的吗?

对于使用二进制补码的系统,类型双关和有符号无符号转换是等效的,例如:

int n = ...;
unsigned u1 = (unsigned)n;
unsigned u2 = *(unsigned *)&n;

此处,u1u2都具有相同的值。这是迄今为止最常见的设置(例如,Gcc为其所有目标记录此行为)。但是,C标准也适用于使用“C&C”的机器。补码或符号幅度来表示有符号整数。在这样的实现中(假设没有填充位并且没有陷阱表示),整数值和类型间隔的转换的结果可以产生不同的结果。例如,假设sign-magnitude和n被初始化为-1:

int n = -1;                     /* 10000000 00000001 assuming 16-bit integers*/
unsigned u1 = (unsigned)n;      /* 11111111 11111111
        effectively 2's complement, UINT_MAX */
unsigned u2 = *(unsigned *)&n;  /* 10000000 00000001
        only reinterpreted, the value is now INT_MAX + 2u */

转换为无符号类型意味着加上/减去比该类型的最大值多一个,直到该值在范围内。取消引用转换后的指针只需重新解释位模式。换句话说,u1初始化中的转换在2的补码机器上是无操作,但需要在其他机器上进行一些计算。

  

如果通过unsigned int读取int*,则该值必须在int范围内,否则会发生整数溢出,并且行为未定义。这是对的吗?

不完全是。 位模式必须代表新类型中的有效值,如果旧的是可表示的,则无关紧要。从C11(n1570)[省略脚注]:

  

6.2.6.2整数类型

     

对于unsigned char以外的无符号整数类型,对象表示的位应分为两组:值位和填充位(不需要后者中的任何一个)。如果有 N 值位,则每个位应表示 1 2 N-1之间 2 的不同幂 ,以便该类型的对象能够表示从 0 2 N -1 的值纯二进制表示;这应该被称为价值表示。任何填充位的值都未指定。

     

对于有符号整数类型,对象表示的位应分为三组:值位,填充位和符号位。不需要任何填充位; signed char不得有任何填充位。应该只有一个符号位。作为值位的每个位应与相应无符号类型的对象表示中的相同位具有相同的值(如果有符号类型中有 M 值位且 N 在无符号类型中,然后M≤N)。如果符号位为零,则不应影响结果值。如果符号位为1,则应以下列方式之一修改该值:

     
      
  • 符号位0的对应值被否定(符号和幅度);
  •   
  • 符号位的值为 -2 M (两个补码);
  •   
  • 符号位的值为 -2 M -1 (其中&#39;补码)。
  •   
     

这些适用中的哪一个是实现定义的,符号位1和所有值位的值是否为零(前两个),或符号位和所有值位1(对于1&#39;补码) ,是陷阱表示或正常值。在符号和幅度的情况下,一个&#39;补码,如果此表示是正常值,则称为负零

例如,unsigned int可能有值位,其中相应的带符号类型(int)具有填充位,类似unsigned u = ...; int n = *(int *)&u;可能导致此类系统上的陷阱表示(阅读其中未定义的行为),但不是相反。

  

如果该值在intunsigned int的范围内,则通过任一类型的指针访问它都是完全定义的,并给出相同的值。这是对的吗?

我认为,该标准允许其中一种类型具有填充位,该填充位始终被忽略(因此,两个不同的位模式可以表示相同的值,并且该位可以设置在初始化时)但是对于另一种类型,它是一个始终陷阱 - 如果设置的位。然而,这种余地至少受到同上的限制。 P5:

  

未指定任何填充位的值。符号位为零的有符号整数类型的有效(非陷阱)对象表示是相应无符号类型的有效对象表示,并且应表示相同的值。对于任何整数类型,所有位为零的对象表示应该是该类型中零值的表示。


  

intlong具有相同范围,对齐等的系统上,可以int*long*别名吗? (我假设没有。)

当然,他们可以,如果你不使用它们;)但不,这些平台上的以下内容无效:

int n = 42;
long l = *(long *)&n; // UB
  

可以char16_t*uint_least16_t*别名吗?我怀疑这在C和C ++之间有所不同。在C中,char16_tuint_least16_t的typedef(正确?)。在C ++中,char16_t是它自己的原始类型,它与uint_least16_t兼容。与C不同,C ++似乎没有异常允许兼容但不同的类型别名。

我不确定C ++,但至少对于C来说,char16_t是一个typedef,但不一定是uint_least16_t,它很可能是某些特定于实现的typedef __char16_t,某些类型与uint_least16_t(或任何其他类型)不兼容。

答案 2 :(得分:1)

没有定义发生这种情况,因为c标准没有准确定义如何存储有问题的整数。所以你不能依赖内部表示。也没有溢出发生。如果您只是对指针进行类型转换,则不会发生其他任何其他情况,然后在以下计算中对二进制数据进

编辑
哦,我误读了短语&#34;但不是等同的整数类型&#34;,但我保留段落供你感兴趣:

你的第二个问题比较麻烦。许多机器只能读取正确对齐的地址,数据必须位于类型宽度的倍数上。如果从非4可分区地址读取int32(因为你铸造了一个2字节的int指针),你的CPU可能会崩溃。

您不应该依赖类型的大小。如果您选择其他编译器或平台,则longint可能不再匹配。

<强>结论:
不要这样做。您编写了高度依赖于平台(编译器,目标机器,体系结构)的代码,该代码隐藏了可以抑制任何警告的强制转换后的错误。

答案 3 :(得分:0)

关于unsigned int*int*的问题:如果是。{ 实际类型中的值不适合您正在阅读的类型 行为是未定义的,仅仅因为标准忽略了定义 在这种情况下的任何行为,以及标准无法定义的任何时间 行为,行为未定义。在实践中,你几乎总是如此 获取一个值(没有信号或任何东西),但 的值会变化 取决于机器:具有签名幅度或1的机器 例如,补码将导致不同的值(两种方式) 从通常的2的补充。

对于其余部分,intlong是不同的类型,无论他们是谁 表示形式,int*long*不能使用别名。同样,和你一样 比方说,在C ++中,char16_t是C ++中的一个独特类型,但是是一个typedef C(所以有关别名的规则是不同的。)