输入 promotion/conversion

Type promotion/conversion

sizeof 运算符的结果似乎是 size_t 类型,在 Windows 64 位上定义为 unsigned long long。

考虑以下一段(伪)代码:

int func(unsigned long arg)
{
    /* ... */
}

/* ... */

unsigned long herp = 3,
              derp = 5,
              durr = 7;

wchar_t wut;

func(/* ... */);

当如下调用 func 时,promotion/conversion 类型实际上发生了什么?

func((herp + derp) * durr * sizeof wut); // 1st example

herp、derp、durr的值是否先提升为unsigned long long,然后在计算结果后,将结果转换回unsigned long?

相反,当按如下方式调用 func 时,唯一发生的转换是将类型转换为 unsigned long 吗?

func((herp + derp) * durr * (unsigned long)sizeof wut); // 2nd example

考虑类型conversion/promotion,最appropriate/correct调用func的方式是什么?

当遇到参数大小不同的算术运算符时,编译器会应用 "Usual Arithmetic Conversions",这基本上使两个操作数的类型相同。

这是在整个表达式中执行的,一次一个运算符。根据C的句法,将表达式解析为单独的操作;编译器不允许修改解析,除非它能证明结果没有区别(在这种情况下,修改或多或少是无关紧要的)。

所以,考虑一下你的表达方式:

func((herp + derp) * durr * sizeof wut);

这在语法上等同于以下一系列操作,其中省略了类型和转换(暂时):

temp1 = herp + derp;
temp2 = temp1 * durr;
temp3 = sizeof wut
temp4 = temp2 * temp3;
temp5 = (unsigned long) temp4;
func(temp5)

前四个临时值自动输入每个运算符的结果类型(这是该运算符的常用算术转换生成的类型)。

  1. temp1 = herp + derp;

    herpderp都是unsigned long;无需转换;结果类型是 unsigned long.

  2. temp2 = temp1 * durr;

    temp1durr都是unsigned long;无需转换;结果类型是 unsigned long.

  3. temp3 = sizeof wut;

    sizeof总是returnssize_t,所以就是结果的类型。

  4. temp4 = temp2 * temp3

    temp2unsigned longtemp3size_t,在示例平台上是unsigned long long。这需要将temp2转换为unsigned long long,之后结果类型为unsigned long long.

所以我们可以在上面的示例代码中插入类型和转换:

unsigned long temp1 = herp + derp;
unsigned long temp2 = temp1 * durr;
unsigned long long temp3 = sizeof wut
unsigned long long temp4 = (unsigned long long)temp2 * temp3;
temp5 = (unsigned long)temp4;
func(temp5)

这里的关键要点是结果将被转换为其他类型这一事实对其计算没有影响。编译器不能决定不应用通常的算术转换,或应用不寻常的算术转换(可能缩小一个参数而不是扩大另一个参数),除非它可以证明最终结果在所有情况下都与标准规定的一种。 (这里,"in all cases" 实际上意味着 "in all cases which do not exhibit undefined behaviour",但由于除 divide/modulo 为 0 之外的无符号算术是明确定义的,因此此细节与此示例无关。)

如果表达式涉及除法运算符,那么溢出实际上很重要。考虑

的情况
(a + b) * c * (sizeof x) / (sizeof y)

其中abc都是同一类型,比size_t.

更窄

与上面的逻辑一样,(a + b) * c 是在 abc 的常见类型中求值的。然后将该结果提升为 size_t,以便它可以乘以 x 的大小。但是肯定有可能 (a + b) * c 在转换之前已经溢出,导致最终结果无效。坚持使用 size_t 个操作数执行整个计算会更安全。这可以通过添加一个显式转换来完成:

((size_t)a + b) * c * (sizeof x) / (sizeof y)