【问题标题】:Why doesn't integer promotion happen in my case?为什么在我的情况下不会发生整数提升?
【发布时间】:2020-04-16 09:21:36
【问题描述】:
#include <stdio.h>

void main()
{
    unsigned char a = 0xfb;
    char b = 0xfb;
    printf("case1 : %d, %d", a, b); // case1

    char x=90, y=60, z=100, h;
    h = x*y/z;
    printf("\ncase2 : %d", h);      // case2

    int m = 32768, n = 65536, o = 2, i;
    i = m*n/o;
    printf("\ncase3 : %d", i);      // case3
}

result

case1 : 251, -5
case2 : 54
case3 : -107341824

在 case1 中,标识符 b 被编译为 -5,这不是语法错误,认为 char 只接受 -128~127 值。那么,第一个问题是标识符b在翻译结束时首先保存为int数据类型?(翻译结束时,b将保存为char。)

在 case2 中,x,y 被提升为 int。所以 h 有正确的结果值。但在 case3 中,m、n 不会被提升为 unsigned int(也许)。标识符 I 没有普通值(2^30)。 C99 这么说

如果一个int可以表示原始类型的所有值,则将该值转换为一个int; 否则,将其转换为无符号整数。

基于C99,h的值是自然的,但是处理m*n/o是溢出的。这不自然,因为它反对 C99。这是我的第二个查询。

【问题讨论】:

  • 请不要发布代码图片 - 将文本复制到问题中。
  • 促销没有发生在您的“案例 3”中 - 因为 mn已经定义为 int。因此,对于i = m * n / o,您只是看到整数溢出。
  • @Adrian Mole 根据'如果一个int可以表示原始类型的所有值,则将该值转换为int;否则,它被转换为无符号整数。',我认为 m 和 n 与'否则,它被转换为无符号整数'有关。所以我无法理解 case3 中没有发生促销。
  • 在这种情况下,没有促销 - 只是有符号和无符号之间的转换(或两个)。
  • m * n 超过了int 的存储大小(加一),中间结果不能用int 表示。 Why is unsigned integer overflow defined behavior but signed integer overflow isn't?

标签: c integer-promotion


【解决方案1】:

在case1中,标识符b被编译为-5,这不是语法错误 认为 char 只接受 -128~127 的值。所以,第一个问题是 标识符 b 首先在其末尾保存为 int 数据类型 翻译?(翻译结束时,b会保存在char中。)

在情况 1 中,变量 b 的初始化器,常量 0xfb,表示类型为 int 的值,值为 251(十进制)。在 C 抽象机器模型中,当初始化程序运行时,此值将转换为 b 的类型 (char),对于块作用域变量,当执行到达该声明时(而不是在翻译期间)。如果您的实现中char 的范围确实是-128 - 127,那么您已经签署了不能代表初始化器值的chars,从而导致实现定义的行为。

因此,再次引用抽象机器模型,nothing 在翻译结束时存储在b。它是一个块作用域变量,因此在执行到达其声明之前它不存在。翻译后的程序确实需要以某种方式存储b 的初始值,但C 并未指定它应该以何种形式存储。但是,在翻译或执行期间,b 绝不会包含 int 类型的值。

printf 调用的参数被计算时,b 的值被读取然后转换为int(因为它是一个可变参数)。可以说,可以使用 %d 字段来打印它,但是如果您想确定打印转换前的值,那么您应该真正使用 %hhd 代替(尽管在您的情况下,这几乎当然打印相同的结果)。

在 case2 中,x,y 被提升为 int。所以 h 有正确的结果值。

更具体地说,在情况 2 中,xyz 的值提升为 int在评估表达式 x*y/z,每个操作都会产生一个int 结果。乘法不会溢出所选类型int,并且总体结果在char 类型的范围内,因此在分配给h 时应用到char 的转换并不显着。

但是在 case3, m, n 不会被提升为 unsigned int(maybe)。标识符 I 没有普通值(2^30)。

在情况 3 中,mno 已经具有类型 int,因此它们不会被提升,并且算术表达式计算相同类型 (int) 的结果。子表达式m*n的结果不在int类型的范围内,所以会出现未定义的行为,按照标准的paragraph 6.5/5

如果在评估过程中出现异常情况 表达式(也就是说,如果结果不是数学定义的,或者 不在其类型的可表示值范围内),行为 未定义。

这是真的

C99 [和 C11] 这么说

如果一个int可以表示原始类型的所有值,则将该值转换为一个int;否则,将其转换为无符号整数。

,但这在这里无关紧要。它是“整数提升”描述的一部分,它适用于表达式的操作数,基于它们的类型。

基于C99,h的值是自然的,但是处理m*n/o是 溢出。这不自然,因为它反对 C99。这是我的 第二个查询。

您似乎期望中间表达式m*n 将被评估以产生unsigned int 类型的结果,因此它不会溢出,但标准不支持这一点。通常的算术转换,包括整数提升,仅基于操作数类型的特征,包括它们的符号和值范围。接受通常算术转换的运算符(包括*)使用它们来确定所有操作数和结果的通用类型。

您的mn 已经是同一类型,并且该类型是int,不适用任何转换/促销活动。如果 mn 的值不是它们的乘积(作为 int)未定义,则乘法的结果也将是 int。然而事实上,该操作溢出了int 类型,产生了未定义的行为。

【讨论】:

    【解决方案2】:

    在情况 1 中,使用大于 CHAR_MAX 的值 0xfb 初始化在您的平台上签名的 char 不是语法错误。效果是实现定义的,不出所料,0xfb 被转换为具有相同 8 位模式的值-5ab 在作为变量参数传递给 printf 时被提升为 int,因为这是这种情况下的指定行为。

    在情况 2 中,xyz 被提升为 int,因为在您的平台上,int 类型可以表示 char 类型的所有值。使用int 算术执行计算,产生54 的结果,它在char 类型的范围内,因此可以毫无问题地存储到h 中。 h 在传递给printf 时被提升为int

    在情况 3 中,mn 不会被提升,因为它们已经具有类型 int。提升规则适用于每个操作数,而不适用于操作的结果,就好像它以任意精度执行一样:m*n 使用 int 算术执行,并且在您的平台上溢出,int 大概是 32 位宽。行为是未定义。碰巧结果是-2147483648,所以除以2 得到-107341824,但C 标准不保证这一点。

    为了准确执行计算,至少mn 必须具有更大的类型或被强制转换为:i = (unsigned)m * n / o; 将在您的平台上生成107341824,但请注意int 类型可能少于 32 位。对于可移植表达式,您需要使用类型 unsigned long,它被指定为至少具有 32 个值位,用于转换和 i 的类型。

    最后,main 应该定义为int main()int main(void)int main(int argc, char *argv[]) 或兼容的原型。 void main() 不正确。

    【讨论】:

      【解决方案3】:
      #include <stdio.h>
      
      int main()
      {
          unsigned char a = 0xfb;
          char b = 0xfb;
          printf("case1 : %d, %d", a, b); // case1
      
          char x=90, y=60, z=100, h;
          h = x*y/z;
          printf("\ncase2 : %d", h);      // case2
      
          unsigned long long int  m = 32768, n = 65536, o = 2,i;
          i = m*n/o;
          printf("\ncase3 : %llu", i);      // case3
      
      }
      

      在这种类型中你可以得到答案 [0, +18,446,744,073,709,551,615] 范围

      【讨论】:

      • 为了防止中间计算溢出,你也可以做int m = 32768, n = 65536, o = 2; unsigned long long i = (unsigned long long)m*n/o;,然后通过包含limits.h和检查iINT_MAX来判断结果是否在@987654326的范围内@.
      • 我没有看到这如何回答提出的问题。
      猜你喜欢
      • 2019-12-21
      • 1970-01-01
      • 1970-01-01
      • 2016-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多