关于常用算术转换的问题-GCC编译器

Question on Usual Arithmetic Conversions - GCC Compiler

我正在尝试理解 C 中的隐式数据类型转换。我以为我已经理解了这个主题,但是下面的代码示例仍然让我感到困惑。

具体来说,我之前从 C 标准的草稿中读到了有关常用算术转换和整数提升的内容。

    unsigned short int a = 0;
    printf("\n%lld", (signed int)a - 1);

我正在使用 GCC 进行编译。

unsigned short int 是 2 个字节。 int 是 4 个字节。

当我运行这段代码时,我得到以下结果:4294967295

我期望结果 -1

这是我预期会发生的事情:

  1. Typecast优先,-的LHS变为signed int.

  2. 进行了
  3. -操作。这里没有整数提升或隐式转换发生,因为 LHS 和 RHS 已经都是 signed int。运算结果为 -1,数据类型为 signed int.

  4. printf语句中,值-1在转换为long long int时保留,结果显示-1。

谁能解释一下我逻辑上的缺陷在哪里?

它是未定义的行为,因为 %lldint 类型的不合适的格式说明符。

确实 (signed int)a - 1 是一个 int 类型,值为 -1,但是 printf 调用是未定义的部分。 C 标准中没有任何内容建议转换为 long long

Within printf statement, value -1 is retained within the conversion to long long int

没有发生这样的转换。 printf(函数族)是愚蠢的,需要一个与参数列表类型相对应的格式字符串。

printf not 像普通函数一样工作 void f (long long int x),它会强制隐式转换为参数类型 ("as per assignment"/"lvalue conversion")。这会给你预期的 "sign extension".

值得注意的是,这里还有另一种专门的隐式转换,称为默认参数提升,它只适用于可变参数函数和没有原型的函数。

C17 6.5.2.2/6

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

C17 6.5.2.2/7 关于可变参数函数:

The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

实际上这意味着:

    传递给 printf
  • float 在函数调用期间隐式转换为 double
  • 传递给 printf 的小整数类型在函数调用期间根据整数提升进行隐式转换,最有可能以 int.
  • 结束
  • 传递给 printf 的其他类型在函数调用期间不会隐式提升。

然后传递的和可能转换的参数在内部被视为转换说明符指定的类型。如果那个与实际类型不匹配,则代码具有未定义的行为。

在您的情况下,您传递了一个 int,它不会被隐式提升,但由于 printf 将其视为 long long,您会得到未定义的行为。

在这里你可以认为自己很幸运。 a 是一个 short int,它经过通常的算术转换为“signed int”,即使进行了强制转换,所以

unsigned short int a = 0;
printf("\n%d", (signed int)a - 1);

unsigned short int a = 0;
printf("\n%d", a - 1);

将具有相同的行为,if unsigned short 的所有值都可以在 int 中表示(就像您的情况一样)。转换的结果是 int。现在,对于可变参数,应用默认参数提升,任何小于 int 的整数如果可表示则转换为 int,否则 unsigned int。但是 lld 期望 signed long long int 是 8 个字节宽。默认参数提升不会将 int 隐式提升为 long long int.

现在运气来了——你确实得到了一个错误的值。看,由于 行为未定义 你可能已经得到了你 期望 的值,这次——毕竟在64位处理器上是完全可行的!