正确检测整型溢出
在看过编译器的这些行为后,你应该会明白——“在整型溢出之前,一定要做检查,不然,就太晚了”。
我们来看一段代码:
void foo(int m, int n)
{
size_t s = m + n;
.......
}
上面这段代码有两个风险:1)有符号转无符号,2)整型溢出。
这两个情况在前面的那些示例中你都应该看到了。所以,你千万不要把任何检查的代码写在 s = m + n 这条语名后面,不然就太晚了。
undefined行为就会出现了——用句纯正的英文表达就是——“Dragon is here”——你什么也控制不住了。(注意:有些初学者也许会以为size_t是无符号的,而根据优先级 m 和 n 会被提升到unsigned int。其实不是这样的,m 和 n 还是signed int,m + n 的结果也是signed int,然后再把这个结果转成unsigned int 赋值给s)
比如,下面的代码是错的:
void foo(int m, int n)
{
size_t s = m + n;
if ( m>0 && n>0 && (SIZE_MAX - m < n) ){
//error handling...
}
}
上面的代码中,大家要注意 (SIZE_MAX – m < n) 这个判断,为什么不用m + n > SIZE_MAX呢?因为,如果 m + n 溢出后,就被截断了,所以表达式恒真,也就检测不出来了。另外,这个表达式中,m和n分别会被提升为unsigned。
但是上面的代码是错的,因为:
1)检查的太晚了,if之前编译器的undefined行为就已经出来了(你不知道什么会发生)。
2)就像前面说的一样,(SIZE_MAX – m < n) 可能会被编译器优化掉。
3)另外,SIZE_MAX是size_t的最大值,size_t在64位系统下是64位的,严谨点应该用INT_MAX或是UINT_MAX
所以,正确的代码应该是下面这样:
void foo(int m, int n)
{
size_t s = 0;
if ( m>0 && n>0 && ( UINT_MAX - m < n ) ){
//error handling...
return;
}
s = (size_t)m + (size_t)n;
}
在《苹果安全编码规范》(PDF)中,第28页的代码中:
[color=rgba(0, 0, 0, 0.9)]如果n和m都是signed int,那么这段代码是错的。正确的应该像上面的那个例子一样,至少要在n * m时要把 n 和 m 给 cast 成 size_t。因为,n*m可能已经溢出了,已经undefined了,undefined的代码转成size_t已经没什么意义了。(如果m和n是unsigned int,也会溢出),上面的代码仅在m和n是size_t的时候才有效。 [color=rgba(0, 0, 0, 0.9)]不管怎么说,《苹果安全编码规范》绝对值得你去读一读。 [color=rgba(0, 0, 0, 0.9)]二分取中搜索算法中的溢出 [color=rgba(0, 0, 0, 0.9)]我们再来看一个二分取中搜索算法(binary search),大多数人都会写成下面这个样子: [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]int binary_search(int a[], int len, int key) [color=rgba(0, 0, 0, 0.9)]{ [color=rgba(0, 0, 0, 0.9)] int low = 0; [color=rgba(0, 0, 0, 0.9)] int high = len - 1; [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)] while ( low<=high ) { [color=rgba(0, 0, 0, 0.9)] int mid = (low + high)/2; [color=rgba(0, 0, 0, 0.9)] if (a[mid] == key) { [color=rgba(0, 0, 0, 0.9)] return mid; [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] if (key < a[mid]) { [color=rgba(0, 0, 0, 0.9)] high = mid - 1; [color=rgba(0, 0, 0, 0.9)] }else{ [color=rgba(0, 0, 0, 0.9)] low = mid + 1; [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] return -1; [color=rgba(0, 0, 0, 0.9)]} [color=rgba(0, 0, 0, 0.9)]上面这个代码中,你可能会有这样的想法: [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]1) 我们应该用size_t来做len, low, high, mid这些变量的类型。没错,应该是这样的。但是如果这样,你要小心第四行 int high = len -1; 如果len为0,那么就“high大发了”。 [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]2) 无论你用不用size_t。我们在计算mid = (low+high)/2; 的时候,(low + high) 都可以溢出。正确的写法应该是: [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]int mid = low + (high - low)/2; [color=rgba(0, 0, 0, 0.9)]上溢出和下溢出的检查 [color=rgba(0, 0, 0, 0.9)]前面的代码只判断了正数的上溢出overflow,没有判断负数的下溢出underflow。让们来看看怎么判断: [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]对于加法,还好。 [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]#include <limits.h> [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]void f(signed int si_a, signed int si_b) { [color=rgba(0, 0, 0, 0.9)] signed int sum; [color=rgba(0, 0, 0, 0.9)] if (((si_b > 0) && (si_a > (INT_MAX - si_b))) || [color=rgba(0, 0, 0, 0.9)] ((si_b < 0) && (si_a < (INT_MIN - si_b)))) { [color=rgba(0, 0, 0, 0.9)] /* Handle error */ [color=rgba(0, 0, 0, 0.9)] return; [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] sum = si_a + si_b; [color=rgba(0, 0, 0, 0.9)]} [color=rgba(0, 0, 0, 0.9)]对于乘法,就会很复杂(下面的代码太夸张了): [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)]void func(signed int si_a, signed int si_b) [color=rgba(0, 0, 0, 0.9)]{ [color=rgba(0, 0, 0, 0.9)] signed int result; [color=rgba(0, 0, 0, 0.9)] if (si_a > 0) { /* si_a is positive */ [color=rgba(0, 0, 0, 0.9)] if (si_b > 0) { /* si_a and si_b are positive */ [color=rgba(0, 0, 0, 0.9)] if (si_a > (INT_MAX / si_b)) { [color=rgba(0, 0, 0, 0.9)] /* Handle error */ [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] } else { /* si_a positive, si_b nonpositive */ [color=rgba(0, 0, 0, 0.9)] if (si_b < (INT_MIN / si_a)) { [color=rgba(0, 0, 0, 0.9)] /* Handle error */ [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] } /* si_a positive, si_b nonpositive */ [color=rgba(0, 0, 0, 0.9)] } else { /* si_a is nonpositive */ [color=rgba(0, 0, 0, 0.9)] if (si_b > 0) { /* si_a is nonpositive, si_b is positive */ [color=rgba(0, 0, 0, 0.9)] if (si_a < (INT_MIN / si_b)) { [color=rgba(0, 0, 0, 0.9)] /* Handle error */ [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] } else { /* si_a and si_b are nonpositive */ [color=rgba(0, 0, 0, 0.9)] if ( (si_a != 0) && (si_b < (INT_MAX / si_a))) { [color=rgba(0, 0, 0, 0.9)] /* Handle error */ [color=rgba(0, 0, 0, 0.9)] } [color=rgba(0, 0, 0, 0.9)] } /* End if si_a and si_b are nonpositive */ [color=rgba(0, 0, 0, 0.9)] } /* End if si_a is nonpositive */ [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)] result = si_a * si_b; [color=rgba(0, 0, 0, 0.9)]} [color=rgba(0, 0, 0, 0.9)]更多的防止在操作中整型溢出的安全代码可以参看《INT32-C. Ensure that operations on signed integers do not result in overflow》
|