Linux下C编程

初识Linux系统下C编程,gcc及Makefile使用,系统为Ubuntu16.04LTS

流程

C语言代码编写流程:

  • 代码编写
  • 编译代码

使用Vim进行代码编写,编写完成以后在使用gcc编译器进行编译。

代码编写

用户根目录下新建C_Project目录用于存放C代码

1
~$ mkdir C_Project

进入C_Project

1
~$ cd C_Project

新建HelloWorld目录存放此次代码

1
~/C_Project$ mkdir HelloWorld

进入HelloWorld目录

1
~/C_Project$ cd HelloWorld

Vim新建并打开main.c

1
~/C_Project/HelloWorld$ vim main.c

编写代码如下:

image-20210120180424626

保存退出

查看main.c

1
~/C_Project/HelloWorld$ cat main.c

image-20210120180830256

编译代码

使用gcc工具编译main.c

1
gcc main.c

编译完成使用ls查看文件,出现a.out文件,a.out就是编译生成的可执行文件,a为默认名

1
ls

执行a.out

1
./a.out

输出运行结果无误

image-20210120182240220

指定可执行文件为main

1
gcc main.c -o main

image-20210120182750956

gcc编译器

Ubuntu下的C语言编译器是GCC,GCC编译器在安装Ubuntu的时候就已经默认安装好了。

查看GCC编译器的版本号:

1
gcc -v

image-20210120181157866

输入命令“gcc -v”命令以后,你的终端输出版本信息,那么说明你的电脑已经有GCC编译器了。最后下面的“gcc version 5.4.0”说明本机的GCC编译器版本为5.4.0的。“Target: x86_64-linux-gnu”说明Ubuntu自带的GCC编译器是针对X86架构的,因此只能编译在X86架构CPU上运行的程序。如果想要编译在ARM上运行的程序就需要针对ARM的GCC编译器,也就是交叉编译器。

gcc命令

1
gcc <选项> <文件名>
1
2
3
4
5
6
选项:
	-c:只编译不链接为可执行文件,编译器将输入的.c文件编译为.o的目标文件。
	-o:<输出文件名>用来指定编译结束以后的输出文件名,如果使用这个选项的话GCC默认编译出来的可执行文件名字为a.out。
	-g:添加调试信息,如果要使用调试工具(如GDB)的话就必须加入此选项,此选项指示编译的时候生成调试所需的符号信息。
	-O:对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进行优化,这样产生的可执行文件执行效率就高。
	-O2:比-O更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。

编译错误警告:

若代码有bug,gcc编译命令不通过,输出错误所在文件行列及信息

gcc编译流程

GCC编译器的编译流程是:预处理、编译、汇编和链接。

  • 预处理:展开所有的头文件、替换程序中的宏、解析条件编译并添加到文件中。

  • 编译:将经过预编译处理的代码编译成汇编代码,也就是我们常说的程序编译。

  • 汇编:将汇编语言文件编译成二进制目标文件。

  • 链接:将汇编出来的多个二进制目标文件链接在一起,形成最终的可执行文件,链接的时候还会涉及到静态库和动态库等问题。

Makefile

描述哪些文件需要编译、哪些需要重新编译的文件就叫做Makefile,Makefile就跟脚本文件一样,里面还可以执行系统命令。使用的时候只需要一个make命令即可完成整个工程的自动编译,极大的提高了软件开发的效率,通过Makefile你也能了解整个工程的处理过程。

预期功能:

1、如果工程没有编译过,那么工程中的所有.c文件都要被编译并且链接成可执行程序。

2、如果工程中只有个别C文件被修改了,那么只编译这些被修改的C文件即可。

3、如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的C文件,并且链接成可执行文件。

Makefile引入

新建目录存放代码文件

image-20210120215354923

vim新建编辑main.c,input.c,input.h,draw.c,draw.h 5个文件

main.c

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include "input.h"
#include "draw.h"
int main()
{
	int lines;
	char symbol;
	inputNum(&lines,&symbol);
	printf("lines=%d,symbol=%c\n",lines,symbol);
	drawTriangle(lines,symbol);
	return 0;
}

input.c

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include "input.h"
void inputNum(int *a,char *b)
{
	printf("input a num:");
	scanf("%d",a);
	printf("input a char:");
	scanf(" %c",b);
}

input.h

1
2
3
4
5
6
#ifndef _INPUT_H
#define _INPUT_H

void inputNum(int *a,char *b);

#endif

draw.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include "draw.h"

void drawTriangle(int a,char b)
{
	int i,j,k;
	for(i=1;i<=a;i++)
	{
		for(j=a-i;j>0;j--)
			printf(" ");
		for(k=2*i-1;k>0;k--)
			printf("%c",b);
		printf("\n");
	}
}

draw.h

1
2
3
4
5
6
#ifndef _DRAW_H
#define _DRAW_H

void drawTriangle(int a,char b);

#endif

完成后按基本步骤使用gcc编译成可执行文件main并执行,输入行数及字符,输出三角形

image-20210120225714558

很容易发现当存在多个.c/.h文件时,使用gcc编译命令过于复杂

如果只编译被修改过的文件,文件数量太多时不方便

所以我们需要使用Make工具解决这个问题

新建Makefile文件并编辑,注意行首缩进必须用TAB

1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
main: main.o input.o draw.o
	gcc -o main main.o input.o draw.o
main.o: main.c
	gcc -c main.c
input.o: input.c
	gcc -c input.c
draw.o: draw.c
	gcc -c draw.c

clean:
	rm *.o
	rm main

使用make命令编译,执行main,输入输出无误,使用make clean清除执行文件

image-20210120231225066

Makefile规则格式

Makefile里面是由一系列的规则组成的,这些规则格式如下:

1
2
3
4
目标...... :   依赖文件集合.....
	命令1
	命令2
	......

对于这里的Makefile,有5条规则

1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
main: main.o input.o draw.o
	gcc -o main main.o input.o draw.o
main.o: main.c
	gcc -c main.c
input.o: input.c
	gcc -c input.c
draw.o: draw.c
	gcc -c draw.c

clean:
	rm *.o
	rm main

make命令执行过程:

  • 在当前目录查询Makefile文件,按照Makefile文件执行
  • 更新main,依赖于main.o input.o draw.o,所以更新这三个文件,更新完执行命令链接编译.o文件
  • 更新main时会更新main.o input.o draw.o,查询并执行2,3,4条规则,更新.c文件并不链接编译.c文件
  • clean没有依赖文件,故make时不执行,make clean时执行命令删除.o文件及main文件
  • 对应文件未修改时不会执行对应编译命令

Makefile变量

Makefile中的变量都是字符串,类似C语言中的宏。

这里将main.o input.o draw.o赋值给变量Objects,引用时使用$(Objects)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Objects = main.o input.o draw.o
main: $(Objects)
	gcc -o main $(Objects)
main.o: main.c
	gcc -c main.c
input.o: input.c
	gcc -c input.c
draw.o: draw.c
	gcc -c draw.c

clean:
	rm *.o
	rm main

赋值符: 功能
= 使用“=”在给变量的赋值的时候,使用最后一次变量定义的值
:= 使用前面已经定义好的值
?= 如果变量前面没有被赋值,那么此变量就是当前赋值,如果前面已经赋过值了,那么就使用前面赋的值。
+= 给前面已经定义好的变量添加一些字符串进去

Makefile模式规则

通过模式规则我们就可以使用一条规则来将所有的.c文件编译为对应的.o文件。

模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值

1
2
3
4
5
6
7
8
9
10
Objects = main.o input.o draw.o
main: $(Objects)
	gcc -o main $(Objects)
%.o: %.c
	命令

clean:
	rm *.o
	rm main

Makefile自动化变量

自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中

自动化变量 描述
$@ 规则中的目标集合,在模式规则中,如果有多个目标的话,“$@”表示匹配模式中定义的目标集合。
$% 当目标是函数库的时候表示规则中的目标成员名,如果目标不是函数库文件,那么其值为空。
$< 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%”)定义的,那么“$<”就是符合模式的一系列的文件集合。
$? 所有比目标新的依赖目标集合,以空格分开。
$^ 所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件,“$^”会去除重复的依赖文件,值保留一份。
$+ 和“$^”类似,但是当依赖文件存在重复的话不会去除重复的依赖文件。
$* 这个变量表示目标模式中”%”及其之前的部分,如果目标是test/a.test.c,目标模式为a.%.c,那么“$*”就是test/a.test。

这里使用$<

1
2
3
4
5
6
7
8
9
10
Objects = main.o input.o draw.o
main: $(Objects)
	gcc -o main $(Objects)
%.o: %.c
	gcc -c $<

clean:
	rm *.o
	rm main

Makefile伪目标

Makefile有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行make命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。使用伪目标的主要是为了避免Makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如我们这里的clean规则

1
2
3
clean:
	rm *.o
	rm main

上述规则中并没有创建文件clean的命令,因此工作目录下永远都不会存在文件clean,

当我们输入“make clean”后,“rm *.o”和“rm main”总是会执行。

如果在工作目录下创建一个名为“clean”的文件时,当执行“make clean”的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的rm命令也就不会执行,我们预先设想的清理工程的功能也就无法完成。

为了避免这个问题,我们可以将clean声明为伪目标,声明方式如下:

1
.PHONY : clean
1
2
3
4
5
6
7
8
9
10
Objects = main.o input.o draw.o
main: $(Objects)
	gcc -o main $(Objects)
%.o: %.c
	gcc -c $<
.PHONY : clean
clean:
	rm *.o
	rm main

image-20210120235830252

Makefile条件判断

1
2
3
4
5
<条件关键字>
	<条件为真时执行的语句>
else
	<条件为假时执行的语句>
endif

else与<条件为假时执行的语句>可省略

条件关键字 描述
ifeq 判断是否相等
ifneq 判断是否不相等
ifdef<变量名> 如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。
ifndef<变量名> 如果“变量名”的值为空,那么表示表达式为真,否则表达式为假。

常见Makefile函数使用

Makefile支持函数,类似C语言一样,Makefile中的函数是已经定义好的,直接使用,不支持自定义函数。

1
$(函数名 参数集合)
1
${函数名 参数集合}

​ 调用函数和调用普通变量一样,使用符号$来标识。

​ 参数集合是函数的多个参数,参数之间以逗号“,”隔开,

​ 函数名和参数之间以“空格”分隔开,函数的调用以$开头。

subst

字符串替换

1
$(subst <from>,<to>,<text>)

此函数的功能是将字符串中的内容替换为,函数返回被替换以后的字符串

patsubst

模式字符串替换

1
$(patsubst <pattern>,<replacement>,<text>)

此函数查找字符串中的单词是否符合模式,如果匹配就用来替换掉,可以使用包括通配符“%”,表示任意长度的字符串,函数返回值就是替换后的字符串。如果中也包涵“%”,那么中的“%”将是中的那个“%”所代表的字符串

dir

获取目录

1
$(dir <names...>)

此函数用来从文件名序列中提取出目录部分,返回值是文件名序列的目录部分

notdir

提取文件名

1
$(notdir <names...>)

此函数用与从文件名序列中提取出文件名非目录部分

foreach

循环

1
$(foreach <var>, <list>,<text>)

此函数的意思就是把参数中的单词逐一取出来放到参数中,然后再执行所包含的表达式。每次都会返回一个字符串,循环的过程中,中所包含的每个字符串会以空格隔开,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串将会是函数foreach函数的返回值。

wildcard

通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,通配符不会自动展开,这个时候就要用到函数wildcard

1
$(wildcard PATTERN...)
1
$(wildcard *.c)			//获取当前目录下所有的.c文件,类似“%”。
--------------------------本文结束 感谢您的阅读--------------------------
坚持原创技术分享,您的支持将鼓励我继续创作!