0%

【安全编码】整数安全

本篇总结了《C和C++安全编码》中整数相关的内容。

常见的整数安全漏洞

1 无符号整数回绕

涉及无符号操作数的计算永远不会溢出,因为回绕,一个无符号整数表达式永远无法求出小于零的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 回绕:涉及无符号操作数的计算永远不会溢出
void test_integer_security_wrap_around()
{
unsigned int ui = UINT_MAX; fprintf(stdout, "ui value 1: %u\n", ui); // 4294967295
ui++; fprintf(stdout, "ui value 2: %u\n", ui); // 0
ui = 0; fprintf(stdout, "ui value 3: %u\n", ui); // 0
ui--; fprintf(stdout, "ui value 4: %u\n", ui); // 4294967295

//for (unsigned i = n; --i >= 0; ) // 此循环将永远不会终止

unsigned int i = 0, j = 0, sum = 0;
// ... 对i, j, sum进行一些赋值运算操作
if (sum + i > UINT_MAX) { } // 不会发生,因为sum+i回绕了
if (i > UINT_MAX - sum) { } // 好很多

if (sum - j < 0) { } // 不会发生,因为sum-j回绕了
if (j > sum) { } // 正确
}

但也并非所有无符号整数回绕都是安全缺陷。精心定义的无符号整数算术求模属性经常被特意使用,例如,在散列算法和C标准里rand()的示例实现中就都用到了这个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void test_integer_security_wrap_around2()
{
{ // 展示了一个无符号整数回绕导致的实际漏洞的例子
size_t len = 1;
char* src = "comment";

size_t size;
size = len - 2;
fprintf(stderr, "size = %u, %x, %x, %d\n", size, size, size+1, size+1); // 4294967295, ffffffff, 0, 0
char* comment = (char*)malloc(size + 1);
//memcpy(comment, src, size); // crash
free(comment);
}

{
int element_t;
int count = 10;
// 库函数calloc接受两个参数:存储元素类型所需要的空间和元素的个数.为了求出所需内存的大小,使用元素个数
// 乘以该元素类型所需的单位空间来计算.如果计算所得结果无法用类型为size_t的无符号整数表示,那么,尽管分
// 配程序看上去能够成功地执行,但实际上它只会分配非常小的内存空间.结果,应用程序对分配的缓冲区的写操作
// 可能会越界,从而导致基于堆的缓冲区溢出
char* p = (char*)calloc(sizeof(element_t), count);
free(p);
}

{
int off = 1, len = 2;
int type_name;
// 这里的off和len都声明为signed int.因为根据C标准的定义,sizeof运算符返回的是一个无符号整数类型(size_t),
// 整数转换规则要求在那些signed int的宽度等于size_t的宽度的实现上,len - sizeof(type_name)被计算为无符号
// 的值,如果len比sizeof运算符返回的值小,那么减法操作会回绕并产生一个巨大的正值
std::cout<<"len - sizeof(type_name): "<<len - sizeof(type_name)<<std::endl; // 18446744073709551614
if (off > len - sizeof(type_name)) return;
// 要消除以上问题,可以把整数范围检查编写为下列替代形式
// 程序员仍然必须保证这里的加法操作不会导致回绕,这是通过保证off的值在一个已定义的范围内实现的.为了消除
// 潜在的转换错误,在本例中也应当把off和len都声明为size_t类型
if ((off + sizeof(type_name)) > len) return;
}
}

2 有符号整数溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 有符号整数溢出
void test_integer_security_overflow()
{
int i = INT_MAX; // 2147483647, int最大值
i++; fprintf(stdout, "i = %d\n", i); // -2147483648, int最小值

i = INT_MIN; // -2147483648, int最小值
i--; fprintf(stdout, "i = %d\n", i); // 2147483647, int最大值

std::cout << "abs(INT_MIN): " << std::abs(INT_MIN) << std::endl; // -2147483648
// 因为二进制补码表示是不对称的,数值0被表示为”正”数,所以用补码表示的一个给定类型最小负值的相反数不能以那种类型表示
// 对最小的负值而言,结果是未定义的或错误的
#define abs(n) ((n) < 0 ? -(n) : (n))
#undef abs
}

3 字符类型表示整数

在把 char 类型用于数值时必须使用明确的 signed char 或 unsigned char。建议仅使用 signed char 和 unsigned char 类型存储和使用小数值,也就是范围分别在 SCHAR_MIN 和 SCHAR_MAX 之间,或 0 和 UCHAR_MAX 之间的值,因为这是可移植的保证数据的符号字符类型的唯一方式。常规的 char 不应该被用来存储数值,因为编译器有定义 char 的自由,使其要么与 signed char,要么与 unsigned char 具有相同的范围、表示和行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 字符类型
void test_integer_security_char()
{
{
// char类型的变量c可能是有符号或无符号的
// 初始值200(它具有signed char类型)无法在(有符号的)char类型中表示(这是未定义的行为)
// 许多编译器将用标准的由无符号转换到有符号的模字大小(modulo-word-size)规则把200转换为-56
char c = 200;
int i = 1000;
fprintf(stdout, "i/c = %d\n", i / c); // 在windows/linux上会输出-17, 1000/-56=-17
}

{
// 声明unsigned char型变量c,使后面的除法操作与char的符号性无关,因此它有一个可预见的结果
unsigned char c = 200;
int i = 1000;
fprintf(stdout, "i/c = %d\n", i / c); // 5
}
}

4 整数类型提升

如果一个整数类型具有低于或等于 int 或 unsigned int 的整数转换级别,那么它的对象或表达式在用于一个需要 int 或 unsigned int 的表达式时,就会被提升。整数类型提升被作为普通算术转换的一个组成部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void test_integer_security_promotion()
{
{
int sum = 0;
char c1 = 'a', c2 = 'b';
// 整数类型提升规则要求把c1和c2都提升到int类型
// 然后把这两个int类型的数据相加,得到一个int类型的值,并且该结果被保存在整数类型变量sum中
sum = c1 + c2;
fprintf(stdout, "sum: %d\n", sum); // 195
}

{
signed char cresult, c1, c2, c3;
c1 = 100; c2 = 3; c3 = 4;
// 在用8位补码表示signed char的平台上,c1与c2相乘的结果可能会因超过这些平台上signed char类型的最大值(+127)
// 而引起signed char类型的溢出.然而,由于发生了整数类型提升,c1, c2和c3都被转换为int,因此整个表达式的结果
// 能够被成功地计算出来.该结果随后被截断,并被存储在cresult中.由于结果位于signed char类型的取值范围内,因
// 此该截断操作并不会导致数据丢失或数据解释错误
cresult = c1 * c2 / c3;
fprintf(stdout, "cresult: %d\n", cresult); // 75
}

{
unsigned char uc = UCHAR_MAX; // 0xFF
// 当uc用作求反运算符"~"的操作数时,通过使用零扩展把它扩展为32位,它被提升为signed int类型,因此,在
// x86-32架构平台中,该操作始终产生一个类型为signed int的负值
int i = ~uc;
fprintf(stdout, "i: %0x\n", i); // 0xffffff00
}
}

5 无符号整数类型转换

从较小的无符号整数类型转换到较大的无符号整数类型始终是安全的,通常通过对其值进行零扩展(zero-extending)而完成。当表达式包含不同宽度的无符号整数操作数时,C标准要求每个操作的结果都具有其中较宽的操作数的类型(和表示范围)。假设相应的数学运算产生一个在结果类型能表示的范围内的结果,则得到的表示值就是那个数学值。如果数学结果值不能用结果类型表示,发生的情况有两类:无符号,损失精度;无符号值转换成有符号值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void test_integer_security_unsigned_conversion()
{
{ // 无符号,损失精度
unsigned int ui = 300;
// 当uc被赋予存储在ui中的值时,值300以模2^8取余,或300-256=44
unsigned char uc = ui;
fprintf(stdout, "uc: %u\n", uc); // 44
}

{ // 无符号值转换成有符号值
unsigned long int ul = ULONG_MAX;
signed char sc;
sc = ul; // 可能会导致截断错误
fprintf(stdout, "sc: %d\n", sc); // -1
}

{ // 当从一个无符号类型转换为有符号类型时,应验证范围
unsigned long int ul = ULONG_MAX;
signed char sc;
if (ul <= SCHAR_MAX) {
sc = (signed char)ul; // 使用强制转换来消除警告
} else { // 处理错误情况
fprintf(stderr, "fail\n");
}
}
}

6 有符号整数类型转换

从较小的有符号整数类型转换为较大的有符号整数类型始终是安全的,并可以采用对该值进行符号扩展的方法在补码表示中实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void test_integer_security_signed_conversion()
{
{ // 有符号,损失精度
signed long int sl = LONG_MAX;
signed char sc = (signed char)sl; // 强制转换消除了警告
fprintf(stdout, "sc: %d\n", sc); // -1
}

{ // 当从一个有符号类型转换到精度较低的有符号类型时,应验证范围
signed long int sl = LONG_MAX;
signed char sc;
if ((sl < SCHAR_MIN) || (sl > SCHAR_MAX)) { // 处理错误情况
fprintf(stderr, "fail\n");
} else {
sc = (signed char)sl; // 使用强制转换来消除警告
fprintf(stdout, "sc: %d\n", sc);
}
}

{ // 负值和无符号值的比较固有问题
unsigned int ui = UINT_MAX;
signed char c = -1;
// 由于整数提升,c被转换为unsigned int类型的值0xFFFFFFFF,即4294967295
if (c == ui) {
fprintf(stderr, "why is -1 = 4294967295\n");
}
}

{ // 从有符号类型转换为无符号类型时,可能发生类型范围错误,包括数据丢失(截断)和损失符号(符号错误)
signed int si = INT_MIN;
// 导致损失符号
unsigned int ui = (unsigned int)si; // 强制转换消除了警告
fprintf(stderr, "ui: %u\n", ui); // 2147483648
}

{ // 从有符号类型转换为无符号类型时,应验证取值范围
signed int si = INT_MIN;
unsigned int ui;
if (si < 0) { // 处理错误情况
fprintf(stderr, "fail\n");
} else {
ui = (unsigned int)si; // 强制转换消除了警告
fprintf(stdout, "ui: %u\n", ui);
}
}
}

下面展示了一些整数转换和截断错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void test_integer_security_conversion_truncation()
{
{ // 由转换错误导致的安全漏洞
int size = 5;
int MAX_ARRAY_SIZE = 10;
// 如果size为负数,此检查将通过,而malloc()函数将被传入一个为负的大小.因为malloc()需要size_t类型的参数,
// 所以size会被转换成一个巨大的无符号数.当有符号整数类型被转换为一个无符号的整数类型时,会重复加上或减去
// 新类型的宽度(2^N),以使结果落在可表示的范围之内.因此,这种转换可能会导致大于MAX_ARRAY_SIZE的值.这种
// 错误可以通过把size声明为size_t而不是int来消除
if (size < MAX_ARRAY_SIZE) { // 初始化数组
char* array = (char*)malloc(size);
free(array);
} else { // 处理错误
fprintf(stderr, "fail\n");
}
}

{ // 由整数截断错误导致的缓冲区溢出漏洞
char* argv[3] = {"", "abc", "123"};
unsigned short int total;
// 攻击者可能会提供两个总长度无法用unsigned short整数total表示的字符做参数,这样,总长度值将会用比结果
// 类型所能表示的最大值大1的数取模截断,函数strlen返回一个无符号整数类型size_t的结果,对于大多数实现而言,
// size_t的宽度大于unsigned short的宽度,必然要进行降级操作,strcpy和strcat的执行将导致缓冲区溢出
total = strlen(argv[1]) + strlen(argv[2]) + 1;
char* buff = (char*)malloc(total);
strcpy(buff, argv[1]);
strcat(buff, argv[2]);
fprintf(stdout, "buff: %s\n", buff);
free(buff);
}
}

7 整数操作

所有证书操作都可能会导致异常情况下的错误,如溢出、回绕和截断。当某个操作产生的结果不能在操作结果类型中表示时,就会发生异常情况。

7.1 赋值

在简单的赋值(=)中,右操作数的值被转换为赋值表达式的类型并替换存储在左操作数所指定的对象的值。用一个有符号整数为一个无符号整数赋值,或者用一个无符号整数为一个宽度相等的有符号整数赋值,都可能导致所产生的值被误解。当从一个具有较大宽度的类型向较小宽度的类型赋值或强制类型转换时,就会导致发生截断。如果该值不能用结果类型表示,那么数据可能会丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int f_5_4(void) { return 66; }
void test_integer_security_assignment()
{
{
char c;
// 函数f_5_4返回的int值可能在存储到char时被截断,然后在比较之前将其转换回int宽度
// 在"普通"char具有与unsigned char相同的取值范围的实现中,转换的结果不能为负,所以下面比较的操作数
// 永远无法比较为相等,因此,为了有充分的可移植性,变量c应声明为int类型
if ((c = f_5_4()) == -1) {}
}

{
char c = 'a';
int i = 1;
long l;
// i的值被转换为c=i赋值表达式的类型,那就是char类型,然后包含在括号中的表达式的值被转换为括号外的赋值
// 表达式的类型,即long int型.如果i的值不在char的取值范围内,那么在这一系列的分配后,比较表达式
// l == i是不会为真的
l = (c = i);
}

{
// 用一个有符号整数为一个无符号整数赋值,或者用一个无符号整数为一个宽度相等的有符号整数赋值,
// 都可能导致所产生的值被误解
int si = -3;
// 因为新的类型是无符号的,那么通过反复增加或减去比新的类型可以表示的最大值大1的数,该值可以被转换,
// 直到该值落在新的类型的取值范围内.如果作为无符号值访问,结果值会被误解为一个大的正值
unsigned int ui = si;
fprintf(stdout, "ui = %u\n", ui); // 4294967293
fprintf(stdout, "ui = %d\n", ui); // -3
// 在大多数实现中,通过逆向操作可以轻易地恢复原来的值
si = ui;
fprintf(stdout, "si = %d\n", si); // -3
}

{
unsigned char sum, c1, c2;
c1 = 200; c2 = 90;
// c1和c2相加产生的值在unsigned char的取值范围之外,把结果赋值给sum时会被截断
sum = c1 + c2;
fprintf(stdout, "sum = %u\n", sum); // 34
}
}
7.2 加法

可以用来将两个算术操作数或者将一个指针与一个整数相加。如果两个操作数都是算术类型,那么将会对它们执行普通算术转换。二元的”+”运算符的结果就是其操作数的和。递增与加1等价。如果表达式是将一个整数类型加到一个指针上,那么其结果将是一个指针,这称为指针算术运算。两个整数相加的结果总是能够用比两个操作数中较大者的宽度大1位的数来表示。任何整数操作的结果都可以用任何比其中较大者的宽度大1的类型表示。如果结果整数类型占用的位数不足以表示其结果,那么整数加法就会导致溢出或回绕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void test_integer_security_add()
{
{ // 先验条件测试,补码表示: 用来检测有符号溢出,该解决方案只适用于使用补码表示的架构
signed int si1, si2, sum;
si1 = -40; si2 = 30;
unsigned int usum = (unsigned int)si1 + si2;
fprintf(stdout, "usm = %x, si1 = %x, si2 = %x, int_min = %x\n", usum, si1, si2, INT_MIN);
// 异或可以被当作一个按位的"不等"操作,由于只关心符号位置,因此把表达式用INT_MIN进行掩码,
// 这使得只有符号位被设置
if ((usum ^ si1) & (usum ^ si2) & INT_MIN) { // 处理错误情况
fprintf(stderr, "fail\n");
} else {
sum = si1 + si2;
fprintf(stdout, "sum = %d\n", sum);
}
}

{ // 一般的先验条件测试
signed int si1, si2, sum;
si1 = -40; si2 = 30;
if ((si2 > 0 && si1 > INT_MAX - si2) || (si2 < 0 && si1 < INT_MIN - si2)) { // 处理错误情况
fprintf(stderr, "fail\n");
} else {
sum = si1 + si2;
fprintf(stdout, "sum = %d\n", sum);
}
}

{ // 先验条件测试:保证没有回绕的可能性
unsigned int ui1, ui2, usum;
ui1 = 10; ui2 = 20;
if (UINT_MAX - ui1 < ui2) { // 处理错误情况
fprintf(stderr, "fail\n");
} else {
usum = ui1 + ui2;
fprintf(stdout, "usum = %u\n", usum);
}
}

{ // 后验条件测试
unsigned int ui1, ui2, usum;
ui1 = 10; ui2 = 20;
usum = ui1 + ui2;
if (usum < ui1) { // 处理错误情况
fprintf(stderr, "fail\n");
}
}
}
7.3 减法

与加法类型,减法也是一种加法操作。对减法而言,两个操作数都必须是算术类型或指向兼容对象类型的指针。从一个指针中减去一个整数也是合法的。递减操作等价于减1操作。如果两个操作之差是负数,那么无符号减法会产生回绕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void test_integer_security_substruction()
{
{ // 先验条件测试:两个正数相减或两个负数相减都不会发生溢出
signed int si1, si2, result;
si1 = 10; si2 = -20;
// 如果两个操作数异号,并且结果的符号与第一个操作数不同,则已发生减法溢出
// 异或用作一个按位的"不等"操作.要测试符号位置,表达式用INT_MIN进行掩码,这使得只有符号位被设置
// 该解决方案只适用于适用补码表示的架构
if ((si1 ^ si2) & (((unsigned int)si1 - si2) ^ si1) & INT_MIN) { // 处理错误条件
fprintf(stderr, "fail\n");
} else {
result = si1 - si2;
fprintf(stdout, "result = %d\n", result);
}

// 可移植的先验条件测试
if ((si2 > 0 && si1 < INT_MIN + si2) || (si2 < 0 && si1 > INT_MAX + si2)) { // 处理错误条件
fprintf(stderr, "fail\n");
} else {
result = si1 - si2;
fprintf(stdout, "result = %d\n", result);
}
}

{ // 无符号操作数的减法操作的先验条件测试,以保证不存在无符号回绕现象
unsigned int ui1, ui2, udiff;
ui1 = 10; ui2 = 20;
if (ui1 < ui2) { // 处理错误条件
fprintf(stderr, "fail\n");
} else {
udiff = ui1 - ui2;
fprintf(stdout, "udiff = %u\n", udiff);
}
}

{ // 后验条件测试
unsigned int ui1, ui2, udiff;
ui1 = 10; ui2 = 20;
udiff = ui1 - ui2;
if (udiff > ui1) { // 处理错误情况
fprintf(stderr, "fail\n");
}
}
}
7.4 乘法

二元运算符”*”的每个操作数都是算术类型。操作数执行普通算术转换。乘法容易产生溢出错误,因为相对较小的操作数相乘时,都可能导致一个指定的整数类型溢出。一般情况下,两个整数的操作数的积总是可以用两个操作数中较大的那个所用的位数的两倍来表示。这意味着,例如,两个8位操作数的积总是可以使用16位类表示,而两个16位操作数的积总是可以使用32位来表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
void test_integer_security_multiplication()
{
{ // 在无符号乘法的情况下,如果需要高位来表示两个操作数的积,那么结果以及回绕了
unsigned int ui1 = 10;
unsigned int ui2 = 20;
unsigned int product;

static_assert(sizeof(unsigned long long) >= 2 * sizeof(unsigned int),
"Unable to detect wrapping after multiplication");

unsigned long long tmp = (unsigned long long)ui1 * (unsigned long long)ui2;
if (tmp > UINT_MAX) { // 处理无符号回绕
fprintf(stderr, "fail\n");
} else {
product = (unsigned int)tmp;
fprintf(stdout, "product = %u\n", product);
}
}

{ // 保证在long long宽度至少是int宽度两倍的系统上,不可能产生符号溢出
signed int si1 = 20, si2 = 10;
signed int result;
static_assert(sizeof(long long) >= 2 * sizeof(int),
"Unable to detect overflow after multiplication");
long long tmp = (long long)si1 * (long long)si2;
if ((tmp > INT_MAX) || (tmp < INT_MIN)) { // 处理有符号溢出
fprintf(stderr, "fail\n");
} else {
result = (int)tmp;
fprintf(stdout, "result = %d\n", result);
}
}

{ // 一般的先验调试测试
unsigned int ui1 = 10, ui2 = 20;
unsigned int product;

if (ui1 > UINT_MAX / ui2) { // 处理无符号回绕
fprintf(stderr, "fail\n");
} else {
product = ui1 * ui2;
fprintf(stdout, "product = %u\n", product);
}
}

{ // 可以防止有符号溢出,而不需要向上强制类型转换到现有位数的两倍的整数类型
signed int si1 = 10, si2 = 20;
signed int product;

if (si1 > 0) { // si1是正数
if (si2 > 0) { // si1和si2都是正数
if (si1 > (INT_MAX / si2)) { // 处理错误情况
fprintf(stderr, "fail\n");
}
} // end if si1和si2都是正数
else { // si1是正数,si2不是正数
if (si2 < (INT_MIN / si1)) { // 处理错误情况
fprintf(stderr, "fail\n");
}
} // end if si1是正数,si2不是正数
} // end fif si1是正数
else { // si1不是正数
if (si2 > 0) { // si1不是正数,si2是正数
if (si1 < (INT_MIN / si2)) { // 处理错误情况
fprintf(stderr, "fail\n");
}
} // end if si1不是正数,si2是正数
else { // si1和si2都不是正数
if ((si1 != 0) && (si2 < (INT_MAX / si1))) { // 处理错误情况
fprintf(stderr, "fail\n");
}
} // end if si1和si2都不是正数
} // end if si1不是正数

product = si1 * si2;
fprintf(stdout, "product = %d\n", product);
}
}
7.5 除法和求余

整数相除时,”/”运算符的结果是代数商的整数部分,任何小数部分都被丢弃,而”%”运算符的结果是余数。这通常称为向零截断(truncation toward zero)。在这两种运算中,如果第二个操作数的值是0,则该行为是未定义的。无符号整数除法不可能产生回绕,因为商总是小于或等于被除数。但并不总是显而易见的是,有符号整数除法也可能导致溢出,因为你可能认为商数始终小于被除数。然而,补码的最小值除以-1时会出现整数溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void test_integer_security_division_remainder()
{
// 先验条件:可以通过检查分子是否为整数类型的最小值以及检查分母是否为-1来防止有符号整数除法溢出的发生
// 只要确保除数不为0,就可以保证不发生除以零错误
signed long sl1 = 100, sl2 = 5;
signed long quotient, result;

// 此先验条件也可测试余数操作数,以保证不可能有一个除以零错误或(内部)溢出错误
if ((sl2 == 0) || ((sl1 == LONG_MIN) && (sl2 == -1))) { // 处理错误情况
fprintf(stderr, "fail\n");
} else {
quotient = sl1 / sl2;
result = sl1 % sl2;
fprintf(stdout, "quotient = %ld, result = %ld\n", quotient, result);
}
}
7.6 求负

对一个补码表示的有符号的整数求反,也可能产生一个符号错误,因为有符号整数类型的可能值范围是不对称的。

7.7 移位

此操作包括左移位和右移位。移位会在操作数上执行整数提升,其中每个操作数都具有整数类型。结果类型是提升后的左操作数类型。移位运算符右边的操作数提供移动的位数。如果该数值为负值或者大于或等于结果类型的位数,那么该行为是未定义的。在几乎所有情况下,试图移动一个负的位数或试图移动比操作数中存在的位数更多的位都表明一个错误(逻辑错误)。这与溢出是不同的,后者是一个表示不足。不要移动一个负的位数或移动比操作数中存在的位数更多的位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void test_integer_security_shift()
{
{ // 消除了无符号整数左移位操作造成的未定义行为的可能性
unsigned int ui1 = 1, ui2 = 31;
unsigned int uresult;

if (ui2 >= sizeof(unsigned int) * CHAR_BIT) { // 处理错误情况
fprintf(stderr, "fail\n");
} else {
uresult = ui1 << ui2;
fprintf(stdout, "uresult = %u\n", uresult);
}
}

{
int rc = 0;
//int stringify = 0x80000000; // windows/liunx will crash in sprintf function
unsigned int stringify = 0x80000000;
char buf[sizeof("256")] = {0};
rc = sprintf(buf, "%u", stringify >> 24);
if (rc == -1 || rc >= sizeof(buf)) { // 处理错误
fprintf(stderr, "fail\n");
} else {
fprintf(stdout, "value: %s\n", buf); // 128
}
}
}
---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。