如何让GCC生成PIC代码时无需通过GOT访问函数局部常量?
问题背景
当使用GCC生成位置无关代码(PIC)时,对于代码中的静态常量数组o,编译器会强制通过全局偏移表(GOT)进行访问,但实际上这种访问并非必要。相关C代码、生成的汇编片段如下:
原始C代码片段
extern const long long *tget(void); static const signed long long o[2] = { -1, 1 }; long long tmogrify(long long t) { long long l; const signed long long *op; long long u = t; const long long *lp = tget(); loop: l = *lp; op = o; if (l < 0) { l = -l; ++op; } // 后续逻辑 }
GCC生成的SPARC汇编(相关片段)
.section "rodata" .align 8 .type o , @object .size o , 16 o: .long -1 .long -1 .long 0 .long 1 .section "text" .align 4 .LGETPC0: retl add %o7, %l7, %l7 .align 4 .globl tmogrify .type tmogrify , @function .proc 05 tmogrify: !#PROLOGUE# 0 save %sp, -104, %sp sethi %hi(_GLOBAL_OFFSET_TABLE_-4), %l7 call .LGETPC0 add %l7, %lo(_GLOBAL_OFFSET_TABLE_+4), %l7 call tget, 0 nop mov %i0, %o2 mov %i1, %o3 .L2: sethi %hi(o), %g2 ldd [%o0], %o4 or %g2, %lo(o), %g1 cmp %o4, 0 bge .L3 ld [%l7+%g1], %g2 // 此处通过GOT访问o subcc %g0, %o5, %o5 add %g2, 8, %g2 subx %g0, %o4, %o4 .L3:
GCC生成的i386汇编(相关片段)
.section .rodata .align 8 .type o , @object .size o , 16 o: .quad -1 .quad 1 .text .globl tmogrify .type tmogrify , @function tmogrify: // 省略前序指令 call .L6 .L6: pop ebx add ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_+(.-.L6) mov esi, DWORD PTR 8[ebp] mov edi, DWORD PTR 12[ebp] call tget@PLT mov DWORD PTR -20[ebp], eax .L2: mov eax, DWORD PTR -20[ebp] mov ecx, DWORD PTR 4[eax] mov edx, DWORD PTR [eax] test ecx, ecx lea eax, o@GOTOFF[ebx] // 此处通过GOT访问o mov DWORD PTR -16[ebp], eax jns .L3 neg edx adc ecx, 0 add eax, 8 neg ecx mov DWORD PTR -16[ebp], eax .L3:
完整测试模块代码:
extern long long tmogrify(long long); extern const long long *tget(void); static const signed long long o[2] = { -1, 1 }; long long tmogrify(long long t) { long long l; const signed long long *op; long long u = t; const long long *lp = tget(); loop: asm volatile("nop"); l = *lp; op = o; if (l < 0) { l = -l; ++op; } asm volatile("nop"); if (l && (t >= l)) { asm volatile("nop"); u += *op; asm volatile("nop"); ++lp; goto loop; } return (u); }
提问
如何在不脱离标准C范畴(无未定义行为/实现定义行为)的前提下,让GCC优化该代码,避免通过GOT访问函数局部常量数组o?
解决方案
方法1:直接使用字面量替代数组访问
既然数组o的元素是固定的-1和1,完全可以去掉数组,直接在代码中根据条件选择对应常量值。这种方式让编译器无需访问内存,自然不会生成GOT相关指令。
修改后的代码:
extern const long long *tget(void); long long tmogrify(long long t) { long long l; long long u = t; const long long *lp = tget(); loop: l = *lp; long long op_val = -1LL; // 对应o[0] if (l < 0) { l = -l; op_val = 1LL; // 对应o[1] } if (l && (t >= l)) { u += op_val; ++lp; goto loop; } return u; }
方法2:用C11 constexpr强化常量属性(适用于C11及以上)
如果需要保留数组形式,可使用constexpr声明数组,明确告诉编译器这是编译期常量,使其直接内联元素值而非通过内存寻址访问。不过这种方法在部分GCC版本中仍可能生成GOT访问,不如方法1彻底。
示例代码:
extern const long long *tget(void); static constexpr signed long long o[2] = { -1, 1 }; // C11标准 long long tmogrify(long long t) { long long l; const signed long long *op; long long u = t; const long long *lp = tget(); loop: l = *lp; op = o; if (l < 0) { l = -l; ++op; } if (l && (t >= l)) { u += *op; ++lp; goto loop; } return u; }
方法3:宏定义封装常量(适用于所有C标准)
如果需要在多处复用这些常量,可使用宏封装,效果和直接使用字面量一致:
#define O_NEG (-1LL) #define O_POS 1LL extern const long long *tget(void); long long tmogrify(long long t) { long long l; long long u = t; const long long *lp = tget(); loop: l = *lp; long long op_val = O_NEG; if (l < 0) { l = -l; op_val = O_POS; } if (l && (t >= l)) { u += op_val; ++lp; goto loop; } return u; }
原理说明
GCC生成PIC代码时,默认会通过GOT访问全局/静态变量的地址以保证位置无关性。但如果代码中无需获取变量地址,直接使用编译期可确定的常量值,编译器会将常量直接嵌入指令中,完全跳过内存寻址操作,自然也就避免了GOT访问。方法1从根本上移除了数组指针的使用,是最可靠的解决方案。
内容的提问来源于stack exchange,提问作者mirabilos
相关产品推荐
相关产品推荐

