0%

位域左移溢出问题

上一周,我在调试项目性能指标时遇到了一个问题,支持的节点数在到达60718644时,程序就出错了。由于节点索引是一个27位的结构体位域,当值为60718644时,最高位为1,此时,代码中有一个转换函数,作了对位域的左移操作并赋给一个更大空间类型的变量,使得最后的结果不是预期的。show u code:

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

#include <stdio.h>

typedef union src_s {
unsigned int rawdata;
struct {
unsigned int type: 1,
id: 4,
index: 27;

};
} src_t;

typedef union dst_s {
unsigned int rawdata[2];
struct {
unsigned : 28;
unsigned high: 4;
unsigned int data;
};
} dst_t;

int main(void)
{
src_t data;
data.type = 0;
data.id = 0;
data.index = 67108869; // 最高位当作符号位了?

printf("base rawdata 0x%x index %x\r\n", data.rawdata, data.index);
//unsigned long long result = ((unsigned long long)data.index << 5) + 9;
unsigned long long result = (data.index << 5) + 9;
printf("result %llx %llu high32 %llx \r\n", result, result, result>>32);

dst_t dst;
dst.data = result;
dst.high = result>>32;
printf("result [0] 0x%x [1] 0x%x\r\n", dst.rawdata[0], dst.rawdata[1]);

return 0;
}


程序执行结果如下:

1
2
3
4
5
6

Keep:test keep$ ./a
base rawdata 0x80000000 index 4000000
result ffffffff80000009 18446744071562067977 high32 ffffffff
result [0] 0xf0000000 [1] 0x80000009

(data.index << 5) + 9;运算的结果 并不是预期的 80000009,而是高位全为f的ffffffff80000009。 此时如果对data.index进行强转,再左移则结果正确.

1
2
3
4
Keep:test keep$ ./a
base rawdata 0x80000000 index 4000000
result 80000009 2147483657 high32 0
result [0] 0x0 [1] 0x80000009

可见,编译器把位域的值强转为了一个有符号的数进行左移,然后赋值给一个更大地址空间的类型。

通过代码汇编可以看出具体实现:

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

unsigned long a = data.index << 5;
2f: 8b 45 f8 movl -8(%rbp), %eax
32: c1 e8 05 shrl $5, %eax
35: c1 e0 05 shll $5, %eax
38: 48 63 c8 movslq %eax, %rcx // 高位符号扩展 赋值
3b: 48 89 4d f0 movq %rcx, -16(%rbp)



; unsigned a = data.index << 5;
2f: 8b 45 f8 movl -8(%rbp), %eax
32: c1 e8 05 shrl $5, %eax
35: c1 e0 05 shll $5, %eax
38: 89 45 f4 movl %eax, -12(%rbp) // 直接赋值



; unsigned long a = (unsigned long)data.index << 5;
2f: 8b 45 f8 movl -8(%rbp), %eax
32: c1 e8 05 shrl $5, %eax
35: 89 c0 movl %eax, %eax
37: 89 c1 movl %eax, %ecx
39: 48 c1 e1 05 shlq $5, %rcx // 逻辑左移
3d: 48 89 4d f0 movq %rcx, -16(%rbp)

针对位域左移,赋值给更大地址空间的情况,最好进行类型强转一下再左移。

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。