1:GCC简介
GCC(GNU Compiler Collection)编译器在1987年发布了第一个C语言版本,它是用GPL许可证发行的自由软件,也是GNU计划的关键部分。GCC现在是GNU Linux操作系统的默认编译器,同时也被很多自由软件采用。GCC 在后续的发展过程中,扩展支持了很多的编程语言,如C++、Java、Go等语言。另外,GCC还支持多种不同的硬件平台,如x86、ARM等体系结构。
2:GCC编译过程
GCC的编译流程主要分成4个步骤。
预处理(Pre-Process)
编译(Compile)
汇编(Assemble)
链接(Link)
以常用的一个hello.c程序的编译为例:
- GCC的预处理器(cpp)进行预处理,把头文件、宏等进行展开,生成hello.i文件。
- 进入GCC的编译器,由于GCC可以支持多种编程语言,这里调用C语言的编译器ccl。
- 编译完成生成汇编程序,输出hello.s文件。
- 汇编阶段,GCC调用汇编器(as)进行汇编,生成可重定位的目标程序。
- 链接,GCC调用链接器把所有目标文件和C语言的标准库链接成可执行的二进制文件。
![[Pasted image 20230719210031.png]]
3:GCC常用选项
gcc常用选项:
| 选项名 | 作用 |
|---|---|
| -o | 产生目标(.i、.s、.o、可执行文件等) |
| -E | 只运行C预编译器 |
| -S | 告诉编译器产生汇编程序文件后停止编译,产生的汇编语言文件拓展名为.s |
| -c | 通知gcc取消连接步骤,即编译源码,并在最后生成目标文件 |
| -Wall | 使gcc对源文件的代码有问题的地方发出警告 |
| -Idir | 将dir目录加入搜索头文件的目录路径 |
| -Ldir | 将dir目录加入搜索库的目录路径 |
| -llib | 连接lib库 |
| -g | 在目标文件中嵌入调试信息,以便gdb之类的调试程序调试 |
现在我们有源文件hello.c,下面是一些gcc的使用示例:
gcc -E hello.c -o hello.i
对hello.c文件进行预处理,生成了hello.i 文件
gcc -S hello.i -o hello.s
对预处理文件进行编译,生成了汇编文件
gcc -c hello.s -o hello.o
对汇编文件进行编译,生成了目标文件
gcc hello.o -o hello
对目标文件进行链接,生成可执行文件
gcc hello.c -o hello
直接编译链接成可执行目标文件
gcc -c hello.c 或 gcc -c hello.c -o hello.o
编译生成可重定位目标文件
使用gcc时可以加上-Wall选项。下面这个例子如果不加上-Wall选项,编译器不会报出任何错误或警告,但是程序的结果却不是预期的:
//bad.c
#include<stdio.h>
int main()
{
printf("the number is %f ",5); //程序输出了the number is 0.000000,结果错误
return 0;
}
使用-Wall选项:
gcc -Wall bad.c -o bad
gcc将输出警告信息:
warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
printf(“the number is %f\n”,5);
4:GCC 编译多个文件
假设现在有三个文件:hello.c hello.h main.c ,三个文件的内容如下:
// hello.c
#include<stdio.h>
#include"hello.h"
void printHello()
{
printf("hello world!\n");
}
//main.c
#include<stdio.h>
#include"hello.h"
int main()
{
printHello();
return 0;
}
//hello.h
//仅包含函数声明
#ifndef _HELLO_
#define _HELLO_
void printHello();
#endif
编译这三个文件,可以一次编译:
gcc hello.c main.c -o main 生成可执行文件main
也可以独立编译:
gcc -Wall -c main.c -o main.o
gcc -Wall -c hello.c -o hello.o
gcc -Wall main.o hello.o -o main
独立编译的好处是,当其中某个模块发送改变时,只需要编译该模块就行,不必重新编译所有文件,这样可以节省编译时间。
5:GCC使用外部库
在使用C语言和其他语言进行程序设计的时候,我们需要头文件来提供对常数的定义和对系统及库函数调用的声明。库文件是一些预先编译好的函数集合,那些函数都是按照可重用原则编写的。它们通常由一组互相关联的可重用原则编写的,它们通常由一组互相关联的用来完成某项常见工作的函数构成。使用库的优点在于:
- 模块化的开发
- 可重用性
- 可维护性
库又可以分为静态库与动态库: - 静态库(
.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。静态库比较占用磁盘空间,而且程序不可以共享静态库。运行时也是比较占内存的,因为每个程序都包含了一份静态库。 - 动态库(
.so或.sa):程序在运行的时候才去链接共享库的代码,多个程序共享使用库的代码,这样就减少了程序的体积。
一般头文件或库文件的位置在:
/usr/include及其子目录底下的include文件夹
/usr/local/include及其子目录底下的include文件夹
/usr/lib
/usr/local/lib
/lib
5.1:生成静态库
为了生成.a文件,我们需要先生成.o文件。下面这行命令将我们的hello.o打包成静态库libhello.a:
ar rcs libhello.a hello.o
ar是gun归档工具,rcs表示replace and create,如果libhello之前存在,将创建新的libhello.a并将其替换。
然后就可以这样来使用静态库libhello.a
gcc -Wall main.c libhello.a -o main
还有另外一种使用方式:
gcc -Wall -L. main.c -o main -lhello 【lhello 是 libhello的缩写】
其中 -L. 表示库文件的位置在当前目录下,由于libhello.a是我们自己生成的,并存放在当前录下下,所以需要加上-L.选项。默认库文件是在系统的目录下进行搜索。同样的,-I.选项用于头文件的搜索。
5.2:生成动态库
生成一个共享库,名称的规则是libxxx.so。将刚才hello.o生成libhello.so的命令为:
gcc -shared -fPIC hello.o -o libhello.so
生成了共享库之后,可以这样来使用共享库:
gcc -Wall main.o -o main -L. -lhello
该命令与使用静态库的命令相同,但是在共享库与静态库共存的情况下,优先使用共享库。
共享库有时候并不不在当前的目录下,为了让gcc能够找得到共享库,有下面几种方法:
- 拷贝.so文件到系统共享库路径下,一般指/usr/lib
- 在~/.bash_profile文件中,配置LD_LIBRARY_PATH变量
- 配置/etc/ld.so.conf,配置完成后调用ldconfig更新ld.so.cache
其中,shared选项表示生成共享库格式。fPIC表示产生位置无关码(position independent code),位置无关码表示它的运行、加载与内存位置无关,可以在任何内存地址进行加载。
库的搜索路径遵循几个搜索原则:从左到右搜索-I -l指定的目录,如果在这些目录中找不到,那么gcc会从由环境 变量指定的目录进行查找。头文件的环境变量是C_INCLUDE_PATH,库的环境变量是LIBRARY_PATH.如果还是找不到,那么会从系统指定指定的目录进行搜索。
6:交叉编译 ARM GCC
GCC 具有良好的可扩展性,除了可以编译 x86 体系结构的二进制程序外,还可以支持很多其他体系结构的处理器,如 ARM、MIPS、RISC-V 等。
这里涉及两个概念,一个是本地编译,另一个是交叉编译。
本地编译:在当前目标平台编译出来的程序,并且可以运行在当前平台上。
交叉编译:在一种平台上编译,然后放到另一种平台上运行,这个过程称为交叉编译。之所以有交叉编译,主要是因为嵌入式系统的资源有限,不适合在嵌入式系统中进行编译,如早期ARM处理器性能低下,要编译一个完整的Linux系统是不现实的。因此,首先会在某个高性能的计算机上编译出能在ARM处理器运行的Linux二进制文件,然后烧录到ARM系统中运行。
交叉工具链:交叉工具链不只是GCC,还包含binutils、glibc等工具组成的综合开发环境,可以实现编译、链接等功能。在嵌入式环境中,通常使用uclibc等小型的C语言库。
交叉工具链的命名规则一般如下。
[arch] [-os] [-(gnu)eabi]
arch:表示体系结构,如ARM、MIPS等。
os:表示目标操作系统。
eabi:嵌入式应用二进制接口。
许多Linux发行版提供了编译好的ARM GCC的工具链,如优麒麟Linux 18.04上提供如下和ARM相关的编译器。
arm-linux-gnueabi:主要用于基于ARM32架构的Linux系统,可以用来编译ARM32架构的u-boot、Linux内核以及Linux应用程序等。优麒麟Linux 18.04系统中提供了GCC 5、GCC 6、GCC 7以及GCC 8等多个版本。
aarch-linux-gnueabi:主要用于基于ARM64架构的Linux系统。
arm-linux-gnueabihf:hf指的是支持硬件浮点(Hard Float)的ARM处理器。
在之前的一些ARM处理器中不支持硬件浮点单元,所以由软件浮点来实现。但是最新的一些高端ARM处理器内置了硬件浮点单元,这样新旧两种架构的差异就产生了两个不同的EABI接口。
GCC是GNUCompilerCollection的简称,它是一个自由软件编译器,支持多种编程语言和硬件平台。GCC的编译过程包括预处理、编译、汇编和链接四个步骤。文章还介绍了GCC的常用编译选项,如何处理多个源文件,以及如何生成和使用静态库与动态库。此外,讨论了ARM架构的交叉编译,强调了交叉工具链在嵌入式开发中的重要性。
789

被折叠的 条评论
为什么被折叠?



