如何在Clang中通过内联汇编跳转至静态常量函数指针指向的函数?
需求与问题
已知静态常量函数指针定义如下:
#include <stdio.h> static int (* const puts_ptr)(char const*) = &puts;
需要通过内联汇编,以puts_ptr为输入生成jmp puts指令。
GCC下的可行实现
以下代码在GCC中可以正常工作:
#include <stdio.h> static int (* const puts_ptr)(char const*) = &puts; void callputs(char const*X){ __asm volatile ("jmp %c0\n" : : "i"(puts_ptr)); __builtin_unreachable(); }
它利用了i(立即数)约束和c(无美元符号输出)修饰符,但该写法无法在Clang上编译通过。
Clang的兼容性问题
Clang甚至无法处理以下简化写法:
- 使用
i约束直接传入puts:
__asm volatile ("jmp %c0\n" : : "i"(puts));
- 使用
r约束传入puts:
__asm volatile ("jmp %0\n" : : "r"(puts));
注:GCC会为第二种写法生成地址加载和寄存器间接跳转,但Clang仍无法处理。
不过在识别静态常量已知值方面,GCC和Clang表现一致,例如:
static int const x = 42; // 也可使用enum保证常量表达式特性 static int const*const p = &x; // 无法使用enum int retint_x(void){ __asm volatile ("ret;" : : "a"(x)); __builtin_unreachable(); } // 返回42; ✓; 无需从x加载 int *retptr(void){ __asm volatile ("ret;" : : "a"(p)); __builtin_unreachable();} // 返回&x ✓; 无需从p加载
编译输出:
0000000000000000 <retint_x>: 0: b8 2a 00 00 00 mov $0x2a,%eax 5: c3 retq 0000000000000006 <retptr>: 6: b8 00 00 00 00 mov $0x0,%eax 7: R_X86_64_32 .rodata b: c3 retq
限制条件:不能使用
constexpr(需适配纯C宏)或#define(常量由宏输出,最多使用仅支持普通整数的enum),仅要求开启优化时编译器能识别静态常量的值。
Clang适配方案
要在Clang中实现生成jmp puts指令的需求,可以采用以下两种方案:
方案1:利用%p修饰符(贴合puts_ptr输入需求)
#include <stdio.h> static int (* const puts_ptr)(char const*) = &puts; void callputs(char const*X){ __asm volatile ("jmp %p0\n" : : "i"(puts_ptr)); __builtin_unreachable(); }
%p修饰符会让Clang输出不带美元符号的符号地址,符合jmp指令对直接地址的要求;开启优化后,编译器能识别puts_ptr的静态常量特性,将其解析为puts的地址,最终生成jmp puts指令。
方案2:直接引用符号(若允许跳过puts_ptr)
如果不需要严格以puts_ptr为输入,可直接编写内联汇编:
void callputs(char const*X){ __asm volatile ("jmp puts\n"); __builtin_unreachable(); }
此方案更简洁,但未直接使用puts_ptr变量,仅适合无需绑定该变量的场景。
内容的提问来源于stack exchange,提问作者Petr Skocik
相关产品推荐
相关产品推荐

