我最近在我的代码中发现了一个错误,花了我几个小时来调试。
问题出在定义如下的函数中:
unsigned int foo(unsigned int i){
long int v[]={i-1,i,i+1} ;
.
.
.
return x ; // evaluated by the function but not essential how for this problem.
}
v的定义在我的开发机器(ubuntu 12.04 32位,g编译器)上没有引起任何问题,其中unsigned int被隐式转换为long int,因此负值被正确处理。
在另一台机器上(ubuntu 12.04 64位,g编译器),这个操作是不安全的。当i=0时,v[0]没有被设置为-1,而是被设置为一个奇怪的大值(当试图使一个无符号int为负时经常发生)。
我可以通过将i的值转换为long int来解决这个问题
long int v[]={(long int) i - 1, (long int) i, (long int) i + 1};
并且(在两台机器上)一切都工作正常。
我不明白为什么第一个在一台机器上工作得很好,而在另一台机器上不工作。
你能帮助我理解这一点吗,这样我就可以避免将来出现这种或其他问题?
3条答案
按热度按时间qco9c6ql1#
对于
unsigned
值,加法/减法被很好地定义为模运算,因此0U-1
的结果类似于std::numeric_limits<unsigned>::max()
。当从unsigned转换为signed时,如果目标类型足够大,可以容纳所有unsigned值,那么它只是直接将数据复制到目标类型中。如果目标类型不够大,不能容纳所有unsigned值,我相信它是由实现定义的(将尝试找到标准引用)。
因此,当
long
为64位时(假设您的64位机器上也是这种情况),无符号函数适合并直接复制。当
long
在32位机器上为32位时,它很可能将位模式解释为有符号值,在本例中为-1。编辑:避免这些问题的最简单方法是避免混合使用有符号和无符号类型。从一个概念不允许负数的值中减去1意味着什么?我将论证在你的例子中函数参数应该是一个有符号的值。
也就是说,g++(至少4.5版)提供了一个方便的
-Wsign-conversion
,它可以检测特定代码中的这个问题。t2a7ltrp2#
您还可以使用专用强制转换来捕获所有溢出强制转换:
使用此方法将在调试中捕获所有从大于结果类型所能容纳的数字进行的强制转换。这包括无符号int为0并减去-1的情况,这将导致最大的无符号int。
c8ib6hqw3#
C++标准中的整数提升规则继承自C标准中的整数提升规则,选择这些整数提升规则不是为了描述一种语言应该如何最有效地表现,而是为了提供一种行为描述,这种行为描述与许多现有实现扩展早期C语言方言以添加无符号类型的方式一致。
由于明显希望让标准指定被认为在现有实现中100%一致的行为方面,而不考虑某些其他兼容行为是否可能更广泛地有用,同时避免让标准对操作强加任何行为要求,如果在某些看似合理的实现上,保证任何行为与顺序一致可能是昂贵的程序执行,但不可能保证任何实际有用的行为。
我认为很明显,委员会希望明确地指定
long1 = uint1+1; uint2 = long1;
和long1 = uint1+1; uint2 = long1;
必须在所有情况下以与绕回行为一致的方式设置uint2
,并且不想禁止它们在设置long1
时使用环绕行为。在对uint2
的赋值将产生与始终使用绕回行为一致的结果的绕回二进制补码平台中,这样做意味着包括一个专门用于静默绕回二进制补码平台的规则,而这是C89和C99(在更大程度上)特别希望避免的。