《Linux编程》学习笔记 ·002【Linux常用工具GCC、GDB、Make】

注:前言、目录见 https://blog.csdn.net/qq_44220418/article/details/108428971

一、GCC编译器

关于gcc的一些使用,我推荐几篇博客如下:


1、GCC编译程序的流程

GCC编译流程

gcc编译C语言程序,可以分为以下几步:

  • 预处理
    \qquad 检查宏定义与预处理指令,并进行转换
    \qquad 删除程序中的注释和多余的空白字符
    \qquad 生成与源文件同名的.i文件(C文件)
  • 编译
    \qquad 进行词法和语法分析 { 出 现 错 误 给 出 错 误 提 示 、 中 止 编 译 没 有 错 误 将 源 代 码 翻 译 成 可 在 目 标 机 器 上 执 行 的 汇 编 代 码 \begin{cases} 出现错误 & 给出错误提示、中止编译 \\ 没有错误 & 将源代码翻译成可在目标机器上执行的汇编代码 \\ \end{cases} {
    \qquad 生成与源文件同名的.s文件(汇编文件)
  • 汇编
    \qquad 将编译产生的汇编代码汇编成目标机器指令
    \qquad 生成与源文件同名的.o文件(目标文件)
  • 链接
    \qquad 将在一个文件中引用的符号同在另一个文件中该符号的定义链接起来,生成可执行程序
    \qquad 生成可执行文件(默认为a.out

2、GCC命令格式、常用参数

gcc命令的常用格式如下

gcc [命令参数] 文件名称

我习惯用的格式及其理解方式是

gcc [编译参数(进行到编译的哪一步)] 要编译的文件名 -o 要生成的文件名

参数有很多

参数作用
-E执行到编译中的预处理步骤,但是默认输出到屏幕而不是文件(可以通过-o输出到相应的.i文件中)
-S执行到编译中的编译步骤,默认生成与源文件同名的.s文件(汇编文件)
-c执行到编译中的汇编步骤,默认生成与源文件同名的.o文件(目标文件)
-w忽略编译时的警告
-o 文件名指定gcc输出的文件名
-g使得gcc输出的文件包含gdb程序调试时所需的调试信息(想用GDB进行调试时,必须实用该参数

上面-E-S-c(啥都不加)就分别对应了执行到预处理编译汇编链接结束

# 对test.c文件进行预编译,生成test.i文件
gcc -E test.c -o test.i

# 对test.c文件编译到汇编结束,生成test.o文件
gcc -c test.c -o test.o

# 对test.c文件一步编译到可执行程序
gcc test.c -o myexe
# 对test.c文件一步编译到可执行程序(不加-o默认生成可执行程序a.out)
gcc test.c

gcc编译,可以从.c文件开始,也可以从.i文件、.s文件之类的开始
就比如,以下两条gcc命令,只要被编译的test.c或者test.i文件正确存在、没有编译错误,都能成功生成test.s文件

gcc -S test.c -o test.s
gcc -S test.i -o test.s

另外,参数的位置也可以调换,因此,下面两条gcc命令是等价的

gcc -S test.i -o test.s
gcc -S -o test.s test.i

我个人会觉得第一种方式好理解一点
\qquad -S的方式(编译到编译这一步)对test.i文件进行编译,生成test.s文件

第二种方式也能理解,就是感觉两个文件名堆一块不顺眼
\qquad -S的方式(编译到编译这一步)、以生成test.s文件为目的,对test.i文件进行编译


下面以一个C源程序(多个.c.h文件)的案例来说明如何进行编译

  • hello.h文件
    /*hello.h*/
    #ifndef  HELLO_H
    #define  HELLO_H
    void hello()   {
    	star1();
    	printf("hello,my friends\n");
    } 
    #endif
    
  • hello.c文件
    void showhello()  {
    	hello();
    }
    
  • starfun.h文件
    /*****starfun.h*****/
    #ifndef STARFUN_H
    #define STARFUN_H
    #define  NUM 4
    #define  NUMBER 3
    int star1() {
    	int i,j,k;
    	for(k=1;k<=NUM;++k) {
    		for(i=1;i<=(NUM-k);++i)
    			printf(" ");
    		for(j=1;j<=(2*k-1);++j)
    			printf("*");
      		printf("\n");
    	}
    	return 0;
    }
    int star2() {
    	int i,j,k;
    	for(k=NUMBER;k>=0;--k)  {
    		for(i=1;i<=(NUMBER-k+1);++i)
      	     	printf(" ");
    		for(j=1;j<=(2*k-1);++j)
    			printf("*");
    		printf("\n");
    	}
    	return 0;
    }
    #endif
    
  • star.c文件
    #include "starfun.h"
    #include "hello.h"
    #include <stdio.h>
    
    int main() {
    	star1();
    	star2();
    	showhello();
    	return 0;
    }
    
  • 一步编译
       \;
    使用以下命令,一步直接将两个.c源文件一起编译生成可执行程序myexe
    gcc star.c hello.c -o myexe
    
    Tips:.h文件在.c文件里被引用,不需要用gcc对.h文件做什么处理
       \;
  • 多步编译
       \;
    使用以下命令,将两个.c源文件分别预处理,生成相应的.i文件
    gcc -E hello.c -o hello.i
    gcc -E star.c -o star.i
    
    使用以下命令,将两个.i文件分别编译,生成相应的.s文件
    gcc -S hello.i -o hello.s -w
    gcc -S star.i -o star.s -w
    
    使用以下命令,将两个.s文件分别汇编,生成相应的.o文件
    gcc -c hello.s -o hello.o
    gcc -c star.s -o star.o
    
    使用以下命令,将两个.o文件进行链接,生成相应的可执行程序文件
    gcc hello.o star.o -o myexe
    
    Tips:.h文件在.c文件里被引用,不需要用gcc对.h文件做什么处理
       \;

3、GCC创建库文件

在软件开发过程中,经常会使用外部或者其他模块提供的功能

这种功能经常以库文件的形式存在,主要分为 { 静 态 库 动 态 库 ( 共 享 库 ) \begin{cases} 静态库 \\ 动态库(共享库) \\ \end{cases} {

(1).静态库

如果编译程序在编译使用库提供的功能代码的程序时,将代码复制到该程序然后编译成可执行程序,则这种库称为静态库


静态库的文件名必须以lib开头,以.a作为后缀


静态库生成、使用举例

  • calc.h文件
    double aver(double, double);
    double sum(double, double);
    
  • aver.c文件
    #include "calc.h"
    double aver(double num1, double num2)
    {
    	return (num1 + num2) / 2;
    }
    
  • sum.c文件
    #include "calc.h"
    double sum(double num1, double num2)
    {
    	return (num1 + num2);
    }
    
  • main.c文件
    #include <stdio.h>
    #include "calc.h"
    int main()
    {
    	double v1, v2, m, sum2;
    	v1 = 3.2;
    	v2 = 8.9;
    	m = aver(v1, v2);
    	sum2 = sum(v1, v2);
    	printf("%3.2f和%3.2f的平均数是%3.2f\n", v1, v2, m);
    	printf("%3.2f和%3.2f的和是%3.2f\n", v1, v2, sum2);
    	return 0;
    }
    

生成、使用静态库的步骤如下:

  • ① 使用gcc生成.o目标文件
    gcc -c aver.c -o aver.o
    gcc -c sum.c -o sum.o
    
  • ② 利用ar命令生成静态库文件
    ar rc libmycalc.a aver.o sum.o
    
    参数r表示将目标文件插入静态库中(如果之前静态库中已经有了与之同名的文件则删除之前的)
    参数c表示创建新的静态库文件
  • ③ 使用gcc利用静态库编译程序
    gcc main.c -L . -l mycalc -o myexe
    

示例截图
\qquad 在这里插入图片描述

(2).动态库(共享库)

共享库比静态库的处理方式更加灵活,因而其所生产的可执行文件更小

使用共享库链接的可执行文件只包含了它所需要的函数的表格,并没有从目标文件中复制全部的外部函数的机器代码

在可执行文件开始执行时,操作系统将外部函数的机器代码从磁盘上的共享库文件复制到内存中,这个过程称为动态链接

它使可执行程序更加精简而且节省磁盘空间,这是因为共享库可在多个程序之间共享:操作系统允许物理内存中共享库的一个复制被所有正在运行的程序使用,因此也能节省内存。

共享库使得程序员可根据需要随时更新库文件,只要接口不变,那么使用它的源程序就不需要重新编译


正是因为这些优点,当系统中同时存在静态库与共享库时,gcc会默认使用共享库文件

例如,当使用-I name参数指定库名称时,gcc首先搜索在路径中是否有libname.so共享库文件,如果有,则使用该文件;如果没有,则继续查找是否有libname.a静态库文件


共享库的文件名必须以lib开头,以.so作为后缀(so代表shared object,共享的对象)


共享库生成、使用举例

生成、使用共享库的步骤如下:

  • ① 使用gcc生成与位置无关的.o目标文件
    gcc -c -fPIC aver.c -o aver.o
    gcc -c -fPIC sum.c -o sum.o
    
    参数-fPIC表示生成位置无关代码
       \;
  • ② 使用gcc生成共享库
    gcc -shared aver.o sum.o -o libmycalc.so
    
    参数-shared表示生成共享库
       \;
  • ③ 使用gcc利用共享库编译程序
    gcc main.c -L . -l mycalc -o myexe
    
  • ④ 执行程序前,配置好库文件路径的环境变量LD_LIBRARY_PATH
    export LD_LIBRARY_PATH=.
    
    Tips:这种方式只能临时生效,关闭终端后需要重新设置。如果想要知道更多配置的方式,可以参考我开头推荐的博客 Linux基础——gcc编译、静态库与动态库(共享库)
       \;

示例截图
\qquad 在这里插入图片描述

二、GDB调试器

1、GDB调试器的使用

在使用gcc对程序进行编译时,需要加上参数-g,生成的可执行程序才能使用GDB进行调试

步骤如下:

  • 先使用如下命令编译程序,并生成调试信息
    gcc -g 要编译的c源程序 -o 要生成的可执行文件
    
  • 然后使用如下命令,使用gdb进行调试
    gdb 要生成的可执行文件
    

2、GDB常用命令

这个,我想很多人和我一样,在一开始学C++的时候,老师就有教过怎么在一些IDE(诸如 { Dev     C++ VC++     6.0 VS     2017 Codeblocks CLion ⋯ ⋯ \begin{cases} \textup{Dev \;C++} \\ \textup{VC++ \; 6.0} \\ \textup{VS \; 2017} \\ \textup{Codeblocks} \\ \textup{CLion} \\ \cdots\cdots \\ \end{cases} Dev C++VC++ 6.0VS 2017CodeblocksCLion)上进行调试

那么很多像是断点步过(Step over)步入(Step into)退出恢复运行等概念,我也就不详说了


常用的GDB命令如下:

命令缩写说明
listl显示多行源代码
breakb设置断点(程序运行到断点的位置会停下)
runr开始运行程序(运行到第一个断点处)
startst开始运行程序(运行到main函数的第一条语句)
continuec恢复程序运行,运行到下一个断点处
steps步入(Step into),继续执行下一条语句(会进入函数体内)
nextn步过(Step over),继续执行下一条语句(不进入函数体内)
printp打印内部变量值
displaydisp跟踪查看每个变量的值(每次停下都显示它的值)
watch监视变量值的变化
set var设置变量的值
infoi描述程序的状态
backtracebt查看函数调用的信息
framf查看栈帧
quitq退出GDB调试

Tips:在gdb调试环境下,依然可以使用shell中的命令,比如想要清屏就可以输入shell clear做到


演示就先不详写了吧,之前在IDE上调试过的人稍微操作操作就很容易理解了

想看部分简单的演示,可以到 上机作业 ·002【Linux常用工具GCC、GDB、Make】 里瞅瞅

三、Make工具

Make工具主要作用是自动化源程序项目的维护

Make工具根据Makefile文件的内容构建程序,该Makefile文件列出了每一个非源程序文件及如何从其他文件构造这些文件


关于Makefile编写与使用的详细的教程,我推荐这一系列的博文


这里我只介绍最最简单、最最基础的makefile编写

1、Makefile编写规则

Makefile的规则的一般形式如下:

目标 : 依赖文件列表
	要执行的命令

Make工具从Makefile文件的第一个目标开始(该第一个目标也被称为默认目标),但在执行命令之前,make必须处理该规则所依赖的其他规则


例如,有makefile文件如下:

main : main1.o main2.o
	gcc main1.o main2.o -o main
	
main1.o : main1.c
	gcc -c main1.c -o main1.o
	
main2.o : main2.c
	gcc -c main2.c -o main2.o

第一个目标是main,其所依赖的是main1.omain2.o

那就会先处理main1.omain2.o的规则,它们又分别依赖main1.cmain2.c

main1.cmain2.c没有定义规则,于是分别执行main1.omain2.o这两个目标对应的命令,利用gcc生成了这两个文件

处理完main1.omain2.o的规则后,回头处理main的规则,用gcc将main1.omain2.o进行链接生成可执行程序main


按我的理解,makefile可以说只会直接处理第一个目标

例如,将上面的makefile文件写成下面这样:

main :
	gcc main1.o main2.o -o main
	
main1.o :
	gcc -c main1.c -o main1.o
	
main2.o :
	gcc -c main2.c -o main2.o

应该是会报错的,找不到main1.omain2.o文件

它并不是一条规则一条规则地顺序处理下去,而是更像是递归地处理完第一个目标所有依赖的依赖的规则,再处理自己本身


Makefile简单使用举例

  • revertnum.c文件
    #include <stdio.h>
    void ShowRevertNum(int iNum) {
    	while (iNum > 10)
    	{
    		printf("%d", iNum % 10);
    		iNum = iNum / 10;
    	}
    	printf("%d\n", iNum);
    }
    int main(void) {
    	int iNum;
    	while (1) {
    		printf("Please input a number :");
    		scanf("%d", &iNum);
    		if (iNum <= 0) { break; }
    		printf("After revert : ");
    		ShowRevertNum(iNum);
    	}
    }
    

创建makefile文件,编辑内容如下

revertnum : revertnum.o
	gcc -g revertnum.o -o revertnum

revertnum.o : revertnum.s
	gcc -g -c revertnum.s -o revertnum.o

revertnum.s : revertnum.i
	gcc -g -S revertnum.i -o revertnum.s -w

revertnum.i : revertnum.c
	gcc -g -E revertnum.c -o revertnum.i

解释:
\qquad 文件的第一个目标写明了整个项目的目标——生成可执行程序revertnum
\qquad 生成可执行程序revertnum需要处理revertnum.o文件的依赖
\qquad 生成revertnum.o文件需要处理revertnum.s文件的依赖
\qquad 生成revertnum.s文件需要处理revertnum.i文件的依赖
\qquad 生成revertnum.i文件需要处理revertnum.c文件的依赖
\qquad revertnum.c文件没有生成规则,根据命令gcc -g -E revertnum.c -o revertnum.i生成revertnum.i文件
\qquad revertnum.s的依赖已处理,根据命令gcc -g -S revertnum.i -o revertnum.s -w生成revertnum.s文件
\qquad revertnum.o的依赖已处理,根据命令gcc -g -c revertnum.s -o revertnum.o生成revertnum.o文件
\qquad revertnum的依赖已处理,根据命令gcc -g revertnum.o -o revertnum生成可执行程序revertnum

   \;

在终端使用make命令维护项目,生成可执行程序revertnum

\qquad 在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

God-Excious

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值