文章目录
1 GCC和gcc
1.1 GCC和gcc有什么不同
GCC(GNU Compiler Collection): GNU编译器集合,包含众多的语言编译器(C、C++、Java、D、Objective-C等)。
gcc: 特指GCC中的C语言编译器。
1.2 GCC和嵌入式
多数嵌入式操作系统都基于GCC进行源码编译:Linux、VxWorks、Android等。
在实际开发中,内核开发使用gcc,应用开发使用gcc、g++、gdc等。
1.3 如何用gcc进行交叉编译
交叉编译的概念:
由于嵌入式设备往往资源受限,不可能在嵌入式上直接对设备进行编程。我们往往在开发主机(PC)上对源码进行编译,最终生成目标主机(嵌入式设备)的可执行程序,这就是交叉编译的概念。
gcc如何进行交叉编译:
- 配置目标主机的编译工具链(如:arm-linux)
- 配置工具链的版本
- 根据具体的目标代码选择相应的工具链版本。
- 正确使用关于硬件体系结构的特殊编译选项。
2 gcc编译过程
一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等4步才能变成可执行文件。
对于编译器来说,就可以分为预处理器、编译器、汇编器和链接器。
2.1 gcc分步编译过程
预处理:
gcc -E -o hello.i hello.c
预处理主要做了如下事情:
- 处理所有的注释,以空格代替。
- 将所有的#define删除,并且展开所有的宏定义。
- 处理条件编译指令#if,#ifdef,#elif,#else,#endif
- 处理#include,展开被包含的文件
- 保留编译器需要使用的#pragma指令
编译:
gcc -S -o hello.s hello.i
编译会做如下事情:
- 对预处理文件进行词法分析,语法分析和语义分析
- 词法分析:分析关键字、标识符、立即数是否合法
- 语法分析:分析表达式是否遵循语法规则
- 语义分析:在语法分析的基础上进一步分析表达式是否合法
- 分析结束后进行代码优化生成相应的汇编代码文件
汇编:
gcc -c -o hello.o hello.s
汇编:
- 汇编器将汇编代码转变成机器可以执行的指令
- 每条汇编语句几乎都对应一条机器指令
链接:
gcc -o hello hello.o xxx.o
链接器的主要作用就是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接。
预处理和编译由cc1命令完成,汇编由as命令完成,链接由collect2命令完成。
如下编译过程中输出的细节,编译时加上-v
选项即可查看到(整体编译,直接输出可执行文件):
cc1 main.c -o /tmp/ccXCx1YG.s
as -o /tmp/ccZfdaDo.o /tmp/ccXCx1YG.s
cc1 sub.c -o /tmp/ccXCx1YG.s
as -o /tmp/ccn8Cjq6.o /tmp/ccXCx1YG.s
collect2 -o test /tmp/ccZfdaDo.o /tmp/ccn8Cjq6.o ....
2.2 编译多个文件
// ① 一起编译、链接:
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
2.3 gcc默认是进行动态链接的
gcc -o test test.c
对于如上命令,gcc默认编译出来的可执行文件是动态链接的。如果需要静态链接,则需要按如下形式进行编译:
gcc -static test test.c
如果是对于自己的C文件,只要不编译生成动态库,都是静态链接到可执行文件中的。
3 常用编译选项
常用选项 | 描述 |
---|---|
-E | 预处理,开发过程中想快速确定某个宏可以使用“-E -dM” |
-c | 把预处理、编译、汇编都做了,但是不链接 |
-o | 指定输出文件 |
-I | 指定头文件目录 |
-L | 指定链接时库文件目录 |
-l | 指定链接哪一个库文件 |
编译选项举例:
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会用(-Wp,-MD,abc.dep之间不能有任何空格)
gcc -Wl,-Map=main.map main.c // 生成映射文件
gcc -D'TEST="HELLO"' main.c // 宏定义
gcc -M main.c // 获取目标的完成依赖文件
gcc -MM main.c // 获取目标的部分依赖文件
4 制作、使用库文件
4.1 制作、使用静态库
静态链接由链接器在链接时将库的内容直接加入到可执行程序中。
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生成静态库) // ar -q是一样的
gcc -o test main.o libsub.a (如果.a不在当前目录下,需要指定它的绝对或相对路径)
// gcc -o test main.o -L. -lsub (上面的一条命令也可以写成这种形式)
运行:不需要把静态库libsub.a放到板子上。
4.2 制作、使用动态库
对于动态链接来说:
- 可执行程序在运行时才动态加载库进行链接。
- 库的内容不会进入可执行程序当中。
制作、编译:
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生成动态库)
# 如果是64位环境则必须加上-fPIC选项
# gcc -shared -fPIC -o libsub.so sub.o sub2.o sub3.o
gcc -o test main.o -lsub -L /libsub.so/所在目录/
运行:
① 先把libusb.so放到PC/lib目录,然后就可以运行test程序。
② 如果不想把libusb.so放到/lib,也可以放在某个目录比如/a,然后如下执行:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a
./test
我们也可以通过另一种方式使用动态库:
// 编译动态库源码
gcc -shared dlib.c -o dlib.so
// 使用动态库编译
gcc main.c -ldl -o main.out
通过这种方式需要如下几个系统调用的配合:
- dlopen:打开动态库
- dlsym:查找动态库中的函数并返回调用地址
- dlclose:关闭动态库文件
#include <stdio.h>
#include <dlfcn.h>
int main()
{
void* pdlib = dlopen("./dlib.so", RTLD_LAZY);
char* (*pname)();
int (*padd)(int, int);
if( pdlib != NULL )
{
pname = dlsym(pdlib, "name");
padd = dlsym(pdlib, "add");
if( (pname != NULL) && (padd != NULL) )
{
printf("Name: %s\n", pname());
printf("Result: %d\n", padd(2, 3));
}
dlclose(pdlib);
}
else
{
printf("Cannot open lib ...\n");
}
return 0;
}
参考资料: