发现问题
- 最近在做gcc plugin和llvm pass相关的东西,偶然之间发现一个奇怪的问题,问题代码如下
#include <stdio.h>
inline void nothing() {
printf("nothing \n");
}
int main() {
nothing();
return 0;
}
- 编译时报错如下:
/usr/bin/ld: /tmp/ccGjvgiZ.o: in function main': csdn.c:(.text+0xa): undefined reference to
nothing’
collect2: 错误:ld 返回 1· - 其实这和我们平常的代码没什么不同,只是加了个inline而已。然后我就尝试编译出汇编代码来看看有什么问题,汇编代码如下:
.file "csdn.c"
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
call nothing
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (GNU) 7.3.0"
.section .note.GNU-stack,"",@progbits
- 我们可以发现在汇编代码中根本没有nothing()这个函数的定义,但是却在main函数中直接call了nothing这个函数,所以肯定后面会报undefined reference的错误。为了印证这个观点,我编译生成了.o文件并通过nm查看符号信息:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5a6602102cf341eab0e74efa8e545a58.png)
可以看到,nothing是一个外部符号,而我们又没有外部的定义,所以肯定会报undefined reference错误。
解决问题
加static关键字
- static关键字的作用有:将局部变量的生命周期延长、将成员函数(变量)的作用域限制在本文件等所以我们通过static关键字,显示的把函数的作用域限制在本文件,也就会限制它生成的符号为内部符号。
- C语言代码如下
#include <stdio.h>
inline static void nothing() {
printf("nothing \n");
}
int main() {
nothing();
return 0;
}
- 符号如下:可以看到nothing已经变成了本地实现的标识
t
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/11b3cb5e98c14907887388c25cae6db0.png)
- 我们也的确能够正确的编译出可执行文件如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fda6b09ebde64ddc8567a0f187ba29a8.png)
存在的问题
- 我们从加static关键字方法编译得到的.s文件中可以看到,其实nothing是并没有被内联展开的,汇编代码如下:
.file "csdn.c"
.text
.section .rodata
.LC0:
.string "nothing "
.text
.type nothing, @function
nothing:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size nothing, .-nothing
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
call nothing
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (GNU) 7.3.0"
.section .note.GNU-stack,"",@progbits
- 但是我们加入inline关键字的目的,肯定是想要内联展开,减少频繁的函数调用的开销,所以有了下面的方法。
attribute((always_inline))编译属性
- 往inline函数中加入__attribute__((always_inline))编译属性,这会告诉编译器必须将该函数内联展开。
- 源码如下:
#include <stdio.h>
#define FORCE_FUNCTION __attribute__((always_inline))
FORCE_FUNCTION inline void nothing()
{
printf("nothing \n");
}
int main() {
nothing();
return 0;
}
- 得到的符号如下:
可以看到已经没有了nothing符号
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/05a57a93c4c04799976d48a84eb902c7.png)
- 而且生成的.s文件也可以看到inline函数被正确的内联展开了,汇编代码如下
.file "csdn.c"
.text
.section .rodata
.LC0:
.string "nothing "
.text
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (GNU) 7.3.0"
.section .note.GNU-stack,"",@progbits
参考大佬分享