答案就是:系统把这些函数实现都做到了名为 libc.so.6 的库文件中去了 , 在没有特别指定时 , gcc 会到系统默认的搜索路径 /usr/lib64 下进行查找 , 也就是链接到 libc.so.6 库函数中去 , 这样就有函数 printf 的实现了,而这也就是链接的作用 。
文章插图
而函数库一般分为静态库和动态库两种:
- 静态库是指在编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不需要库文件了 。在 Linux 中静态库一般以 .a 作为后缀 。
- 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时链接文件加载库,这样就可以节省系统的开销 。在 Linux 中动态库一般以 .so 作为后缀 。
有关动态库和静态库的详细介绍,将在下文进行具体讲解 。
1.3.2.5 总结最后 , 通过一张图来总结一下上述流程:
文章插图
在 Linux 下使用 GCC 编译器编译单个文件十分简单,直接使用1.4 GCC 常用参数下面的表格中列出了一些常用的 gcc 参数,这些参数在 gcc 命令中没有位置要求,只需要编译程序的时候将需要的参数指定出来即可 。$ gcc test.c
(test.c 为要编译的 C 语言的源文件) , GCC 会自动生成文件名为 a.out 的可执行文件(也可以通过参数 -o 指定生成的文件名);也就是通过一个简单的命令就可以将上边提到的 4 个步骤全部执行完毕了;但是如果想要单步执行也是没问题的 。
gcc 编译选项解释说明-E预处理,主要是进行宏展开等步骤,生成 test.i-S编译指定的源文件,但是不进行汇编,生成 test.s-c编译、汇编源文件 , 但是不进行链接,生成 test.o-o指定链接的文件名及路径-g在编译的时候,生成调试信息,该程序可以被调试器调试-D在程序编译的时候,指定一个宏-std指定 C 方言,如 -std=c99 。gcc 默认的方言是 GNU C-l在程序编译的时候,指定使用的库(库的名字一定要掐头去尾,如 libtest.so 变为 test)-L在程序编译的时候 , 指定使用的库的路径-fpic生成与位置无关的代码-shared生成共享目标文件,通常用在建立动态库时1.4.1 指定一个宏(-D)在程序中我们可以通过使用
#define
定义一个宏,也可以通过宏控制某段代码是否能够被执行 。#include <stdio.h>int main(){int num = 60;printf("num = %d\n", num);#ifdef DEBUGprintf("定义了 DEBUG 宏, num++\n");num++;#elseprintf("未定义 DEBUG 宏, num--\n");num--;#endifprintf("num = %d\n", num);return 0;}
由于我们在程序中并没有定义 DEBUG 宏,所以第 8~9 行的代码就不会被执行:文章插图
那么如何才能够在程序中不定义 DEBUG 宏的情况下执行第 8~9 行的代码呢?答案是通过 -D 参数:
文章插图
需要注意的是,-D 参数必须在生成 test.o 前使用(链接前) 。如下所示,是无效的:
文章插图
说了这么多,-D 参数有什么用呢?下面我们简单叙述一下 -D 参数的应用场景 。
1.4.1.1 应用场景一在发布程序的时候,一般都会要求将程序中所有的 log 输出去掉,如果不去掉会影响程序的执行效率 , 很显然删除这些打印 log 的源代码是一件很麻烦的事情 , 解决方案是这样的:
- 将所有的打印 log 的代码都写到一个宏判定中,可以模仿上边的例子;
- 在调试程序的时候指定 -D,就会有 log 输出;
- 在发布程序的时候不指定 -D , log 就不会输出;
- 每个用户对应一个维护分支 , 用户 A 对应 project_1 分支包含付费功能的代码,用户 B 对应的 project_2 分支不包含付费功能的代码 。
- 当用户 B 付费订阅时,再将付费项目的代码拷贝到 project_2 中
推荐阅读
- java中GC的日志认识详解
- 我的世界指令附魔攻略(我的世界附魔1000级的指令)
- JUC中的AQS底层详细超详解
- 图文详解 微服务 Zipkin 链路追踪原理
- 唐人街探案3剧情详解_唐人街探案3讲了什么剧情
- C++之值传递&指针传递&引用传递详解
- 非常全面 Dubbo 原理和机制详解
- 未知暗殿走法路线详解(未知暗殿怎么走视频)
- JWT基础概念详解
- Go | 基本数据类型详解