You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Clang对两种goto表达式生成不同汇编代码的原因探究

Clang中两种goto间接跳转写法的代码生成差异原因

我发现clang(版本clang-1400.0.29.202)对goto *&&label和goto *(void*)&&label生成的汇编代码存在明显差异:前者生成预期的直接跳转至标签的指令,后者则先将标签地址加载到寄存器,再通过寄存器执行间接跳转。将void *替换为unsigned long或unsigned long long时该问题仍会复现,而gcc在两种情况下均生成直接跳转,且最新版clang在测试中结果一致。

最小复现代码

void run(long *x) {
    if (x[2]) {
#if X
        goto *&&end;
#else
        goto *(void*)&&end;
#endif
    }
beg:
    x[4] = x[2] ? (long)&&beg : (long)&&end;
end:
    if (x[3])
        goto *(void *)x[4];
}

汇编代码差异

diff -u <(cc -DX=1 -O3 -S -o- run.c) <(cc -DX=0 -O3 -S -o- run.c) 
--- /dev/fd/63  2022-11-04 15:05:34.000000000 -0700
+++ /dev/fd/62  2022-11-04 15:05:34.000000000 -0700
@@ -11,25 +11,24 @@
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movq    16(%rdi), %rax
+   leaq    Ltmp0(%rip), %rcx       <---- 将标签地址加载到rcx
    testq   %rax, %rax
-   jne LBB0_2
-Ltmp0:                                  ## Block address taken
+   je  LBB0_1
+## %bb.5:
+   jmpq    *%rcx                    <---- 通过寄存器执行间接跳转
+Ltmp1:                                  ## Block address taken
 LBB0_1:
    testq   %rax, %rax
-   leaq    Ltmp1(%rip), %rcx
-   leaq    Ltmp0(%rip), %rdx
+   leaq    Ltmp1(%rip), %rdx
    cmoveq  %rcx, %rdx
    movq    %rdx, 32(%rdi)
-LBB0_2:
-   movq    24(%rdi), %rcx
-   .p2align    4, 0x90
-Ltmp1:                                  ## Block address taken
-LBB0_3:                                 ## =>This Inner Loop Header: Depth=1
-   testq   %rcx, %rcx
-   je  LBB0_5
-## %bb.4:                               ##   in Loop: Header=BB0_3 Depth=1
+Ltmp0:                                  ## Block address taken
+LBB0_2:                                 ## =>This Inner Loop Header: Depth=1
+   cmpq    $0, 24(%rdi)
+   je  LBB0_4
+## %bb.3:                               ##   in Loop: Header=BB0_2 Depth=1
    jmpq    *32(%rdi)
-LBB0_5:
+LBB0_4:
    popq    %rbp
    retq
    .cfi_endproc

差异产生的原因

这本质是Clang对两种写法的语义处理和优化路径不同:

  • 对于goto *&&end:这种写法直接使用标签地址常量作为间接跳转的操作数,Clang的优化器能直接识别出这是一个编译期确定的固定标签地址,因此会将其优化为等价的直接跳转指令(如jne LBB0_2),完全跳过间接跳转的流程。
  • 对于goto *(void*)&&end:这里的强制类型转换会让Clang前端将该表达式视为一个通用指针值,而非原生的标签地址常量。即便这个指针的值在编译期是确定的,Clang的优化逻辑中,经过类型转换的表达式会被归类为“运行时可计算的指针”,而非编译期可见的标签常量,因此会生成间接跳转的代码:先把标签地址加载到寄存器,再通过寄存器完成跳转。

简言之,Clang对原生标签地址和经过类型转换的标签地址采取了不同的优化策略,前者能被优化为直接跳转,后者则会被当作普通指针处理,生成间接跳转指令。

内容的提问来源于stack exchange,提问作者nikita

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 12:30:58