19.头文件.h的作用
编译器需要了解函数和类型的定义,才能生成正确的可执行程序,所以需要头文件来声明函数,例如我定义了一个函数int add(int a,int b),我需要在头文件内声明这个函数,这样编译器可以检测我传入的参数是否符合这个函数的要求。.c文件相当于定于(实现)这个函数的要求
20.头文件的区别
#include <stdio.h>
#include "sub.h"
对于使用<>的头文件,会在编译工具链里去寻找默认的指定的路径里的头文件,而””的头文件,会在当前文件夹里去寻找。
21.GCC 编译过程
一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等 4 步才能变成可执行文件。
常用编译选项
gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o
通过不同的 gcc 选项可以控制这些过程:
21.1预处理(.c->.i)
C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理
21.2 编译(.i->.s语法错误是在编译过程检测出来的)
编译就是把 C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码,所用到的工具为 cc1(它的名字就是 cc1,x86 有自己的 cc1 命令,ARM 板也有自己的cc1 命令)。
21.3 汇编(.s->.o)
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux 系统上一般表现为 ELF 目标文件(OBJ 文件),用到的工具为 as。x86 有自己的 as 命令,ARM 版也有自己的 as 命令,也可能是 xxxx-as(比如 armlinux-as)。
“反汇编”是指将机器代码转换为汇编代码,这在调试程序时常常用到。
21.4 链接(.o->APP)
链接就是将上步生成的 OBJ 文件和系统库的 OBJ 文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件,用到的工具为 ld 或 collect2。
22.怎么编译多个文件
一起编译、链接: gcc -o test main.c sub.c
分开编译,统一链接:
gcc -c -o main.o main.c gcc -c -o sub.o sub.c gcc -o test main.o sub.o
在编译多个c文件时,可以单独把各个的c文件编译成机器文件(gcc -c -o main.o main.c),最后在统一链接为APP(gcc -o test main.o sub.o),这样在后续修改时,不用每一个文件都在编译一次,提高效率。
23.静态库和动态库
23.1概念
静态库(Static Library)
静态库是一个文件,包含了代码和数据。当程序被编译或链接时,这个库会被直接嵌入到目标文件中。使用静态库不需要额外的操作,它会永久地存在于可执行文件中。
动态库(Dynamic Library)
动态库也是一个包含代码和数据的文件,但它是在程序运行时通过特定的调用(如dlopen)加载到内存中的。与静态库不同,动态库不会被直接嵌入到可执行文件中,而是以共享的方式加载。
静态库的优点:一次编译多次使用,节省时间;没有依赖性问题,程序不会因缺少库文件而崩溃。
静态库的缺点:增加可执行文件的体积,因为会复制库内容到目标文件中;不支持版本控制和动态更新,如果需要更改库,必须重新编译整个程序。
动态库的优点:支持模块化设计,便于维护和扩展,只需重新编译或更新特定部分;允许版本控制和动态更新,可以在不停机的情况下升级库;节省内存,按需加载代码和数据,适合资源受限的环境;支持延迟绑定,减少启动时间。
动态库的缺点:可能需要更多的管理和维护,确保所有依赖的动态库都正确地加载和调用;如果某个动态库版本不兼容或被破坏,可能导致程序崩溃或功能异常。
23.2配置静态库
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
ar crs libsub.a sub.o sub2.o sub3.o(可以使用多个.o 生成静态库)
gcc -o test main.o libsub.a (如果.a 不在当前目录下,需要指定它的绝对或相对路径)
可以理解为将多个.o文件整合到一起,成为libsub.a,方便后续编译
23.3配置动态库
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
gcc -shared -o libsub.so sub.o sub2.o sub3.o(可以使用多个.o 生成动态库)
gcc -o test1 main.o -lsub -L ./
gcc -o test1 main.o libsub.so 这两句性质一样
动态库可以理解为调用api,他不需要嵌入到执行文件内,可以节省空间,但需要配置相应的环境变量。
编译为可执行文件后还需要配置环境变量,因为libsub.so是在当前目录,执行app是是在默认的工具链的lib库内寻找,所以需要配置环境变量。
第2步 配置环境变量:
先把 libsub.so 放到 Ubuntu 的/lib 目录,然后就可以运行 test 程序。如果不想把 libsub.so 放到/lib,也可以放在某个目录比如/a,然后如下执行:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a (临时环境变量)
./test
23.4 常用选项
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)
gcc -E main.c // 查看预处理结果,比如头文件是哪个
gcc -E -dM main.c > 1.txt // 把所有的宏展开,存在 1.txt 里
gcc -Wp,-MD,abc.dep -c -o main.o main.c // 生成依赖文件 abc.dep,后面 Makefile 会用