当前位置: 首页 > news >正文

常州做网站的 武进天津响应式网页建设公司

常州做网站的 武进,天津响应式网页建设公司,麻将app开发公司,企业网站的宣传功能体现在哪里文章目录 GCC简介单个文件编译过程预处理(Preprocessing)编译(Compilation)汇编(Assembly)链接(Linking) 多文件编译过程头文件搜索路径三种不推荐的方法两种推荐的方法 库文件静态库文件创建和使用静态库链接顺序 动态库文件创建和使用动态库 Warning编译选项调试信息(-g)编译… 文章目录 GCC简介单个文件编译过程预处理(Preprocessing)编译(Compilation)汇编(Assembly)链接(Linking) 多文件编译过程头文件搜索路径三种不推荐的方法两种推荐的方法 库文件静态库文件创建和使用静态库链接顺序 动态库文件创建和使用动态库 Warning编译选项调试信息(-g)编译优化总结参考文献 GCC简介 gcc(GNU Compiler CollectionGNU编译器套件)是一个功能强大、跨平台的编译器套件用于编译C、C、Objective-C、Fortran、Ada、Go和D等多种编程语言的源代码。它是GNU项目的一部分遵循GPL(GNU General Public License)许可证因此可以自由使用和分发。gcc主要有如下特点 1、跨平台gcc可以在多种操作系统上运行包括Linux、Windows(通过MinGW)等。2、优化gcc提供了多种优化选项可以根据需要选择不同级别的优化来生成更高效率的代码。3、可扩展性gcc支持通过插件和扩展来增加新功能或支持新的编程语言。4、开源。5、多语言支持支持C、C、Objective-C、Fortran、Ada、Go和D等语言的编译。6、丰富的选项gcc提供了大量的编译选项允许开发者精细地控制编译过程包括预处理、编译、汇编和链接等各个阶段。7、调试支持gcc生成的代码可以与多种调试器(如gdb)配合使用帮助开发者定位和解决程序中的问题。8、文档和社区支持gcc拥有详细的文档和广泛的社区支持。 本文主要是介绍gcc的入门使用gcc的安装教程请参考其他文章。 检验gcc是否安装可以使用gcc --version 。如果内容是类似下面的格式则说明已经安装了gcc。 zldzld:~$ gcc --version gcc (Ubuntu 13.2.0-4ubuntu3) 13.2.0 Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.单个文件编译过程 对于经典的Hello, World程序 zldzld:~/Codes/tmp0926$ cat -n hello.c 1 #include stdio.h23 int main()4 {5 printf(Hello, World\n);6 return 0;7 }使用gcc编译单个文件非常简单只需要使用命令gcc 源文件即可比如用gcc编译上面的hello.c则用命令gcc hello.c就能编译成功gcc会生成一个a.out的可执行文件(当然也可以通过参数-o来指定生成的文件名) zldzld:~/Codes/tmp0926$ gcc hello.c zldzld:~/Codes/tmp0926$ ll total 28 drwxrwxr-x 2 zld zld 4096 Sep 27 11:16 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rwxrwxr-x 1 zld zld 15952 Sep 27 11:16 a.out* -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c zldzld:~/Codes/tmp0926$ ./a.out Hello, World# 通过 -o 参数生成可执行文件 zldzld:~/Codes/tmp0926$ gcc hello.c -o hello zldzld:~/Codes/tmp0926$ ll total 44 drwxrwxr-x 2 zld zld 4096 Sep 27 11:22 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rwxrwxr-x 1 zld zld 15952 Sep 27 11:16 a.out* -rwxrwxr-x 1 zld zld 15952 Sep 27 11:22 hello* -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c zldzld:~/Codes/tmp0926$ ./hello Hello, World上面的编译看似很简单但其实这个过程可以分为4个阶段分别是预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)。 默认情况下gcc编译不会保存中间结果我们可以通过选项-save-temps来保存中间结果。如下所示当加了-save-temps选项后除了生成最终的可执行文件a.out外还有以下几个文件a-hello.i、a-hello.s、a-hello.o这其实就是上面几个阶段生成的中间结果。其中.i文件是预处理之后产生的结果.s是编译后产生的结果.o是汇编产生的结果。 zldzld:~/Codes/tmp0926$ gcc hello.c -save-temps zldzld:~/Codes/tmp0926$ ll total 56 drwxrwxr-x 2 zld zld 4096 Sep 27 11:28 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 19640 Sep 27 11:28 a-hello.i -rw-rw-r-- 1 zld zld 1496 Sep 27 11:28 a-hello.o -rw-rw-r-- 1 zld zld 659 Sep 27 11:28 a-hello.s -rwxrwxr-x 1 zld zld 15952 Sep 27 11:28 a.out* -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c因此对于上述hello.c文件用gcc编译的过程可以用下图说明 下面将结合hello.c源程序详细讲述这4个阶段的处理过程。 预处理(Preprocessing) gcc编译过程的第一阶段就是预处理预处理过程主要处理那些源代码文件中的以#“开始的预编译指令。比如”#include、#define等主要处理规则如下 将所有的#define删除并且展开所有的宏定义。处理所有条件预编译指令比如#if、“#ifdef”、“#elif”、“#else”、“#endif”。处理#include预编译指令将被包含的文件插入到该预编译指令的位置。注意这个过程是递归进行的也就是说被包含的文件可能还包含其他文件。删除所有的注释//“和”/* */。添加行号和文件名标识以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。保留所有的#pragma编译器指令因为编译器需要使用它们。 经过预处理之后生成的.i文件不包含任何宏定义因为所有的宏已经被展开并且包含的文件也已经被插入到.i文件中。 预处理阶段是用预处理器cpp程序处理的可以用以下命令处理预处理过程。(-E表示只进行预处理)。 cpp hello.c hello.i 或者 gcc -E hello.c -o hello.i对上述的hello.c程序进行预处理 zldzld:~/Codes/tmp0926$ ll total 12 drwxrwxr-x 2 zld zld 4096 Sep 27 11:39 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c zldzld:~/Codes/tmp0926$ gcc -E hello.c -o hello1.i zldzld:~/Codes/tmp0926$ cpp hello.c hello2.i zldzld:~/Codes/tmp0926$ ll total 52 drwxrwxr-x 2 zld zld 4096 Sep 27 11:44 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 19640 Sep 27 11:43 hello1.i -rw-rw-r-- 1 zld zld 19640 Sep 27 11:44 hello2.i -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c预处理之后的结果如下 zldzld:~/Codes/tmp0926$ cat hello1.i ... extern int __uflow (FILE *); extern int __overflow (FILE *, int); # 967 /usr/include/stdio.h 3 4# 2 hello.c 2# 3 hello.c int main() {printf(Hello, World\n);return 0; }对于上述的例子可能还不好理解我们再举一个例子。 对于下面的一段程序test1.c。 zldzld:~/Codes/tmp0926$ cat -n test1.c 1 #define TEST hello,world23 /* This is a comments */4 const char str[] TEST;我们的预处理结果如下 zldzld:~/Codes/tmp0926$ cpp test1.c test1.i zldzld:~/Codes/tmp0926$ ll total 60 drwxrwxr-x 2 zld zld 4096 Sep 27 12:11 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 19640 Sep 27 11:43 hello1.i -rw-rw-r-- 1 zld zld 19640 Sep 27 11:44 hello2.i -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c -rw-rw-r-- 1 zld zld 78 Sep 27 12:09 test1.c -rw-rw-r-- 1 zld zld 165 Sep 27 12:11 test1.i zldzld:~/Codes/tmp0926$ cat test1.i # 0 test1.c # 0 built-in # 0 command-line # 1 /usr/include/stdc-predef.h 1 3 4 # 0 command-line 2 # 1 test1.cconst char str[] hello,world;从上面的结果可以看到源程序test1.c的第1行的宏定义#define TEST hello,world以及第3行的注释/* This is a comments */已经被删除了并且在源程序的第四行 const char str[] TEST;已经将该宏定义展开了。 编译(Compilation) 编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件(简单来说编译就是将预处理之后的源代码翻译成汇编代码)。这是整个构建过程的核心部分。对于编译过程gcc使用的是ccl程序来完成的。 编译过程相当于如下命令(-S表示执行编译后停止不进行汇编和链接) gcc -S hello.i -o hello.s 或者 gcc -S hello.c -o hello.s对于hello.c程序执行结果如下 zldzld:~/Codes/tmp0926$ gcc -S hello.i -o hello.s zldzld:~/Codes/tmp0926$ ll total 40 drwxrwxr-x 2 zld zld 4096 Sep 27 12:24 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c -rw-rw-r-- 1 zld zld 19640 Sep 27 11:43 hello.i -rw-rw-r-- 1 zld zld 659 Sep 27 12:24 hello.s -rw-rw-r-- 1 zld zld 78 Sep 27 12:09 test1.c如果想看详细过程还可以加上选项-v它表示显示gcc执行时的详细过程。 从上面可以看到在编译过程实际是由ccl程序执行的因此也可以直接用ccl程序执行 zldzld:~/Codes/tmp0926$ /usr/libexec/gcc/x86_64-linux-gnu/13/cc1 hello.i main Analyzing compilation unit Performing interprocedural optimizations*free_lang_data {heap 920k} visibility {heap 920k} build_ssa_passes {heap 920k} opt_local_passes {heap 1224k} remove_symbols {heap 1224k} targetclone {heap 1224k} free-fnsummary {heap 1224k}Streaming LTOwhole-program {heap 1224k} fnsummary {heap 1224k} inline {heap 1224k} modref {heap 1224k} free-fnsummary {heap 1224k} single-use {heap 1224k} comdats {heap 1224k}Assembling functions:simdclone {heap 1224k} main Time variable usr sys wall GGCphase setup : 0.00 ( 0%) 0.00 ( 0%) 0.01 ( 33%) 1819k ( 80%)phase parsing : 0.00 ( 0%) 0.01 (100%) 0.01 ( 33%) 403k ( 18%)phase opt and generate : 0.01 (100%) 0.00 ( 0%) 0.01 ( 33%) 60k ( 3%)callgraph optimization : 0.01 (100%) 0.00 ( 0%) 0.00 ( 0%) 0 ( 0%)callgraph ipa passes : 0.01 (100%) 0.00 ( 0%) 0.00 ( 0%) 4880 ( 0%)preprocessing : 0.00 ( 0%) 0.01 (100%) 0.00 ( 0%) 35k ( 2%)parser (global) : 0.00 ( 0%) 0.00 ( 0%) 0.01 ( 33%) 351k ( 15%)final : 0.00 ( 0%) 0.00 ( 0%) 0.01 ( 33%) 2120 ( 0%)TOTAL : 0.01 0.01 0.03 2283k汇编(Assembly) 汇编器就是将编译生成的汇编代码(.s)转变成机器可以执行的指令在Linux系统上一般表现为ELF目标文件(OBJ文件)(简单来说就是将汇编代码翻译成机器码)用到的汇编器工具为as。 汇编过程用到的命令如下 gcc -c hello.s -o hello.o 或者 as hello.s -o hello.o对于hello.c程序执行结果如下 zldzld:~/Codes/tmp0926$ gcc -c hello.s -o hello.o zldzld:~/Codes/tmp0926$ as hello.s -o hello2.o zldzld:~/Codes/tmp0926$ ll total 52 drwxrwxr-x 2 zld zld 4096 Sep 27 12:51 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 1376 Sep 27 12:51 hello2.o -rw-rw-r-- 1 zld zld 659 Sep 27 12:27 hello2.s -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c -rw-rw-r-- 1 zld zld 19640 Sep 27 11:43 hello.i -rw-rw-r-- 1 zld zld 1376 Sep 27 12:51 hello.o -rw-rw-r-- 1 zld zld 474 Sep 27 12:40 hello.s -rw-rw-r-- 1 zld zld 78 Sep 27 12:09 test1.c详细执行过程 链接(Linking) 链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来最终生成了可以在特定平台运行的可执行文件用到的工具为ld或collect2。 链接的命令如下 zldzld:~/Codes/tmp0926$ gcc hello.o -o hello zldzld:~/Codes/tmp0926$ ll total 68 drwxrwxr-x 2 zld zld 4096 Sep 27 13:11 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rwxrwxr-x 1 zld zld 15880 Sep 27 13:11 hello* -rw-rw-r-- 1 zld zld 1376 Sep 27 12:51 hello2.o -rw-rw-r-- 1 zld zld 659 Sep 27 12:27 hello2.s -rw-rw-r-- 1 zld zld 73 Sep 27 11:14 hello.c -rw-rw-r-- 1 zld zld 19640 Sep 27 11:43 hello.i -rw-rw-r-- 1 zld zld 1376 Sep 27 12:52 hello.o -rw-rw-r-- 1 zld zld 474 Sep 27 12:40 hello.s -rw-rw-r-- 1 zld zld 78 Sep 27 12:09 test1.c其详细执行过程 多文件编译过程 上一章节是讲的单个源文件的编译过程。在一个实际的项目中一般情况下是包含多个源文件的。本章将介绍多个文件的编译过程。 我们首先将上一章的hello.c程序改造一下该目录下有三个文件分别是hello.c、hello.h、main.c其中hello.c中定义了一个函数hello()其功能就是简单的打印字符串。hello.h头文件中就是声明了hello()函数。在main.c中则是定义了main()函数在main函数中调用了hello.c文件中的hello()函数。 zldzld:~/Codes/tmp0926$ tree . ├── hello.c ├── hello.h └── main.czldzld:~/Codes/tmp0926$ cat hello.c #include stdio.h #include hello.hvoid hello(const char *string) {printf(%s\n, string); }zldzld:~/Codes/tmp0926$ cat hello.h void hello(const char *string);zldzld:~/Codes/tmp0926$ cat main.c #include stdio.h #include hello.hint main() {hello(hello,world);return 0; }对于多个源文件的编译有两种方式第1种就是将多个源程序一起编译生成可执行程序第2种方法就是分别编译生成多个目标文件然后再将这些目标文件再链接到一起生成可执行程序。 第1种方法将多个源程序一起编译生成可执行程序。 zldzld:~/Codes/tmp0926$ gcc hello.c main.c -o hello zldzld:~/Codes/tmp0926$ ll total 36 drwxrwxr-x 2 zld zld 4096 Sep 27 13:48 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rwxrwxr-x 1 zld zld 16016 Sep 27 13:48 hello* -rw-rw-r-- 1 zld zld 99 Sep 27 13:31 hello.c -rw-rw-r-- 1 zld zld 32 Sep 27 13:31 hello.h -rw-rw-r-- 1 zld zld 88 Sep 27 13:33 main.c第2种方法分别编译生成多个目标文件然后再将这些目标文件再链接到一起生成可执行程序。 zldzld:~/Codes/tmp0926$ gcc -c hello.c -o hello.o zldzld:~/Codes/tmp0926$ gcc -c main.c -o main.o zldzld:~/Codes/tmp0926$ ll total 28 drwxrwxr-x 2 zld zld 4096 Sep 27 13:50 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 99 Sep 27 13:31 hello.c -rw-rw-r-- 1 zld zld 32 Sep 27 13:31 hello.h -rw-rw-r-- 1 zld zld 1360 Sep 27 13:49 hello.o -rw-rw-r-- 1 zld zld 88 Sep 27 13:33 main.c -rw-rw-r-- 1 zld zld 1488 Sep 27 13:50 main.o zldzld:~/Codes/tmp0926$ gcc hello.o main.o -o hello zldzld:~/Codes/tmp0926$ ll total 44 drwxrwxr-x 2 zld zld 4096 Sep 27 13:50 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rwxrwxr-x 1 zld zld 16016 Sep 27 13:50 hello* -rw-rw-r-- 1 zld zld 99 Sep 27 13:31 hello.c -rw-rw-r-- 1 zld zld 32 Sep 27 13:31 hello.h -rw-rw-r-- 1 zld zld 1360 Sep 27 13:49 hello.o -rw-rw-r-- 1 zld zld 88 Sep 27 13:33 main.c -rw-rw-r-- 1 zld zld 1488 Sep 27 13:50 main.o zldzld:~/Codes/tmp0926$ ./hello hello,world这里有以下几个问题值得讨论一下 问题1、在上面的方法1或者方法2中都没有用到头文件hello.h为什么 在编译过程中不需要用到头文件这是因为在预处理步骤中预处理的程序会将#include中包含的文件插入该预处理指令的位置。 问题2、在预处理处理#include指令时程序是在哪个目录查找到头文件的 对于该问题先卖个关子在后面的例子中再讲。 问题3、在第2种方法中当将多个目标文件链接再一起时对目标文件的顺序有没有要求 比如上面是gcc hello.o main.o -o hello那能不能按照gcc main.o hello.o -o hello的顺序呢 我们实践一下 zldzld:~/Codes/tmp0926$ gcc main.o hello.o -o hello2 zldzld:~/Codes/tmp0926$ ll total 60 drwxrwxr-x 2 zld zld 4096 Sep 27 14:06 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rwxrwxr-x 1 zld zld 16016 Sep 27 13:50 hello* -rwxrwxr-x 1 zld zld 16016 Sep 27 14:06 hello2* -rw-rw-r-- 1 zld zld 99 Sep 27 13:31 hello.c -rw-rw-r-- 1 zld zld 32 Sep 27 13:31 hello.h -rw-rw-r-- 1 zld zld 1360 Sep 27 13:49 hello.o -rw-rw-r-- 1 zld zld 88 Sep 27 13:33 main.c -rw-rw-r-- 1 zld zld 1488 Sep 27 13:50 main.o实践证明gcc对目标文件的链接顺序是没有要求。之所以讲这个是因为有些老的链接器如果顺序不对则有可能报“undefined references”错误如果调整一下链接顺序则该错误又没有了。但是现在的编译器和链接器一般是不需要考虑这个顺序的GCC就不需要考虑。 但是我们后面会看到如果链接过程中有静态库文件和目标文件则有顺序要求。 通常来说编译所消耗的时间要比链接所消耗的时间要长因此如果把所有的程序都写到一个文件中那只要有修改则需要重新编译和链接对于一个大型的程序来说这是非常耗时的。但如果分成了多个文件那我们可以只对修改的文件进行编译然后再将编译后生成的目标文件和之前没有修改的文件的目标文件再一起链接即可这样就能减少整个构建的时间。 头文件搜索路径 上面的例子是将3个文件(main.c、hello.c、hello.h)放到同一个目录中的。但有过实际项目的人都知道一般不会将所有文件放到同一个目录中的而是比如头文件放到注入include的目录中库文件放到诸如lib的目录中然后源文件也会基于各自的功能分别创建各自的目录的。 我们稍微改一下上面3个文件的目录。目录结构如下。将hello.c文件放到了hello目录中将hello.h放到include目录中而main.c和hello文件夹以及include文件夹在同一层中。 zldzld:~/Codes/tmp0926$ tree . ├── hello │ └── hello.c ├── include │ └── hello.h └── main.c3 directories, 3 files此时我们按照上面的方法1再编译一下(这里注意一下我是在当前main.c文件所处的目录编译的所以hello.c的相对位置就是hello/hello.c) zldzld:~/Codes/tmp0926$ gcc main.c hello/hello.c -o hello1 main.c:2:10: fatal error: hello.h: No such file or directory2 | #include hello.h| ^~~~~~~~~ compilation terminated. hello/hello.c:2:10: fatal error: hello.h: No such file or directory2 | #include hello.h| ^~~~~~~~~ compilation terminated.从上面可知编译报错了错误原因是找不到hello.h头文件。为什么会找不到头文件呢我们用-v选项来看下详细信息 zldzld:~/Codes/tmp0926$ gcc main.c hello/hello.c -v -o hello1 ... #include ... search starts here: #include ... search starts here:/usr/lib/gcc/x86_64-linux-gnu/13/include/usr/local/include/usr/include/x86_64-linux-gnu/usr/include End of search list. Compiler executable checksum: edbc28f9c9bb85637ee0b8e5b79ac141 main.c:2:10: fatal error: hello.h: No such file or directory2 | #include hello.h| ^~~~~~~~~ compilation terminated.我只显示了关键的一些信息从上面可以看到当搜索#include “…” 时是在当前目录下查找#include ... search starts here:。在搜索#include ...是在以下路径查找(下面的这些路径称为系统路径) #include ... search starts here:/usr/lib/gcc/x86_64-linux-gnu/13/include/usr/local/include/usr/include/x86_64-linux-gnu/usr/include很显然hello.h既不在当前路径下也不在系统路径上所以找不到而报错。 既然知道报错的原因了那就知道怎么解决问题了。 三种不推荐的方法 方法1在#include中写出头文件hello.h的绝对路径。如下所示 zldzld:~/Codes/tmp0926/hello$ cat hello.c #include stdio.h // #include hello.h #include /home/zld/Codes/tmp0926/include/hello.hzldzld:~/Codes/tmp0926$ cat main.c #include stdio.h // #include hello.h #include /home/zld/Codes/tmp0926/include/hello.h修改完成后再次编译这次就编译成功了而且运行正确 zldzld:~/Codes/tmp0926$ gcc main.c hello/hello.c -o hello2 zldzld:~/Codes/tmp0926$ ll total 36 drwxrwxr-x 4 zld zld 4096 Sep 27 16:49 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ drwxrwxr-x 2 zld zld 4096 Sep 27 16:47 hello/ -rwxrwxr-x 1 zld zld 16016 Sep 27 16:49 hello2* drwxrwxr-x 2 zld zld 4096 Sep 27 14:31 include/ -rw-rw-r-- 1 zld zld 142 Sep 27 16:46 main.c zldzld:~/Codes/tmp0926$ ./hello2 hello,world但这种方式非常不推荐。 1、可移植性差。 绝对路径是特定于一个具体的文件系统布局的。如果代码被移植到其他系统或环境文件系统的结构可能不同导致编译器无法找到头文件。这使得代码在不同开发者之间、不同机器之间或不同项目之间的共享和复用变得困难。 2、维护困难。 如果头文件的位置发生变化则所有包含绝对路径的源代码文件都需要更新。这增加了维护负担尤其是在大型项目中头文件的位置可能会频繁变动。 方法2在#include中写出头文件hello.h的相对路径。如下所示 zldzld:~/Codes/tmp0926/hello$ cat hello.c #include stdio.h // #include hello.h #include ../include/hello.hzldzld:~/Codes/tmp0926$ cat main.c #include stdio.h // #include hello.h #include include/hello.h运行结果 zldzld:~/Codes/tmp0926$ gcc main.c hello/hello.c -o hello3 zldzld:~/Codes/tmp0926$ ll total 52 drwxrwxr-x 4 zld zld 4096 Sep 27 17:37 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ drwxrwxr-x 2 zld zld 4096 Sep 27 17:36 hello/ -rwxrwxr-x 1 zld zld 16016 Sep 27 16:49 hello2* -rwxrwxr-x 1 zld zld 16016 Sep 27 17:37 hello3* drwxrwxr-x 2 zld zld 4096 Sep 27 14:31 include/ -rw-rw-r-- 1 zld zld 118 Sep 27 17:35 main.c zldzld:~/Codes/tmp0926$ ./hello3 hello,world这种方法比方法1要好一些如果各个文件的相对位置不变则也不用花费许多功夫维护实际上有些开源代码上有这么写。但是如果相对位置一变就需要修改对应的include。因此这种方法本人也是不推荐的。 方法3将头文件放到上面所说的系统路径中然后在写#include语句时就可以不想相对或绝对路径了 zldzld:~/Codes/tmp0926/include$ cp hello.h /usr/local/include/ cp: cannot create regular file /usr/local/include/hello.h: Permission denied zldzld:~/Codes/tmp0926/include$ su Password: rootzld:/home/zld/Codes/tmp0926/include# cp hello.h /usr/local/include/rootzld:/home/zld/Codes/tmp0926/hello# cat hello.c #include stdio.h #include hello.hrootzld:/home/zld/Codes/tmp0926# cat main.c #include stdio.h #include hello.h运行结果 rootzld:/home/zld/Codes/tmp0926# gcc main.c hello/hello.c -o hello4 rootzld:/home/zld/Codes/tmp0926# ./hello4 hello,world但是这也有一些问题。比如从上面可以知道我将头文件拷贝到系统路径时必须要root权限。此外如果将所有的头文件都拷贝到系统路径那这个系统路径下将包含各种各样的头文件这样这个目录下将变得非常的杂乱不堪。因此也是不推荐这种方法。 两种推荐的方法 上面的3种方法我们都不太满意那有什么更好的办法呢我们想一下如果我们告诉编译器去哪个路径去找头文件那是不是事情就解决了 有两种方法可以解决这个事情 1、设置C头文件的搜索路径的环境变量。 export C_INCLUDE_PATH/home/zld/zld/codes/0926:$C_INCLUDE_PATH将上面的例子用该方法编译一下编译过程如下 # 设置C语言头文件的环境变量 rootzld:/home/zld/Codes/tmp0926# export C_INCLUDE_PATH$C_INCLUDE_PATH:/home/zld/Codes/tmp0926/include rootzld:/home/zld/Codes/tmp0926/include# env | grep C_INCLUDE C_INCLUDE_PATH:/home/zld/Codes/tmp0926/include#重新编译与运行(注意我已经将方法3中拷贝到系统路径下的hello.h文件已经删除了防止影响) rootzld:/home/zld/Codes/tmp0926# gcc main.c hello/hello.c -o hello5 rootzld:/home/zld/Codes/tmp0926# ./hello5 hello,world2、在gcc命令中加上-Idir选项其中dir是头文件的路径I是字母i的大小并且I与dir之间没有空格。 # -I加上绝对路径 rootzld:/home/zld/Codes/tmp0926# gcc main.c hello/hello.c -I/home/zld/Codes/tmp0926/include -o hello6 rootzld:/home/zld/Codes/tmp0926# ./hello6 hello,world# -I加上相对路径点.表示当前路径 rootzld:/home/zld/Codes/tmp0926# gcc main.c hello/hello.c -I. -o hello7上述两个方法比之前的三种方法要方便得多。因此推荐这两种方法。在这两种方法中在命令中加上-I选项更加常用。因为如果换到其他的环境中那还得重新设置一下环境变量。但是命令行中加上-I选项一般可以在makefile文件中都已经写好了因此更加方便。 对于添加环境变量的方法对于C来说是用CPLUS_INCLUDE_PATH对于静态库文件来说是用LIBRARY_PATH对于动态库文件来说是LD_LIBRARY_PATH。至于什么是静/动态库文件本文后面会讲到。 这里做一下总结 # C头文件搜索路径环境变量设置 C_INCLUDE_PATH$C_INCLUDE_PATH:/xxx/yyy/zzz export C_INCLUDE_PATH# C头文件搜索路径环境变量设置 CPLUS_INCLUDE_PATH$CPLUS_INCLUDE_PATH:/xxx/yyy/zzz export CPLUS_INCLUDE_PATH# 动态库搜索路径环境变量设置 LD_LIBRARY_PATH$LD_LIBRARY_PATH:/xxx/yyy/zzz/lib export LD_LIBRARY_PATH# 静态库搜索路径环境变量设置 LIBRARY_PATH$LIBRARY_PATH:/xxx/yyy/zzz/lib export LIBRARY_PATH同样对于gcc命令中可以加-L来添加库文件路径。 # 在gcc命令中添加 -Idir 选项来添加头文件搜索路径 # 在gcc命令中添加 -Ldir 选项来添加库文件搜索路径库文件 请大家思考一个问题在hello.c程序中我们调用了printf()函数但是我们实际上并不知道这个函数是怎么实现的我们只是#include stdio.h。 zldzld:~/Codes/tmp0926$ cat -n hello.c 1 #include stdio.h23 int main()4 {5 printf(Hello, World\n);6 return 0;7 }假设还有另外一个场景假设你是一个公司的老板你们公司开发了一个非常牛B的模块这时候有客户希望单独将该模块提供给他们使用。作为公司的老板你自然是不希望将该模块的源代码提供给客户。 上面的两个场景都涉及到库(Library)文件。把库文件和头文件给到对方就可以达到提供功能又不暴露源码的目的了。 库文件可以分成静态库文件和动态库文件。在Linux下静态库文件是以.a后缀结束的文件。动态库文件是以.so后缀结束的文件。 静态库文件 什么是静态库文件呢我举一个例子。假设当前你有4个文件add.c、sub.c、multi.c、div.c。从名字也可以看出来它们分别实现了加法、减法、乘法、除法的功能。并且将这4个源文件都生成了对应的目标文件add.o、sub.o、multi.o、div.o。我们前面说过如果其他程序要引用这4个文件那么可以在gcc命令生成可执行程序时加上这4个目标文件即可。加上4个文件还好但是如果有非常多的文件呢这样一个个添加似乎不太合理效率也不太高。另外一方面加减乘除这四个文件其实可以统称为对数字的运算嘛。能不能将这4个文件像压缩包一样压缩成一个文件呢可以静态库文件就是这样干的。 通俗的说静态库(Static Library)文件就是一个打包了多个目标文件(.o文件)的归档(archive)文件。打个比方就是目标文件相当于图书馆里面的书。而静态库文件就是图书馆我想这就是为啥库文件英文是Library了。 静态库文件有以下特点 1.编译时链接静态库在编译时被链接到最终的可执行文件中这意味着库中的代码和数据会被直接复制到可执行文件中。2.独立性由于静态库在编译时就被整合到可执行文件中因此生成的可执行文件是独立的不依赖于任何外部的库文件。这使得静态库编译的程序具有更好的可移植性和部署简便性。3.重复代码整合如果多个程序使用相同的静态库每个程序都会在自己的可执行文件中包含一份库代码的副本。这增加了可执行文件的大小但同时确保了每个程序都有完整的代码副本无需担心其他程序对库代码的修改或删除。4.性能考虑静态库在编译时就被整合到可执行文件中因此没有运行时加载库的开销。这可能在某些性能敏感的应用中是一个优势。5.更新和维护挑战如果静态库中的代码需要更新或者修复必须重新编译链接所有依赖于该库的程序。这可能会是一个繁琐的过程特别是在大型项目或涉及多个依赖库的情况下。 创建和使用静态库 在Linux中可以用ar命令来创建静态库 ar cr libNAME.a file1.o file2.o ... filen.o其中其中cr是选项c表示creater表示replace。ar其实就是archiver的缩小。 通过该命令最终创建出一个静态库文件libNAME.a 我们还可以使用以下命令查看一个静态库文件中包含的目标文件列表 ar t libNAME.a举例 假设有如下4个文件func1.c中定义了一个函数func1()它的功能是返回两个整型数相加的值。func2.c中定义了func2()函数,该函数的功能是打印给定的数字。在main.c中分别调用了func1函数和func2函数。在mylib.h中定义了两个函数的声明。我们试图将func1.c和func2.c的程序生成一个静态库文件然后共main.c使用。 zldzld:~/Codes/tmp0926$ ll total 24 drwxrwxr-x 2 zld zld 4096 Sep 27 22:07 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.hzldzld:~/Codes/tmp0926$ cat func1.c #include mylib.hint func1(int x, int y) {return (xy); } zldzld:~/Codes/tmp0926$ cat func2.c #include stdio.h #include mylib.hvoid func2(int x) {printf(The result is %d\n, x); } zldzld:~/Codes/tmp0926$ cat main.c #include stdio.h #include mylib.hint main() {int i;i func1(1,2);func2(i);return 0; } zldzld:~/Codes/tmp0926$ cat mylib.h int func1(int x, int y); void func2(int x);首先将func1.c和func2.c文件分别生成两个目标文件(.o) # -Wall是告警选项本文后面会讲到如果你还不了解可以去掉这个选项 zldzld:~/Codes/tmp0926$ gcc -Wall func1.c -c zldzld:~/Codes/tmp0926$ gcc -Wall func2.c -c zldzld:~/Codes/tmp0926$ ll total 32 drwxrwxr-x 2 zld zld 4096 Sep 27 22:14 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 1224 Sep 27 22:13 func1.o -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rw-rw-r-- 1 zld zld 1512 Sep 27 22:14 func2.o -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.h然后将func1.o和func2.o两个目标文件生成一个hello的静态库文件 # 生成静态库文件 zldzld:~/Codes/tmp0926$ ar cr libhello.a func1.o func2.o zldzld:~/Codes/tmp0926$ ll total 36 drwxrwxr-x 2 zld zld 4096 Sep 27 22:16 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 1224 Sep 27 22:13 func1.o -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rw-rw-r-- 1 zld zld 1512 Sep 27 22:14 func2.o -rw-rw-r-- 1 zld zld 2948 Sep 27 22:16 libhello.a -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.h# 查看静态库文件libhello.a包含的目标文件 zldzld:~/Codes/tmp0926$ ar t libhello.a func1.o func2.o这样我们就生成了静态库文件libhello.a。 最后生成最终的可执行文件并运行 zldzld:~/Codes/tmp0926$ gcc -Wall main.c libhello.a -o hello zldzld:~/Codes/tmp0926$ ./hello The result is 3# 也可以先生成main.o文件然后再和静态库文件链接生成最终可执行文件 zldzld:~/Codes/tmp0926$ gcc main.o libhello.a -o hello2 zldzld:~/Codes/tmp0926$ ./hello2 The result is 3上述命令我们是通过libNAME的方式来链接的我们还可以通过-lNAME的方式来链接因此也可以用以下的命令实现 zldzld:~/Codes/tmp0926$ gcc -Wall main.o -lhello -o hello6 /usr/bin/ld: cannot find -lhello: No such file or directory collect2: error: ld returned 1 exit status我们发现命令行竟然执行失败了说找不到-lhello也就是说找不到libhello.a。明明我们当前目录下有libhello.a那为啥找不到呢这是因为通过-lhello的方式是在系统目录下去找而系统目录下没有该静态库文件因此找不到该hello库文件。那怎么办呢前面我们说过有三种方法 1、Command-line options ‘-I’ and ‘-L’, from left to right.在命令行中加上-I或者-L选项指明头文件或者库文件的目录。其中-I是指明头文件的目录-L是指明库文件的目录。 2、设置环境变量。Directories specified by environment variables, such as C_INCLUDE_PATH and # C头文件搜索路径环境变量设置 C_INCLUDE_PATH$C_INCLUDE_PATH:/xxx/yyy/zzz export C_INCLUDE_PATH# C头文件搜索路径环境变量设置 CPLUS_INCLUDE_PATH$CPLUS_INCLUDE_PATH:/xxx/yyy/zzz export CPLUS_INCLUDE_PATH# 动态库搜索路径环境变量设置 LD_LIBRARY_PATH$LD_LIBRARY_PATH:/xxx/yyy/zzz/lib export LD_LIBRARY_PATH# 静态库搜索路径环境变量设置 LIBRARY_PATH$LIBRARY_PATH:/xxx/yyy/zzz/lib export LIBRARY_PATH3、将头文件或者库文件放到系统目录中。 前面我们在头文件的搜索过程说过不推荐使用方法3并且在那里也有示例因此这里我们就不再对方法3进行举例了。我们只对方法1和方法2进行举例。 我们使用第1种方法 zldzld:~/Codes/tmp0926$ gcc main.o -L. -lhello -o h6 zldzld:~/Codes/tmp0926$ ll total 120 drwxrwxr-x 2 zld zld 4096 Sep 27 23:09 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 1224 Sep 27 22:13 func1.o -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rw-rw-r-- 1 zld zld 1512 Sep 27 22:14 func2.o -rwxrwxr-x 1 zld zld 16080 Sep 27 23:09 h6* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:20 hello2* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:26 hello3* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:27 hello4* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:27 hello5* -rw-rw-r-- 1 zld zld 2948 Sep 27 22:16 libhello.a -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 1432 Sep 27 22:20 main.o -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.h在上面的命令gcc main.o -L. -lhello -o h6中-L.表示指定搜索库库文件目录为当前目录(所以用的是点号.)然后指定外部库为-lhello。 从上面的执行结果可以看到成功生成了可执行文件h6。 -L也可以用绝对路径 zldzld:~/Codes/tmp0926$ gcc main.o -L/home/zld//Codes/tmp0926 -lhello -o h7 zldzld:~/Codes/tmp0926$ ll total 136 drwxrwxr-x 2 zld zld 4096 Sep 27 23:11 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 1224 Sep 27 22:13 func1.o -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rw-rw-r-- 1 zld zld 1512 Sep 27 22:14 func2.o -rwxrwxr-x 1 zld zld 16080 Sep 27 23:09 h6* -rwxrwxr-x 1 zld zld 16080 Sep 27 23:11 h7* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:20 hello2* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:26 hello3* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:27 hello4* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:27 hello5* -rw-rw-r-- 1 zld zld 2948 Sep 27 22:16 libhello.a -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 1432 Sep 27 22:20 main.o -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.h下面使用第2种方法 # 首先创建环境变量 zldzld:~/Codes/tmp0926$ env | grep LIB zldzld:~/Codes/tmp0926$ pwd /home/zld/Codes/tmp0926 zldzld:~/Codes/tmp0926$ export LIBRARY_PATH/home/zld/Codes/tmp0926:$LIBRARY_PATH zldzld:~/Codes/tmp0926$ env | grep LIB LIBRARY_PATH/home/zld/Codes/tmp0926:# 执行命令 zldzld:~/Codes/tmp0926$ gcc main.o -lhello -o h8 zldzld:~/Codes/tmp0926$ ll total 152 drwxrwxr-x 2 zld zld 4096 Sep 27 23:14 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 1224 Sep 27 22:13 func1.o -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rw-rw-r-- 1 zld zld 1512 Sep 27 22:14 func2.o -rwxrwxr-x 1 zld zld 16080 Sep 27 23:09 h6* -rwxrwxr-x 1 zld zld 16080 Sep 27 23:11 h7* -rwxrwxr-x 1 zld zld 16080 Sep 27 23:14 h8* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:20 hello2* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:26 hello3* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:27 hello4* -rwxrwxr-x 1 zld zld 16080 Sep 27 22:27 hello5* -rw-rw-r-- 1 zld zld 2948 Sep 27 22:16 libhello.a -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 1432 Sep 27 22:20 main.o -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.h链接顺序 我们前面说过将几个目标文件进行链接时各个目标文件的位置在哪是没有关系的比如下面的几种链接顺序都是可以生成可执行程序的 zldzld:~/Codes/tmp0926$ gcc main.o func1.o func2.o -o hello3 zldzld:~/Codes/tmp0926$ ./hello3 The result is 3 zldzld:~/Codes/tmp0926$ gcc func1.o func2.o main.o -o hello4 zldzld:~/Codes/tmp0926$ ./hello4 The result is 3 zldzld:~/Codes/tmp0926$ gcc func2.o func1.o main.o -o hello5 zldzld:~/Codes/tmp0926$ ./hello5 The result is 3那我们之前的命令 gcc -Wall main.c libhello.a -o hello6 或者 gcc main.o libhello.a -o hello7可以将main.c(或者main.o)与libhello.a的顺序对调吗也就是说下面的命令会执行正确吗 gcc -Wall libhello.a main.c -o hello gcc libhello.a main.o -o hello7我们运行一下发现两条命令都报错了 zldzld:~/Codes/tmp0926$ gcc -Wall libhello.a main.c -o hello /usr/bin/ld: /tmp/ccxCA1TA.o: in function main: main.c:(.text0x17): undefined reference to func1 /usr/bin/ld: main.c:(.text0x24): undefined reference to func2 collect2: error: ld returned 1 exit statuszldzld:~/Codes/tmp0926$ gcc libhello.a main.o -o hello7 /usr/bin/ld: main.o: in function main: main.c:(.text0x17): undefined reference to func1 /usr/bin/ld: main.c:(.text0x24): undefined reference to func2 collect2: error: ld returned 1 exit status链接器的主要任务之一就是解析符号引用即将目标文件和库文件中的未解析符号(如函数和变量)与其他文件或库中的已定义符号进行匹配。链接器按照命令中指定的顺序依次处理每个目标文件和库文件。如果被依赖的目标文件(或库)在引用它的目标文件(或库)之前被处理链接器将无法找到并解析这些符号引用因为它们还未被加入到符号集合中。这有可能导致undefined reference错误。 在上面的命令gcc libhello.a main.o -o hello7中gcc是按照从左到右的顺序依次处理的这样被依赖的库libhello.a先被处理而引用它的目标文件main.o后被处理因此就无法解析func1和func2函数了。 动态库文件 和静态库对应的还有动态库。既然已经有了静态库那为什么还要动态库呢这是由于静态库的特点导致的。我们前面说过如果多个程序使用相同的静态库每个程序都会在自己的可执行文件中包含一份库代码的副本。这样会导致内存空间的浪费。并且如果静态库中的代码需要更新或者修复必须重新编译链接所有依赖于该库的程序这样会导致难以维护和更新。 正是由于静态库的这些缺点引入了动态库。 动态库文件也称为动态库链接(Dynamic Link Library简称DLL)在Windows系统上或在Linux和Unix系统上称为共享对象库(Shared Object Library简称so)是一种包含可被多个程序同时使用的代码和数据的库文件。 使用动态库可以节省内存空间因为多个程序可以共享同一个库文件中的代码而不需要在每个程序的可执行文件中都包含一份副本。此外它还有助于软件更新因为只需要更新一个库文件而不必重新编译所有依赖该库的应用程序。 动态库的主要特点包括 1.共享性多个应用程序可以同时使用同一个动态库中的函数和资源这有助于减少系统的整体内存占用。2.可更新性如果动态库中的错误被修复或者功能得到增强只需要替换掉旧版本的库文件无需重新编译或重新安装依赖此库的所有应用程序。3.模块化动态库支持将大型应用程序分解为更小、更易于管理的部分这有利于团队合作开发和维护。 下面总结一下动态库文件和静态库文件的区别 1、链接时机动态库文件在程序运行时被加载到内存中而静态库文件在程序编译链接时被整合到可执行文件中。2、文件大小与磁盘空间动态库文件不会增加可执行文件的大小多个程序可以共享一个动态库文件节省磁盘空间。静态库文件会增加可执行文件的大小因为静态库的代码被完全复制到了可执行文件中如果多个程序使用同一个静态库会造成存储资源的浪费。3、运行时依赖动态库文件编译的程序在运行时需要外部库文件的支持如果动态库缺失或版本不匹配程序可能无法正常运行。静态库文件编译的程序在运行时不需要外部库文件的支持因为它们已经包含了所有必要的代码和数据。4、更新与维护动态库文件可以独立于程序进行更新当需要更新时只需要替换掉旧的动态库文件无需重新编译链接依赖于它的所有程序。静态库文件更新时需要重新编译链接所有依赖该库的程序以确保所有程序都使用更新后的代码。5、性能与内存使用动态库文件在程序运行时被加载可能会增加一些加载时间但多个程序可以共享同一个动态库文件节省内存使用。静态库文件在程序运行时已经加载到内存中无需额外的加载时间但可能会导致内存使用量的增加尤其是当多个程序使用同一个静态库时。 在实际中动态库用得更多。 创建和使用动态库 在Linux上动态链接库的名字形式为 libxxx.so前缀是lib后缀名为.so。 可以通过gcc编译器使用-fPIC和-shared选项来创建共享对象文件(.so文件)。其中-fPIC 创建与地址无关的编译程序picposition independent code是为了能够在多个应用程序间共享。 gcc -fpic -shared 源文件名... -o 动态库文件或者先使用 gcc -c 指令将指定源文件编译为目标文件再由目标文件生成动态链接库 gcc -c -fPIC 源文件名... -o 目标文件 gcc -shared 目标文件... -o 动态库文件我们对之前的4个文件将func1.c和func2.c合并创建一个动态库文件。 zldzld:~/Codes/tmp0926$ ll total 24 drwxrwxr-x 2 zld zld 4096 Sep 27 23:59 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.hzldzld:~/Codes/tmp0926$ gcc -fpic -shared func1.c func2.c -o libhello.so zldzld:~/Codes/tmp0926$ ll total 40 drwxrwxr-x 2 zld zld 4096 Sep 28 00:04 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rwxrwxr-x 1 zld zld 15600 Sep 28 00:04 libhello.so* -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.h然后像静态库链接一样和main.c一起生成可执行文件 zldzld:~/Codes/tmp0926$ gcc main.c libhello.so -o h1 zldzld:~/Codes/tmp0926$ ll total 56 drwxrwxr-x 2 zld zld 4096 Sep 28 00:10 ./ drwxrwxr-x 11 zld zld 4096 Sep 26 18:54 ../ -rw-rw-r-- 1 zld zld 63 Sep 27 22:04 func1.c -rw-rw-r-- 1 zld zld 97 Sep 27 22:06 func2.c -rwxrwxr-x 1 zld zld 15968 Sep 28 00:10 h1* -rwxrwxr-x 1 zld zld 15600 Sep 28 00:04 libhello.so* -rw-rw-r-- 1 zld zld 101 Sep 27 22:07 main.c -rw-rw-r-- 1 zld zld 44 Sep 27 22:05 mylib.h zldzld:~/Codes/tmp0926$ ./h1 ./h1: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory发现执行可执行程序h1时失败说无法找到动态库文件libhello.so 运行由动态库生成的可执行文件时必须确保程序在运行时可以找到这个动态库。和静态库文件一样可以通过添加动态库的环境变量来解决或者在执行上述gcc命令时添加动态库路径。 # 通过添加动态库环境变量解决 zldzld:~/Codes/tmp0926$ LD_LIBRARY_PATH$LD_LIBRARY_PATH:/home/zld/Codes/tmp0926 zldzld:~/Codes/tmp0926$ export LD_LIBRARY_PATH zldzld:~/Codes/tmp0926$ ./h1 The result is 3# 在执行gcc命令时添加动态库链接 zldzld:~/Codes/tmp0926$ gcc main.c -lhello -L. -o h2 zldzld:~/Codes/tmp0926$ ./h2 The result is 3zldzld:~/Codes/tmp0926$ gcc main.c libhello.so -L. -o h3 zldzld:~/Codes/tmp0926$ ./h3 The result is 3同静态库文件一样动态库文件在链接时也有顺序要求如果将上述命令换一个顺序则也会报错 zldzld:~/Codes/tmp0926$ gcc libhello.so main.c -L. -o h4 /usr/bin/ld: /tmp/ccLWWSlT.o: in function main: main.c:(.text0x17): undefined reference to func1 /usr/bin/ld: main.c:(.text0x24): undefined reference to func2 collect2: error: ld returned 1 exit statusWarning编译选项 我们在前面的一些例子中增加了-Wall选项这个选项就是一个编译告警选项。事实上在实际的项目中一般总是会增加告警选项的。 比如下面的一段程序中在打印printf(MAX MIN %f\n, MAX MIN);中我们将格式化输出写成%f了即浮点型但是实际结果是一个整型。 [zldlocalhost 0926]$ cat test.c #include math.h #include stdio.h #define MAX 3 #define MIN 1 int main() {printf(MAX MIN %f\n, MAX MIN);return 0; }如果不加告警选项则不会有告警信息并且运行结果不符合预期 [zldlocalhost 0926]$ gcc test.c -o test [zldlocalhost 0926]$ ./test MAX MIN 0.000000因此无论如何都建议加上告警选项提前识别出告警风险。 # 加上告警选项上就有告警信息提示了 [zldlocalhost 0926]$ gcc -Wall test.c -o test2 test.c: In function ‘main’: test.c:7: warning: format ‘%f’ expects type ‘double’, but argument 2 has type ‘int’# 根据提示修改程序。然后告警消失 [zldlocalhost 0926]$ cat test.c #include math.h #include stdio.h #define MAX 3 #define MIN 1 int main() {printf(MAX MIN %d\n, MAX MIN); #这里改成%dreturn 0; } [zldlocalhost 0926]$ gcc -Wall test.c -o test3 [zldlocalhost 0926]$ ./test3 MAX MIN 4 # 运行结果正确一些常用的告警选项 1、-w(小写)禁止所有告警信息 2、以**-W**(大写)开头开启特定的告警。比如 -Wreturn-type(返回值告警), -Wsign-compare有符号和无符号对比告警 -Wall (除extra外的所有告警) -Wextra (all外的其他告警) 3、以“-Wno-”开头关闭特定的警告; 例如 -Wno-return-type 取消返回值告警 -Wno-sign-compare取消有符号和无符号对比告警4、将告警转变成错误。 -Werror 所有告警当错误报-Werror 将指定的警告转换为错误。反过来-Wno-error取消编译选项-Werror GCC定义了非常多的告警信息这里就不一一列出来了可以直接参考gcc文档。 调试信息(-g) 一般来说之前的gcc命令生成的可执行程序都不包含调试信息如果程序崩溃了那么则获取崩溃的文件名以及行号。 gcc提供了-g 调试选项这样生成的可执行程序倘若出现问题便可以使用 gdb 找出问题具体出现的位置便于问题的解决。 例如对于下面一段有问题的程序 [zldlocalhost 0926]$ cat -n null.c 1 int a(int *p);23 int main()4 {5 int *p 0;6 return a(p);7 }89 int a(int *p)10 {11 int y *p;12 return y;13 }在第5行因为p赋值为0也就是NULL。因此在调用函数a时在第11行对指针进行解引用时会出错。但是这是运行时错误因此在编译时编译器不会报错 [zldlocalhost 0926]$ gcc -Wall -g null.c -o null [zldlocalhost 0926]$ 但是在运行时就会报错 [zldlocalhost 0926]$ ./null Segmentation fault (core dumped)但是看当前目录并没有产生core文件这是因为很多操作系统默认是不产生core文件的。可以通过ulimit -c查看。如果是0则说明不会产生core文件。 [zldlocalhost 0926]$ ulimit -c 0可以通过ulimit -c unlimited来修改可以产生core文件 [zldlocalhost 0926]$ ulimit -c unlimited [zldlocalhost 0926]$ ulimit -c unlimited再次运行就产生了core文件了 [zldlocalhost 0926]$ ./null Segmentation fault (core dumped) [zldlocalhost 0926]$ ll -rw-------. 1 zld zld 188416 Sep 26 03:47 core.6484然后就可以用gdb工具来定位了 [zldlocalhost 0926]$ gdb null core.6484 zldzld:~/Codes/tmp0926$ gdb null core GNU gdb (Ubuntu 14.0.50.20230907-0ubuntu1) 14.0.50.20230907-git Copyright (C) 2023 Free Software Foundation, Inc. License GPLv3: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type show copying and show warranty for details. This GDB was configured as x86_64-linux-gnu. Type show configuration for configuration details. For bug reporting instructions, please see: https://www.gnu.org/software/gdb/bugs/. Find the GDB manual and other documentation resources online at:http://www.gnu.org/software/gdb/documentation/.For help, type help. Type apropos word to search for commands related to word... Reading symbols from null... [New LWP 2738]This GDB supports auto-downloading debuginfo from the following URLs:https://debuginfod.ubuntu.com Enable debuginfod for this session? (y or [n]) n Debuginfod has been disabled. To make this setting permanent, add set debuginfod enabled off to .gdbinit. [Thread debugging using libthread_db enabled] Using host libthread_db library /lib/x86_64-linux-gnu/libthread_db.so.1. Core was generated by ./null. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x0000625194ea415b in a (p0x0) at null.c:13 13 int y *p; (gdb) backtrace #0 0x0000625194ea415b in a (p0x0) at null.c:13 #1 0x0000625194ea4149 in main () at null.c:7由于本文是介绍gcc的使用因此关于gdb如何使用本文不做过多描述。大家可以在网上搜索如何使用gdb。 编译优化 gcc 提供了为了满足用户不同程度的的优化需要提供了近百种优化选项用来对{编译时间目标文件长度执行效率}这个三维模型进行不同的取舍和平衡。优化的方法不一而足总体上将有以下几类1精简操作指令2尽量满足cpu的流水操作3通过对程序行为地猜测重新调整代码的执行顺序4充分使用寄存器5对简单的调用进行展开等等。想全部了解这些编译选项并在其中挑选适合的选项进行优化无疑像个噩梦般的过程。 幸好gcc提供了从O0到O3这几种不同的优化级别供大家选择。 在编译时如果没有指定上面的任何优化参数则默认为 -O0即没有优化。 参数 -O1、-O2、-O3 中随着数字变大代码的优化程度也越高不过这在某种意义上来说也是以牺牲程序的可调试性为代价的因此优化和调试是一对矛盾体。 举例 下面一段代码 zldzld:~/Codes/tmp0926$ cat test.c #include stdio.hdouble powern(double d, unsigned n) {double x 1.0;unsigned j;for (j1;jn;j){x * d;}return x; }int main() {double sum 0.0;unsigned i;// 循环20亿次for (i1;i 2000000000;i){sum powern(i, i%5);}printf(sum %g\n, sum);return 0; }用不同的优化级别看下运行的时间从结果上看随着优化级别的调高相应的运行时间也逐渐减少。 zldzld:~/Codes/tmp0926$ gcc -Wall -O0 test.c -o O0 zldzld:~/Codes/tmp0926$ time ./O0 sum 1.28e45real 0m12.983s user 0m12.963s sys 0m0.000szldzld:~/Codes/tmp0926$ gcc -Wall -O1 test.c -o O1 zldzld:~/Codes/tmp0926$ time ./O1 sum 1.28e45real 0m3.522s user 0m3.517s sys 0m0.000szldzld:~/Codes/tmp0926$ gcc -Wall -O2 test.c -o O2 zldzld:~/Codes/tmp0926$ time ./O2 sum 1.28e45real 0m3.245s user 0m3.233s sys 0m0.004s zldzld:~/Codes/tmp0926$ zldzld:~/Codes/tmp0926$ gcc -Wall -O3 test.c -o O3 zldzld:~/Codes/tmp0926$ time ./O3 sum 1.28e45real 0m3.124s user 0m3.118s sys 0m0.000s# 加上循环展开优化(-funroll-loop)又进一步优化了 zldzld:~/Codes/tmp0926$ gcc -Wall -O4 -funroll-loops test.c -o O4 zldzld:~/Codes/tmp0926$ time ./O4 sum 1.28e45real 0m2.755s user 0m2.748s sys 0m0.000s我们上面说过编译优化和调试信息(-g)通常来说是一对矛盾体。我个人的建议是要优先保证调试信息毕竟如果没有调试信息当程序崩溃时我们都无法定位这在实际工作中是一件非常麻烦的事得加班加点了。。。。。 总结 这个章节是对前面章节用到的一些gcc命令以及选项做一个总结。 参数选项含义-E仅执行预处理不进行编译、汇编和链接生成后缀为 .i 的预编译文件)-S执行编译后停止不进行汇编和链接生成后缀为 .s 的预编译文件-c编译程序但不链接成为可执行文件生成后缀为 .o 的文件-o对输出文件命名-O/-O1/-O2/-O3优化代码减少代码体积提高代码效率但是相应的会增加编译的时间-lNAME这里是小写的L指定程序要链接的库NAME为库文件名称。可以是静态库文件也可以是动态库文件-Ldir指定-l小写-L所使用到的库文件所在路径。dir为具体的路径-Idir这里是大写的i增加 include 头文件路径。dir为具体的路径-DNAME预定义宏NAME为相应的宏名称-shared生成共享文件然后可以与其它文件链接生成可执行文件-fpic生成适用于共享库的与地址无关的代码PIC-w不输出任何警告信息-Wall开启编译器的批量告警选项-Werror将所有的警告当成错误进行处理在所有产生警告的地方停止编译-g生成调试信息方便gdb调试-save-temps保存编译中间结果-v显示gcc执行时的详细过程–version显示gcc的版本信息 参考文献 1、GCC, the GNU Compiler Collection - GNU Project 2、https://blog.csdn.net/bandaoyu/article/details/115419255 3、https://blog.csdn.net/qq_31108501/article/details/51842166 4、https://www.runoob.com/w3cnote/gcc-parameter-detail.html 5、文心一言
文章转载自:
http://www.morning.gxqpm.cn.gov.cn.gxqpm.cn
http://www.morning.kqpq.cn.gov.cn.kqpq.cn
http://www.morning.fyxr.cn.gov.cn.fyxr.cn
http://www.morning.wprxm.cn.gov.cn.wprxm.cn
http://www.morning.yntsr.cn.gov.cn.yntsr.cn
http://www.morning.fksyq.cn.gov.cn.fksyq.cn
http://www.morning.gjqnn.cn.gov.cn.gjqnn.cn
http://www.morning.kvzvoew.cn.gov.cn.kvzvoew.cn
http://www.morning.rjcqb.cn.gov.cn.rjcqb.cn
http://www.morning.rckmz.cn.gov.cn.rckmz.cn
http://www.morning.trkhx.cn.gov.cn.trkhx.cn
http://www.morning.pngfx.cn.gov.cn.pngfx.cn
http://www.morning.tkxyx.cn.gov.cn.tkxyx.cn
http://www.morning.gtqx.cn.gov.cn.gtqx.cn
http://www.morning.hwpcm.cn.gov.cn.hwpcm.cn
http://www.morning.wphzr.cn.gov.cn.wphzr.cn
http://www.morning.gbsby.cn.gov.cn.gbsby.cn
http://www.morning.mlwpr.cn.gov.cn.mlwpr.cn
http://www.morning.mqwnp.cn.gov.cn.mqwnp.cn
http://www.morning.ypzsk.cn.gov.cn.ypzsk.cn
http://www.morning.nxstj.cn.gov.cn.nxstj.cn
http://www.morning.jpgfx.cn.gov.cn.jpgfx.cn
http://www.morning.mnccq.cn.gov.cn.mnccq.cn
http://www.morning.fjgwg.cn.gov.cn.fjgwg.cn
http://www.morning.krtcjc.cn.gov.cn.krtcjc.cn
http://www.morning.kkjlz.cn.gov.cn.kkjlz.cn
http://www.morning.tkgxg.cn.gov.cn.tkgxg.cn
http://www.morning.pgmbl.cn.gov.cn.pgmbl.cn
http://www.morning.zpfqh.cn.gov.cn.zpfqh.cn
http://www.morning.hengqilan.cn.gov.cn.hengqilan.cn
http://www.morning.fcftj.cn.gov.cn.fcftj.cn
http://www.morning.jpdbj.cn.gov.cn.jpdbj.cn
http://www.morning.jwpcj.cn.gov.cn.jwpcj.cn
http://www.morning.kbfzp.cn.gov.cn.kbfzp.cn
http://www.morning.kdxzy.cn.gov.cn.kdxzy.cn
http://www.morning.qgjp.cn.gov.cn.qgjp.cn
http://www.morning.zmyzt.cn.gov.cn.zmyzt.cn
http://www.morning.pudejun.com.gov.cn.pudejun.com
http://www.morning.dhqg.cn.gov.cn.dhqg.cn
http://www.morning.xkjrq.cn.gov.cn.xkjrq.cn
http://www.morning.mmplj.cn.gov.cn.mmplj.cn
http://www.morning.rddlz.cn.gov.cn.rddlz.cn
http://www.morning.kfmlf.cn.gov.cn.kfmlf.cn
http://www.morning.dwgcx.cn.gov.cn.dwgcx.cn
http://www.morning.clwhf.cn.gov.cn.clwhf.cn
http://www.morning.ctxt.cn.gov.cn.ctxt.cn
http://www.morning.nxtgb.cn.gov.cn.nxtgb.cn
http://www.morning.lktjj.cn.gov.cn.lktjj.cn
http://www.morning.knsmh.cn.gov.cn.knsmh.cn
http://www.morning.qkqzm.cn.gov.cn.qkqzm.cn
http://www.morning.lhldx.cn.gov.cn.lhldx.cn
http://www.morning.zrks.cn.gov.cn.zrks.cn
http://www.morning.khpgd.cn.gov.cn.khpgd.cn
http://www.morning.fwmln.cn.gov.cn.fwmln.cn
http://www.morning.zrdqz.cn.gov.cn.zrdqz.cn
http://www.morning.wsyst.cn.gov.cn.wsyst.cn
http://www.morning.ndpwg.cn.gov.cn.ndpwg.cn
http://www.morning.fldk.cn.gov.cn.fldk.cn
http://www.morning.hfytgp.cn.gov.cn.hfytgp.cn
http://www.morning.gcxfh.cn.gov.cn.gcxfh.cn
http://www.morning.mtdfn.cn.gov.cn.mtdfn.cn
http://www.morning.nlpbh.cn.gov.cn.nlpbh.cn
http://www.morning.xhddb.cn.gov.cn.xhddb.cn
http://www.morning.wmgjq.cn.gov.cn.wmgjq.cn
http://www.morning.cpnsh.cn.gov.cn.cpnsh.cn
http://www.morning.skrrq.cn.gov.cn.skrrq.cn
http://www.morning.rdfq.cn.gov.cn.rdfq.cn
http://www.morning.tlpgp.cn.gov.cn.tlpgp.cn
http://www.morning.wmlby.cn.gov.cn.wmlby.cn
http://www.morning.prprj.cn.gov.cn.prprj.cn
http://www.morning.hbjqn.cn.gov.cn.hbjqn.cn
http://www.morning.nmkbl.cn.gov.cn.nmkbl.cn
http://www.morning.rbnp.cn.gov.cn.rbnp.cn
http://www.morning.xcszl.cn.gov.cn.xcszl.cn
http://www.morning.zlbjx.cn.gov.cn.zlbjx.cn
http://www.morning.chtnr.cn.gov.cn.chtnr.cn
http://www.morning.kkwgg.cn.gov.cn.kkwgg.cn
http://www.morning.lflsq.cn.gov.cn.lflsq.cn
http://www.morning.qywfw.cn.gov.cn.qywfw.cn
http://www.morning.rxpp.cn.gov.cn.rxpp.cn
http://www.tj-hxxt.cn/news/249299.html

相关文章:

  • 网站制作公司crm客户管理系统如何看出一个网站有做seo
  • 建设银行湖北省分行 网站怎么制作一个平台
  • 用jsp做网站一般会用到什么软件asp.net wordpress
  • 网站关键字怎么做wordpress媒体库图片
  • 在线视频网站开发方案php国外电商怎么做
  • 正定县建设局 网站wordpress自动发文章
  • 傻瓜做网站泰州网站设计哪家好
  • 海南网站制作多少钱网站制作眼
  • 贵州省建设厅网站造价工程信息网中信云 做网站
  • 我们的社区手机在线观看苏州电商关键词优化
  • 公司网站开发费入什么科目wordpress做查询系统
  • 推荐西安优秀的响应式网站建设公司百度2018旧版下载
  • 门窗网站源码南充楼盘网
  • 绍兴大明电力建设有限公司网站网站一直不被收录
  • 山西省网站专业网站建设公司兴田德润简介
  • 做电影网站步骤乐清网站建设哪家好
  • 哪里可以做网站啊发光字体制作网站
  • 商务网站开发与建设论文wordpress自动发货
  • 国家建设工程造价数据监测平台在哪个网站网站优化自己做该怎么做
  • 通州网站建设青岛城阳网站开发
  • 网站原型怎么做做网站计划
  • 广西桂川建设集团网站重庆网站建设推荐
  • 网站商城前台模板免费下载工业企业网站建设也的意义
  • 天津在哪做网站深圳app开发哪家专业
  • 网站建设选题排名点击软件
  • 广告网站搭建国外wordpress主题风险
  • 做网站的公司叫什么名字福田做网站价格
  • 北京著名网站设计公司中国新农村建设促进会网站
  • 触屏音乐网站源码做淘宝客网站需要工商营业执照
  • 公司网站功能性建设有哪些网站的基本要素