前言
最近在review log模块的设计,当时实现这个模块的时候,为了在log中输出源文件名(不包含路径),使用了库函数basename。
#define log_comm(level, fmt, ...) \
do { \
if (LOG_LEVEL_##level >= log_level) { \
time_t now; \
time(&now); \
struct tm local; \
localtime_r(&now, &local); \
struct timeval tim; \
gettimeofday(&tim, NULL); \
log_write_comm(LOG_LEVEL_##level, #level":\t%04d-%02d-%02d %02d:%02d:%02d:%03d: %s:%d: " fmt, \
local.tm_year+1900, local.tm_mon+1, local.tm_mday, local.tm_hour, \
local.tm_min, local.tm_sec, tim.tv_usec/1000, \
basename(__FILE__), __LINE__, ##__VA_ARGS__); \
} \
} while (0);
每调用一次log输出都会使用同样的参数__FILE__
调用basename,产生相同的返回值,属于浪费的操作,且会影响到日志模块的执行效率。
之前听到过编译期basename函数,可以在编译期间直接将函数调用结果插入到代码中,但当时一直没有找到相关资料,这个问题昨天通过其他方式得到了解决,下面说明下解决思路。
编译器优化
开始以为像这种固定参数传入,返回值也属于固定值的函数调用,编译器能够优化掉其调用过程,直接使用返回值。为了验证这个思路,特意写了一个程序进行验证。
#include <stdio.h>
#include <libgen.h>
int main(void) {
printf("%s\n", basename(__FILE__));
return 0;
}
使用-O3的优化级别,查看生成的汇编代码:
gcc -O3 -S /home/yu/temp/base.c
cat base.s
.file "base.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "/home/yu/temp/base.c"
.section .text.unlikely,"ax",@progbits
.LCOLDB1:
.section .text.startup,"ax",@progbits
.LHOTB1:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB23:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $.LC0, %edi
call __xpg_basename
movq %rax, %rdi
call puts
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE23:
.size main, .-main
.section .text.unlikely
.LCOLDE1:
.section .text.startup
.LHOTE1:
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
basename的系统调用__xpg_basename
还是存在,编译器优化没起作用。
宏定义方法
后来找到了另外一种方法,在编译时通过指定宏定义,将短文件名传入(需要Makefile的配合)。为了演示效果,特意在编译时给源文件加入了全路径。
base: base.c
gcc -D__SHORT_FILE__=\"`basename $<`\" -S `pwd`/$<
源代码修改为
#include <stdio.h>
#include <libgen.h>
int main(void) {
printf("%s\n", __SHORT_FILE__);
return 0;
}
查看汇编代码
.file "base.c"
.section .rodata
.LC0:
.string "base.c"
.text
.globl main
.type main, @function
main:
.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
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
没了系统调用,puts的入参时是“base.c”,达到了在编译期执行basename的同等效果。
修改log宏定义
将basename(FILE) 改为 SHORT_FILE,log模块关于basename调用的优化完毕。