当遇到参数大小不同的算术运算符时,编译器会应用"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)
前四个临时变量自动输入到每个运算符的结果类型(这是该运算符的常用算术转换生成的类型)。
-
temp1 = herp + derp;
herp和derp都是unsigned long;无需转换;结果类型为unsigned long。
-
temp2 = temp1 * durr;
temp1 和durr 都是unsigned long;无需转换;结果类型为unsigned long。
-
temp3 = sizeof wut;
sizeof 总是返回size_t,所以这就是结果的类型。
-
temp4 = temp2 * temp3
temp2 是 unsigned long,temp3 是 size_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)
这里的关键点是结果将被转换为其他类型这一事实对它的计算没有影响。编译器不能决定不应用通常的算术转换,或者应用不寻常的算术转换(可能缩小一个参数而不是扩大另一个),除非它可以证明最终结果在所有情况下都与标准规定的一种。 (这里,“在所有情况下”实际上是指“在所有情况下都没有表现出未定义的行为”,但是由于无符号算术是明确定义的,除了除/模除以 0,这个细节在这个例子中是不相关的。)
如果表达式涉及除法运算符,则溢出实际上很重要。考虑
的情况
(a + b) * c * (sizeof x) / (sizeof y)
其中a、b和c都是同一个类型,比size_t窄。
与上面的逻辑一样,(a + b) * c 在a、b 和c 的通用类型中进行评估。然后将该结果提升为size_t,以便它可以乘以x 的大小。但肯定有可能(a + b) * c在转换之前就已经溢出,导致最终结果无效。坚持使用size_t 操作数执行整个计算会更安全。这可以通过添加一个显式转换来完成:
((size_t)a + b) * c * (sizeof x) / (sizeof y)