请考虑以下计划:
#include <stdio.h>
typedef char*(*g)(const char*)
__attribute__((__access__(__read_only__, 1)))
;
char *h(const char*)
__attribute__((__access__(__read_only__, 1)))
;
// not from string.h, declared here!
char *strdup(const char*);
int main() {
g f = h; // change h to strdup to see the effect
char c[2] = "0";
f(c);
printf("%c\n", c[0]);
return 0;
}
我想做的是说服 GCC 在调用
c[0]
时不需要从内存中加载 printf()
的值。
我也对适用于函数指针的解决方案感兴趣。
由于
const
很容易被抛弃,所以我对编译器不会使用这些信息进行优化已经不那么惊讶了。所以我尝试了 access 属性。但没有运气。
但是想象一下当我将
g f = h
更改为 g f = strdup
时我惊讶的表情。
赋值
h
时生成的代码如下:
call h
movsbl 14(%rsp), %esi // loaded from memory
movl $.LC0, %edi
xorl %eax, %eax
call printf
虽然分配
strdup
时生成的代码也是我也希望看到的 h
:
call strdup
movl $48, %esi // the "0" is directly written to register
movl $.LC0, %edi // because here GCC recognizes that the
xorl %eax, %eax // content did not change
call printf
请注意,当我在该文件中使用
strdup
的自定义声明(而不是 string.h 中的声明)时,效果也可见。
编译器突然意识到函数调用不会改变
c
中的内容,并直接用printf()
调用0x48
,而不是从内存中加载值。
看起来编译器中存在一些针对
strdup
的硬编码优化。
问题:但是我如何说服 GCC 对我的函数进行相同的优化
h
(最好也针对通过函数指针调用的函数)?
更新:由于一些评论者对此感到困惑,该示例使用单个
char
而不是正确的字符串来说明效果,因此我相应地更改了示例。此外,我强调了主要问题。
更新2:将godbolt.org的结果复制到问题中,因为不是每个人都想点击链接并尝试一下。
这是因为编译器将
strdup
识别为特殊的库函数情况,因此基于此假设内联调用。而您的自定义函数具有外部链接并且无法内联。如果您提供函数定义以及有保证的内联,您将看到与 strdup
: 相同的行为
__attribute__((always_inline)) static inline char *h (const char* x)
{
/* do something meaningful here */
}
由于定义在同一翻译单元中可见,我得到的
movl $48, %esi
与您对 strdup
所做的相同。据说这是因为当函数内联时,编译器可以知道函数未触及寄存器。