一个 C 语言 uint64_t 变量移位赋值问题

碰到一个问题:

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint64_t a = 0;
    uint8_t b = 0xff;

    a = b << 24;
    printf("a = 0x%lx\\n", a);
}

乍一看, a 的值应该是 0x0000 0000 ff00 0000,

然而在 X86_64 gcc 上 实际编译后,却是输出 0xffff ffff ff00 0000。

这里的问题是,b 的长度为 8 位,在 C 语言标准中,对于移位长度超过变量本身长度的操作是未定义的,所以可能导致不可预期的结果。

参考【https://stackoverflow.com/questions/10499104/is-shifting-more-than-32-bits-of-a-uint64-t-integer-on-an-x86-machine-undefined

具体到汇编指令上,可以看到,objdump -dS 反汇编后的结果如下:

int main()
{
    1149:       f3 0f 1e fa             endbr64
    114d:       55                      push   %rbp
    114e:       48 89 e5                mov    %rsp,%rbp
    1151:       48 83 ec 10             sub    $0x10,%rsp
    uint64_t a = 0;
    1155:       48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
    115c:       00
    uint8_t b = 0xff;
    115d:       c6 45 f7 ff             movb   $0xff,-0x9(%rbp)

    a = b << 24;
    1161:       0f b6 45 f7             movzbl -0x9(%rbp),%eax
    1165:       c1 e0 18                shl    $0x18,%eax
    1168:       48 98                   cltq
    116a:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    printf("a = 0x%lx\\n", a);
    116e:       48 8b 45 f8             mov    -0x8(%rbp),%rax
    1172:       48 89 c6                mov    %rax,%rsi
    1175:       48 8d 3d 88 0e 00 00    lea    0xe88(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    117c:       b8 00 00 00 00          mov    $0x0,%eax
    1181:       e8 ca fe ff ff          callq  1050 <printf@plt>
    1186:       b8 00 00 00 00          mov    $0x0,%eax

}

可以看到,在使用 shl 指令将 eax 寄存器的值移位 24 位后,又调用 cltq 指令,该指令会对 将通过符号扩展的方式将 eax —> rax,也就是 32 位扩展为 64 位。这时候由于 eax 最高位为 1, 故被扩展为 0xffff ffff ff00 0000。

至于具体为什么这么编译,还有待探究。

继续测试:

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint64_t a = 0;
    uint8_t b = 0xff;
    uint32_t c = 0xff;

    a = b << 24;
    printf("a = 0x%lx\\n", a);

    a = b << 3;
    printf("a = 0x%lx\\n", a);

    a = b;
    printf("a = 0x%lx\\n", a);

    a = c << 24;
    printf("a = 0x%lx\\n", a);

    a = c << 32;
    printf("a = 0x%lx\\n", a);
}

反汇编后为:

int main()
{
    1149:       f3 0f 1e fa             endbr64
    114d:       55                      push   %rbp
    114e:       48 89 e5                mov    %rsp,%rbp
    1151:       48 83 ec 10             sub    $0x10,%rsp
    uint64_t a = 0;
    1155:       48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
    115c:       00
    uint8_t b = 0xff;
    115d:       c6 45 f3 ff             movb   $0xff,-0xd(%rbp)
    uint32_t c = 0xff;
    1161:       c7 45 f4 ff 00 00 00    movl   $0xff,-0xc(%rbp)

    a = b << 24;
    1168:       0f b6 45 f3             movzbl -0xd(%rbp),%eax
    116c:       c1 e0 18                shl    $0x18,%eax
    116f:       48 98                   cltq
    1171:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    printf("a = 0x%lx\\n", a);
    1175:       48 8b 45 f8             mov    -0x8(%rbp),%rax
    1179:       48 89 c6                mov    %rax,%rsi
    117c:       48 8d 3d 81 0e 00 00    lea    0xe81(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    1183:       b8 00 00 00 00          mov    $0x0,%eax
    1188:       e8 c3 fe ff ff          callq  1050 <printf@plt>

    a = b << 3;
    118d:       0f b6 45 f3             movzbl -0xd(%rbp),%eax
    1191:       c1 e0 03                shl    $0x3,%eax
    1194:       48 98                   cltq
    1196:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    printf("a = 0x%lx\\n", a);
    119a:       48 8b 45 f8             mov    -0x8(%rbp),%rax
    119e:       48 89 c6                mov    %rax,%rsi
    11a1:       48 8d 3d 5c 0e 00 00    lea    0xe5c(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    11a8:       b8 00 00 00 00          mov    $0x0,%eax
    11ad:       e8 9e fe ff ff          callq  1050 <printf@plt>

    a = b;
    11b2:       0f b6 45 f3             movzbl -0xd(%rbp),%eax
    11b6:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    printf("a = 0x%lx\\n", a);
    11ba:       48 8b 45 f8             mov    -0x8(%rbp),%rax
    11be:       48 89 c6                mov    %rax,%rsi
    11c1:       48 8d 3d 3c 0e 00 00    lea    0xe3c(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    11c8:       b8 00 00 00 00          mov    $0x0,%eax
    11cd:       e8 7e fe ff ff          callq  1050 <printf@plt>

    a = c << 24;
    11d2:       8b 45 f4                mov    -0xc(%rbp),%eax
    11d5:       c1 e0 18                shl    $0x18,%eax
    11d8:       89 c0                   mov    %eax,%eax
    11da:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    printf("a = 0x%lx\\n", a);
    11de:       48 8b 45 f8             mov    -0x8(%rbp),%rax
    11e2:       48 89 c6                mov    %rax,%rsi
    11e5:       48 8d 3d 18 0e 00 00    lea    0xe18(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    11ec:       b8 00 00 00 00          mov    $0x0,%eax
    11f1:       e8 5a fe ff ff          callq  1050 <printf@plt>

    a = c << 32;
    11f6:       ba 20 00 00 00          mov    $0x20,%edx
    11fb:       8b 45 f4                mov    -0xc(%rbp),%eax
    11fe:       89 d1                   mov    %edx,%ecx
    1200:       d3 e0                   shl    %cl,%eax
    1202:       89 c0                   mov    %eax,%eax
    1204:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    printf("a = 0x%lx\\n", a);
    1208:       48 8b 45 f8             mov    -0x8(%rbp),%rax
    120c:       48 89 c6                mov    %rax,%rsi
    120f:       48 8d 3d ee 0d 00 00    lea    0xdee(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    1216:       b8 00 00 00 00          mov    $0x0,%eax
    121b:       e8 30 fe ff ff          callq  1050 <printf@plt>
    1220:       b8 00 00 00 00          mov    $0x0,%eax
}

可以看出:

  1. 对于 8 位到 64 位的移位赋值,即使是移位数小于 变量长度,也依然会调用 cltq;
  2. 如果是单纯的赋值,从 8 位到 64 位并不会调用 cltq;
  3. 但如果是 32 到 64 位的移位赋值,不管移位长度是否超过变量长度,都不会调用 cltq。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值