位域左移溢出问题

328 阅读1分钟

上一周,我在调试项目性能指标时遇到了一个问题,项目支持的节点数在到达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/