上一周,我在调试项目性能指标时遇到了一个问题,项目支持的节点数在到达60718644时,程序就异常出错了。原因是节点索引是一个27位的结构体位域,当值为60718644时,最高位为1,此时,代码中有一个转换函数,作了对位域的左移操作并赋给一个更大空间类型的变量,使得最后的结果不是预期的。show u code:
#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 = 67108864;
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;
}
程序执行结果如下:
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进行强转,再左移则结果正确。
Keep:test keep$ ./a
base rawdata 0x80000000 index 4000000
result 80000009 2147483657 high32 0
result [0] 0x0 [1] 0x80000009
可见,编译器把位域的值高位当作了符号位,转为了一个有符号的数进行左移,然后赋值给一个更大地址空间的类型。
通过代码汇编可以看出具体实现:
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
github博客: fishmwei.github.io/