gcc/g++使用自定义的同名函数覆盖C库函数

前言

其实这问题以前就想过,每次都没有深究到底。原因在于无论是哪本Linux C编程的书,基本都会使用可靠语义的signal函数来覆盖相应的库函数。
比如在《Unix网络编程》中是如下定义的:对被SIGALRM以外的信号中断的系统调用自动重启,并且不阻塞其他的信号。(虽然信号掩码是空,但是POSIX保证被捕获的信号在其信号处理函数运行期间总是阻塞的)
但是书中并未提及具体怎么覆盖库函数的定义, 毕竟对于不同的编译器来说做法不同,这里仅针对gcc而言。

静态链接VS动态链接

注:想直接看结论可以忽略本部分的内容。
简单来说,链接即把可重定位目标文件组合成最终的可执行目标文件(下文均以“程序”一词代替)。而可重定向目标文件中有一个符号表,其中有一些未被解析的符号引用,比如源文件中声明了一个函数,但未给出其具体定义。
这时链接器就会在其他目标文件中查找是否有对应的符号定义。
比如有下列源文件

// main.c
void foo();
int main() {
    foo();
    return 0;
}

可以看到main.c中只包含foo的声明,而没有定义,因此直接编译main.c会报错。如果提供一个foo.c编译而成的静态库libfoo.a(编译过程如下)

// foo.c
#include <stdio.h>
void foo() { puts("foo"); }
$ gcc -c foo.c 
$ ar -rcs libfoo.a foo.o

那么就可以进行链接了,gcc编译过程如下

$ gcc main.c libfoo.a

这个过程中,首先编译源码main.c得到一个可重定位目标文件,其中符号表中包含未解析的符号引用foo,此时链接器记录下来,然后在后面的可重定位目标文件(静态库)中查找是否含有foo的符号定义,若找到则匹配,之后不再查找定义。
比如现在给出另一个定义了foo函数的库libfoo2.a,源码如下,编译过程同libfoo.a

// foo2.c
#include <stdio.h>
void foo() { puts("foo2"); }

现在分别按照不同的顺序进行链接,运行程序,观察结果

$ gcc main.c libfoo.a libfoo2.a 
$ ./a.out 
foo
$ gcc main.c libfoo2.a libfoo.a 
$ ./a.out 
foo2

印证了刚才的结论,不存在什么后面的覆盖了前面的行为。
OK,那么问题来了,stdio.h中只有puts函数的声明,却没有定义。这就是动态库了,可以用ldd命令查看程序调用的动态库

$ ldd a.out 
    linux-vdso.so.1 =>  (0x00007fff78b02000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe770f5a000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fe771324000)

libc.so.6即C标准库(动态库),放在特定目录下,然后通过gcc的-l选项指定链接的动态库,符号定义的具体内容不会放入最终的程序中,而是记录符号定义所在动态库路径,在程序运行时进行查找。优点是简化了程序体积,缺点是第一次调用动态链接的函数时会比较费时。
链接时,C标准库不需要额外选项就可以进行动态链接,只有特地加上-static选项时才不进行动态链接,而是去静态链接C标准库的静态库。

更多细节部分可以参考《深入理解计算机系统》(即CSAPP)第七章
库函数一般是进行动态链接

如何覆盖库函数

使用gcc选项no-builtin,在gcc的manpage中可以看到相关说明(这里不贴出来了),大致就是gcc对于某些内置函数会有底层优化,比自己实现同样的功能,能产生体积更小,速度更快的底层代码。开启这个选项,则默认不使用系统的优化函数,而使用自定义的函数。
比如我们来自定义printf(只是示例,并不是还原功能)

// printf.c
#include <unistd.h>
#include <string.h>

int printf(const char* format, ...) {
    write(STDOUT_FILENO, "my printf\n", 10);
    write(STDOUT_FILENO, format, strlen(format));
    return 0;
}
// main.c
#include <stdio.h>

int main() {
    printf("hello\n");
    return 0;
}

观察不同编译方式下的结果

$ gcc -c printf.c 
$ gcc main.c printf.o -fno-builtin
$ ./a.out 
my printf
hello
$ gcc main.c printf.o
$ ./a.out 
hello

对于像signal这样的未给予优化的函数(毕竟仅仅是系统调用的包装),直接静态链接即可。

// signal.c
#include <stdio.h>
#include <signal.h>  // 假设signal函数的定义调用了sigaction等函数

typedef void Sigfunc(int);

Sigfunc* signal(int signo, Sigfunc* func) {
    printf("%d\n", signo);
    return func;
}
// main.c
#include <signal.h>

int main() {
    signal(SIGINT, SIG_DFL);
    return 0;
}
$ gcc -c signal.c 
$ gcc main.c signal.o
$ ./a.out 
2

另外,还可以使用宏定义的方式来替换库函数,比如

#define printf my_printf
int my_printf(const char* format, ...)
{
    // 具体实现
}

但不推荐这种做法,因为宏替换是在编译之前进行的,最终程序中的符号信息并不是printf而是my_printf,而且stdio.h中对printf的声明也失去了意义,因为实际调用的是my_printf
使用前一种方法,就可以在不需要修改现有代码的基础上,调用自己对库函数的重写版本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容