这可能是一个非常基本的编程问题,但这是我一段时间以来想要理解的问题。
考虑这个简单的例子:
int main(void)
{
unsigned char a = 5;
unsigned char b = 20;
unsigned char m = 0xFF;
unsigned char s1 = m + a - b;
unsigned char s2 = m - b + a;
printf("s1 %d s2 %d", s1, s2);
return 0;
}
假设在C中从左到右计算算术运算符,这里的第一个计算应该在m + a处溢出。但是,运行此程序会为s1和s2返回相同的答案。 我的问题是:由于溢出,第一个表达式会导致未定义的行为吗? 第二个表达式应该避免溢出,但我想理解为什么这两个表达式返回相同的答案。
答案 0 :(得分:2)
由于C&C的整数提升,s1计算有效地执行为:
unsigned char s1 = (unsigned char)( (int)m + (int)a - (int)b );
没有临时溢出。
答案 1 :(得分:1)
(已更正)对整数类型进行算术运算时,所有小于int的类型在计算过程中都会提升为int,如果结果类型较小则会被截断。
见:
https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules
答案 2 :(得分:0)
根据ISO C specification §6.2.5.9
涉及无符号操作数的计算永远不会溢出, 因为无法用结果无符号整数类型表示的结果以模数减少,该数字大于可由结果类型表示的最大值。
这意味着分别在加法和减法中出现的可能正和负溢出实际上都是作为带符号的unsigned char
执行的,因此它们都是定义良好的。在计算表达式之后,结果将被截断回{{1}},因为这是左侧结果类型。
答案 3 :(得分:0)
小于int
的类型的操作是通过将结果转换为int
,进行计算,然后将结果转换回原始类型来执行的。对于小的无符号类型,提供的计算结果适合类型int
,这将导致结果的高位被静默忽略。已发布的标准基本原理表明,作者希望非古老的实现在将值存储到不大于int
的无符号类型时忽略高位,而不考虑计算是否适合在int
类型中,它已不再适用于"现代" 可靠的编译器以这种方式运行。例如,在具有16位短和32位int
的系统上,函数
unsigned mulMod65536(unsigned short x, unsigned short y)
{ return (x*y) & 0xFFFFu; }
通常表现得相当于:
unsigned mulMod65536(unsigned short x, unsigned short y)
{ return (1u*x*y) & 0xFFFFu; }
但在某些情况下,gcc会使#34;聪明的"基于以下事实的优化
如果x*y
超过2147483647,它被允许以任意方式行事,即使高位不应该影响结果。
涉及小型签名类型的操作与使用无符号类型的操作类似,不同之处在于允许实现将超出较小类型范围的值映射到实现定义方式中的那些类型的值,或者如果实现定义的信号,则引发实现定义的信号尝试转换超出范围的值。实际上,即使在这种情况下,几乎所有实现都使用两个补码截断。虽然在某些情况下某些其他行为可能更便宜,但标准要求实施以一致的文档形式表现。