gcc 基本操作(带例子)
1.基本介绍
GCC(GNU Compiler Collection )是由GNU项目产生的优化编辑器,支持各种编程语言、硬件架构和操作系统。gcc在编译程序程序时,分为以下四个阶段:预处理(Pre-Processing)、编译(Compiling)、汇编(Assembling)、链接(Linking)。
2.基本工具、命令使用
gcc的基本用法是:gcc [options] [filenames],[options]表示参数,[filenames]表示相关文件的名称,一些最基本的参数及含义下表所示:
(1)参数汇总
参数名称 | 含义 |
---|---|
-c | 编译程序,但不链接成为可执行文件(linux上生成后缀为 .o 的文件) |
-E | 编译器经过预处理阶段后停止工作,(生成后缀为 .i 的预编译文件) |
–S | 编译器经过预处理阶段后停止工作,(生成后缀为 .s 的预编译文件) |
-o | 一步直接生成可执行文件 |
-O/-O1/-O2/O3 | 优化代码,减少代码体积,提高代码效率,但是相应的会增加编译的时间 |
-Os | 优化代码体积(注:多个-O参数默认最后一个) |
-Og | 为调试代码做优化,不能与“-O”一起用 |
-O0 | 关闭优化 |
— | --------------------------------------------------------------------------------------------------------------------- |
-w | 不输出任何警告信息 |
-Wall | 将警告视为错误,编译过程一有警告便停止编译 |
-g | 生成调试信息 |
-v | 输出 gcc 工作的详细过程 |
-ggdb | 加入GDB调试器能识别的格式 |
— | -------------------------------------------------------------------------------------------------------------------- |
-static | 链接静态库生成目标文件,禁止使用动态库(在支持动态链接的系统上) |
-share | 尽量使用动态库,但是需要系统存在动态库,生成的目标文件较小 |
-shared | 生成共享文件,然后可以与其它文件链接生成可执行文件 |
-fpic | 生成适用于共享库的与地址无关的代码(PIC)(如果机器支持的话) |
-fPIC | 生成与位置无关的的代码,适用于使用动态库,与“-fpic”的区别在于去除去全局偏移表的任何限制(如果机器支持的话) |
-fPIE | 使用与地址无关的代码生成可执行文件 |
— | -------------------------------------------------------------------------------------------------------------------- |
-M | 生成适合于make规则的主要文件的依赖信息 |
-MM | 与“-M”相比忽略由“#include”所造成的依赖 |
-MD | 与-M作用类似,将输出导入到 .d 文件中 |
-MMD | 与-MM作用类似,将输出导入到 .d 文件中 |
— | ------------------------------------------------------------------------------------------------------------------- |
-Wa,option | 将 option 作为选项传递给汇编器。 如果选项包含逗号,则将其在逗号处拆分为多个选项 |
-Wl,option | 将 option 作为选项传递给链接器。如果选项包含逗号,则将其在逗号处拆分为多个选项。如:‘-Wl,-Map,output.map’ 传递 ‘-Map output.map’给链接器( GNU的链接器也可这样表示:‘-Wl,-Map=output.map’) |
-T script | 使用script作为链接器的脚本 |
-I dir | 将dir作为包含的头文件路径(-I 大写的‘i’) |
-L dir | 将dir作为‘-l’(小写的’L’)操作的文件路径 |
(2)常见工具(GCC插件)
工具名称 | 作用 |
---|---|
ar | 用来创建、修改库,也可以从库中提出单个模块 |
as | gnu汇编器 |
ld | gnu连接器 |
nm | nm是name的缩写,它显示指定文件中的符号信息,文件可以是对象文件、可执行文件或对象文件库 |
objcopy | 将目标文件的一部分或者全部内容拷贝到另外一个目标文件中,或者实现目标文件的格式转换 |
objdump | 用查看目标文件或者可执行的目标文件的构成 |
addr2line | 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻译成源代码文件名和行号 |
size | 列出目标文件中每个部分的名字和尺寸 |
(3)简单的例子
注:使用测试环境说明
虚拟机:VM Ware 14
ubuntu:18.04 LTS
gcc版本:7.5.0
具体使用 gcc -v 命令查看如下图所示:
首先我们编写一个简单的例子,输出“Hello World”:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 printf("Hello World\n");
6 return 0;
7 }
然后使用 gcc -E Hello_World.c -o Hello.i 命令生成预处理文件并且使用vim查看 Hello.i 文件,其最后一部分如下图:
然后使用 gcc -S Hello.i -o Hello.s 命令将生成的预处理文件编译成汇编程序 Hello.s,并且使用vim查看 Hello.s 的内容如下图所示:
然后使用 gcc -c Hello.s -o Hello.o 命令将生成的汇编文件编译成目标文件 Hello.o
然后使用 gcc -static Hello.o -o Hello-s 命令将生成的目标文件静态链接最终生成可执行文件 Hello-s,同时使用 gcc Hello.o -o Hello-d 命令将生成的目标文件动态链接最终生成可执行文件 Hello-d (不指定参数默认动态链接),并且分别使用 size 命令查看文件大小如下图所示:(可以看到静态链接的代码大小比动态链接大得多)
分别运行 Hello-s 与 Hello-d 验证可执行文件正确性如下图所示,可以看到静态库的运行时间静态库快一点(注:使用命令:time 可查看运行时间)
此时我们如果直接用 vim 打开 目标文件(.o)或者是可执行文件,会看到一对乱码,可以使用前面表格2中提到的 objdump 工具。使用命令:objdump -S Hello.o>hello-o-ob.txt 将目标文件进行反汇编,并且添加显示汇编源码(-S),重定向输出到TXT文件(>)。使用命令objdump -S Hello-d>hello-d-ob.txt* 将链接了动态库的可执行文件反汇编。然后再查看两个TXT文件,内容如图6所示:
3.稍复杂一点的例子
上面使用的例子比较简单,只使用到了一个源文件,下面我们以简单的学生成绩管理为背景,演示如何使用gcc编译多个源文件、生成库文件、链接库文件等功能。
(1)代码
首先学生成绩管理文件的结构树如图7所示,为了方便演示功能,我特意建立了files1、files2文件夹存放不同的源文件,建立base、includes1、includes2文件夹存放不同的头文件,建立my_lib文件夹存放生成的库文件
A base.h
base.h 文件主要定义了存放学生成绩的结构体,结构体包括学生姓名、卷面成绩、平时成绩、总成绩四个成员。源代码如下所示:
//===============================================================//
// File Name: base.h
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//===============================================================//
#ifndef _BASE_
#define _BASE_
#ifdef _cplusplus
extern "C"
{
#endif
#define MAX_STU_NUM 10 //最大学生数
#define PROPORTION 0.7f //卷面占比
typedef struct _STU_GRADE
{
char Name[4]; //姓名
float Testscore; //考试成绩
float Usualscore; //平时成绩
float Finalscore; //总成绩
} st_Stu_Grade;
#ifdef _cplusplus
}
#endif
#endif // _BASE_
B Get_Finalscore.cpp
Get_Finalscore.cpp文件主要是个根据传入Get_Finalscore函数的比例计算得到学生的总成绩,源码如下所示:
//===============================================================//
// File Name: Get-Finalscore.cpp
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//===============================================================//
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/base/base.h"
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/includes1/Get_Finalscore.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**************************************************************************
* Description: 获得最终成绩
* Arguments :
* infolist[]:成绩结构体数组
* Propor :卷面比例
* length :学生个数
* Note(s) :无
**************************************************************************/
short Get_Finalscore(st_Stu_Grade infolist[], float Propor, int length)
{
if (Propor <= 1.0)
{
for (short i = 0; i < length; i++)
{
infolist[i].Finalscore = infolist[i].Testscore * Propor +
infolist[i].Usualscore * (1.0 - Propor);
}
return 0;
}
else
{
return -1;
}
}
#ifdef __cplusplus
}
#endif
C Get_Meanscore.cpp
Get_Meanscore.cpp文件主要是计算学生的平均成绩,源码如下所示:
//===============================================================//
// File Name: Get_Meanscore.cpp
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//===============================================================//
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/includes2/Get_Meanscore.h"
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/base/base.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**************************************************************************
* Description: 学生平均成绩
* Arguments :
* infolist[]:成绩结构体数组
* length :学生个数
* Note(s) :需要先调用Get_Finalscore()函数
**************************************************************************/
float Get_Meanscore(st_Stu_Grade infolist[], int lenth)
{
float temp;
for (short i = 0; i < lenth; i++)
{
temp = temp + infolist[i].Finalscore;
}
return (temp / float(lenth));
}
#ifdef __cplusplus
}
#endif
D Get_Finalscore.h
//===============================================================//
// File Name: Get_Finalscore.h
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//==============================================================//
#ifndef _GET_FINALS_
#define _GET_FINALS_
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/base/base.h"
#ifndef _cplusplus
extern "C"
{
#endif
short Get_Finalscore(st_Stu_Grade infolist[], float Propor, int length);
#ifndef _cplusplus
}
#endif
#endif //_GET_FINALS_
E Get_Meanscore.h
//===============================================================//
// File Name: Get_Meanscore.h
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//==============================================================//
#ifndef _GET_MEANS_
#define _GET_MEANS_
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/base/base.h"
#ifndef _cplusplus
extern "C"
{
#endif
float Get_Meanscore(st_Stu_Grade infolist[], int lenth);
#ifndef _cplusplus
}
#endif
#endif //_GET_MEANS_
F Who_Passed.cpp
Who_Passed.cpp文件主要判断哪些学生通过了考试(总成绩大于60分),并且将名字返回给name[][4]二维数组,源码如下所示:
//===============================================================//
// File Name: Who_Passed.cpp
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//===============================================================//
#ifdef __cplusplus
extern "C"
{
#endif
#include <string.h>
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/my_lib/Who_Passed.h"
/**************************************************************************
* Description: 学生平均成绩
* Arguments :
* infolist[]:成绩结构体数组
* name[][4] : 学生名字
* length :学生个数
* Note(s) :无
**************************************************************************/
void Who_Passed(st_Stu_Grade infolist[], char name[][4], int length)
{
short j = 0;
for (short i = 0; i < length; i++)
{
if (infolist[i].Finalscore >= 60.0)
{
strcpy(&name[j++][0], infolist[i].Name);
}
}
}
#ifdef __cplusplus
}
#endif
G Who_Passed.h
//===============================================================//
// File Name: Who_Passed.h
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//==============================================================//
#ifndef _WHO_PASS_
#define _WHO_PASS_
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/base/base.h"
#ifndef _cplusplus
extern "C"
{
#endif
void Who_Passed(st_Stu_Grade p[], char name[][4] , int length);
#ifndef _cplusplus
}
#endif
#endif //_WHO_PASS_
H main.cpp
main.cpp文件创建了一个10个成员的学生成绩结构体数组,用来模拟学生的真实成绩,然后通过调用Get_Finalscore()函数得到并打印第六个学生的最终成绩;调用Get_Meanscore()函数得到并打印学生的平均成绩;调用Who_Passed()并且打印通过学生的名字。
//============================================================================
// File Name: main.cpp
// Project Name: My_MakeTest
// Create Time: 2022-01-20
// Creater : NightVoyager
//============================================================================
//Note(s):makefile 基础学习简单例子,有Get_Finalscore.cpp、
// Get_Meanscore.cpp、Who_Aassed.cpp、main.cpp 四个源文件
// 分别对应:根据比例求最终成绩,获取平均成绩,输出通过同学的名字,主程序四个
// 功能,Who_passed.cpp被编译成lib文件。
//============================================================================
#include <iostream>
#include <stdlib.h>
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/base/base.h"
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/includes2/Get_Meanscore.h"
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/includes1/Get_Finalscore.h"
#include "/home/nvtu18/MyCodes/CPrimerCodes/Make_Demo/My_MakeTest/my_lib/Who_Passed.h"
using namespace std;
int main()
{
char name[10][4] ;
float meangrades = 0.0 ;
//输入成绩
st_Stu_Grade Stu_List[MAX_STU_NUM] = \
{
{"A",39,80,0.},{"B",58.,70,0.},{"C",82.,88,0.},{"D",46.,75,0.},{"E",88.,85,0.},
{"F",66.,80,0.},{"G",76.,70,0.},{"H",90.,92,0.},{"I",31.,75,0.},{"J",41.,78,0.}
};
Get_Finalscore(Stu_List,PROPORTION,MAX_STU_NUM);
cout << Stu_List[6].Finalscore << endl ; //测试是否正确
meangrades = Get_Meanscore(Stu_List,MAX_STU_NUM);
cout << meangrades << endl ; //输出平均成绩
Who_Passed(Stu_List,name,10); //通过的名字
for (int i = 0; i < 5; i++)
{
cout << name[i] << endl;
}
return 0;
}
(2)操作
A 逐一编译文件
程序编写完毕后,我们开始一步步编译:
首先编译Get_Finalscore.cpp文件:
g++ -c ./files1/Get_Finalscore.cpp -o ./files1/Get_Finalscore.o -I includes1 -I base
同理编译Get_Meanscore.cpp文件:
g++ -c ./files2/Get_Meanscore.cpp -o ./files2/Get_Meanscore.o -I includes2 -I base
上面的用到的命令和前面在编译Hello World时的区别是:①上面编译的源文件不是在命令输入的路径下,所以需要使用到相对路径(或是绝对路径)指定;② -I includes1 -I base 指定了源文件所依赖的头文件路径。(注:由于是编译CPP文件,故使用的是g++)逐一编译文件截图如下所示:
B 生成静态库文件
有时候我们希望给别人提供函数接口调用但是又不想让别人知道我们是如何实现的,或者是方便对源文件进行管理,我们可以将一个或多个的源文件生成库文件,然后提供头文件给别人调用。将Who_Passed.cpp生成库文件演示此功能:
g++ -c ./my_lib/Who_Passed.cpp -o ./my_lib/Who_Passed.o -I my_lib -I base
ar -crv ./my_lib/libtools.a ./my_lib/Who_Passed.o
上面的命令中,使用到了ar用于库的创建,参数 -crv 的含义分别是:创建一个库,不管库是否存在,都将创建(-c);在库中插入模块(替换),当插入的模块名已经在库中存在,则替换同名的模块(-r);显示执行操作选项的附加信息(-v)。然后查看my_lib路径下确实生成了库文件,如下图所示:
C 生成可执行文件
根据A B的操作,我们已经生成了 Get_Finalscore.o、Get_Meanscore.o、libtools.a三个文件,接下来我们利用这三个文件生成可执行文件main:
g++ main.cpp -o main -I includes1 -I includes2 -I base ./files1/Get_Finalscore.o ./files2/Get_Meanscore.o -L./my_lib/ -ltools
上面的命令将main.cpp文件编译生成了可执行文件main,链接了两个目标文件,并且通过:-L./my_lib/ -ltools将库文件链接进来。需要注意的是,我们在B步时,对库文件的命名是 libtools.a,然后在此处链接的时候用的是 -ltools,需要去除前缀libs。查看结果,可以看到生成了可执行文件main,执行main,输出正确如下图所示:
D 附:生成动态库
前面提供了生成静态库的方法,那么如果要生成动态库要怎么做呢?这个时候就要用到表格2中提到的 -fPIC 参数了,待我一一道来:
同样的,首先逐一生成目标文件:(此时需要加上 -fPIC参数)
g++ -c ./files1/Get_Finalscore.cpp -o ./files1/Get_Finalscore.o -I includes1 -I base -fPIC
g++ -c ./files2/Get_Meanscore.cpp -o ./files2/Get_Meanscore.o -I includes2 -I base -fPIC
g++ -c ./my_lib/Who_Passed.cpp -o ./my_lib/Who_Passed.o -I my_lib -I base -fPIC
然后生成动态库文件:
gcc --share -fPIC -o ./my_lib/libtools.so ./my_lib/Who_Passed.o -Iincludes2 -Iincludes1 -Ibase
生成可执行文件:
g++ main.cpp -o main -I includes1 -I includes2 -I base ./files1/Get_Finalscore.o ./files2/Get_Meanscore.o -L./my_lib/ -ltools
使用ldd 查看链接的库,发现我们自己的库没有找到,运行main也报错了,如图11所示,为了解决错误,我们需要添加我们的库文件到库路径,采用如下命令:export LD_LIBRARY_PATH=./my_lib:$LD_LIBRARY_PATH 这样就可以找到了,如图12所示。
这样的话,我们的动态库便制作完成了,另外使用 -L./my_lib/ -ltools 的时候会优先使用动态库,我们可以使用参数 -static 指定使用静态库,即:g++ main.cpp -o main -I includes1 -I includes2 -I base ./files1/Get_Finalscore.o ./files2/Get_Meanscore.o -static -L./my_lib/ -ltools
4.总结
整篇博客主要介绍了 gcc的一些基本操作方法、一些参数的含义以及其附带的一些组件的用法。采用一个简单的Hello World例子和稍微复杂的学生成绩列表例子对这些操作方法,组件使用进行了实际操作。整个过程下来,其实可以感觉得直接在命令行输入命令的效率比较低,而且当有了几十个以上的源文件时,这样做简直要命。这时,强大的makefile工具便出现了,具体见我下一篇博客。本人不才,必有疏漏,望指正。
5.参考
https://gcc.gnu.org/onlinedocs/gcc-11.2.0/gcc.pdf
https://sourceware.org/binutils/docs/
https://zhuanlan.zhihu.com/p/456125607
https://blog.csdn.net/superinzaghi747/article/details/32318249?_t=t
https://www.cnblogs.com/kele-dad/p/9394568.html