在linux开发编程过程中,需要对完成的代码进行编译运行,这时我们便需要用到gcc命令,下面我介绍gcc的安装与使用
在ubuntu系统下终端中输入下面的命令
sudo apt-get install build-essential
该命令中,sudo表示使用管理员权限安装,需要输入系统的密码,安装完成后可以通过如下指令查看gcc版本
gcc -v
输出结果如下
可以看到我的gcc版本为7.5.0,出现该界面则安装成功.
gcc语法如下 g c c ( 选项 ) ( 文件名 ) gcc (选项) (文件名) gcc(选项)(文件名)具体选项如下表
选项 | 作用 |
---|---|
-o | 指定生成的输出文件 |
-E | 仅执行编译预处理 |
-S | 将C代码转为汇编代码 |
-wall | 显示警告信息 |
-c | 仅进行编译操作,不进行链接操作 |
下面以一个例子介绍gcc使用,首先在ubuntu创建一个test文件夹,并在文件夹下创建test.c文件
mkdir test
cd test
touch test.c
在test文件夹下可以看到test文件创建成功,打开test.c文件,输入如下代码
#include<stdio.h>
#define mynum1 12
#define mynum2 15
int main()
{
int a = mynum1+mynum2;
printf("测试结果为:%d",a);
return 0;
}
程序编译一般经过这几个步骤:预处理,编译,汇编与链接。在预处理阶段,会处理以#开头的预处理命令,比如#include,#define等命令,处理完成后一般是生成.i文件,利用gcc对test.c进行预处理
gcc -E test.c -o test.i
//命令中test.c为源文件,-o参数指定预处理后的输出文件为test.i
打开test.i,找到文件最后,可以看到如下结果
通过gcc -E命令解释了代码中#include,并且替换了#define的内容。
C语言编译的第二个步骤为编译,这一步是将C语言代码(上一步预处理后的.i文件)转换为汇编语言,具体命令如下
gcc -S test.i -o test.s
//命令中test.i为处理源文件,-o参数指编译后的输出文件为test.s
运行完该命令会产生.s汇编文件
C语言编译的第三个步骤为汇编,在这个阶段,汇编代码会生成二进制代码(二进制代码文件以.o为后缀名)
gcc -c test.s -o test.o
C语言编译的最后一个步骤为链接,在这个阶段,会链接找到依赖的库文件(静态与动态),将二进制文件链接为可执行文件,一般链接命令如下
gcc [目标文件] -o [可执行文件] -l [动态库名]
由于这个例子没有动态库,可以直接
gcc test.o -o test
运行过后你会生成可执行文件,./test执行,结果如图
如果利用gcc进行多文件编译,一般有两种编译方法,假设有文件test1.c与test2.c,第一种编译方法为多个文件一起编译
gcc test1.c test2.c -o test
将test1.c与test2.c编译连接成test可执行文件
第二种编译方法为分别编译各个源文件,之后对编译输出文件进行链接
gcc -c test1.c //将test1.c编译为test1.o
gcc -c test2.c //将test2.c编译为test2.o
gcc -o test1.o test2.o -o test//链接
一般而言,我们更多的使用第二种编译方式,主要原因如下:在一个很多文件的大型工程中,假如我们修改了其中一个文件,使用第一种编译方法就需要把所有文件都重新编译,而第二种编译方法只需要重新编译这个文件然后链接即可。
在使用gcc命令时,我们往往需要对工程进行操作,一个工程又具有多个文件,如果按照上述编译方法,往往需要输入很多指令,而且修改文件也不方便,因此引入Makefile文件解决该问题。下面以一个例子简单讲解Makefile的编写与使用。首先建立一个文件夹,叫Makefiletest,然后在该文件夹里创建cal.c,cal.h,input.c,input.h,main.c一共5个文件,分别输入以下代码:
//cal.c
#include"cal.h"
int caltwonum(int a,int b)
{
return a+b;
}
//cal.h
#ifndef _CAL_H
#define _CAL_H
int caltwonum(int a,int b);
#endif
//input.c
#include<stdio.h>
#include"input.h"
void inputint(int* a,int* b)
{
printf("请输入两个整数:");
scanf("%d %d",a,b);
printf("\r\n");
}
//input.h
#ifndef _INPUT_H
#define _INPUT_H
void inputint(int* a,int* b);
#endif
//main.c
#include<stdio.h>
#include"input.h"
#include"cal.h"
int main()
{
int a,b,sum;
inputint(&a,&b);
sum = caltwonum(a,b);
printf("%d + %d = %d \r\n",a,b,sum);
return 0;
}
同时在Makefiletest文件夹下创建Makefile文件,输入以下内容
#main是需要生成的目标文件,它依赖main.o input.o cal.o
main:main.o input.o cal.o
#gcc命令,通过main.o input.o cal.o生成main,语句前面要用TAB
gcc -o main main.o input.o cal.o
#main.o是目标文件,依赖main.c文件,通过gcc -c main.c生成main.o文件
main.o:main.c
gcc -c main.c
input.o:input.c
gcc -c input.c
cal.o:cal.c
gcc -c cal.c
#增加clean命令
clean:
#删除所有以.o结尾的文件,使用了通配符*
rm *.o
#删除可执行文件main
rm main
创建完成后输入make命令就可以编译文件,生成可执行文件main
可以看到,通过Makefile完成了一系列代码编译与链接,假如此时我修改了cal.c,把a+b换成a*b,此时再次make
可以看到,make仅仅重新编译了cal.c文件,不会整个工程全部编译。
其实,在上述Makefile中,我们可以利用一些Makefile语法简化编写。
1.变量,可以通过定义变量替换某些语句,例如对上述Makefile进行修改
#用objects替换main.o input.o cal.o
objects = main.o input.o cal.o
main:$(objects)
gcc -o main $(objects)
在Makefile中,变量赋值有四种方式,下面分别讲解
1.简单赋值(:=)常规赋值方式,只对当前语句变量有效
2.递归赋值(=)赋值语句会影响多个变量,所有与目标变量相关的其他变量都会受影响
3.条件赋值(?=)如果变量没有定义,则使用符号中的值定义变量,如果该变量已经被赋值,则此语句无效
4.追加赋值(+=)原变量用空格隔开追加一个新值
下面以一段Makefile代码讲解不同赋值方式
x:=hjl
y:=hjl$(x)
x:=kk
x1=hjl
y1=hjl$(x1)
x1=kk
x2=lop
x2?=56
x3=12
x3+=$(x2)
test1:
@echo "x == $(x)"
@echo "y == $(y)"
@echo "x1 == $(x1)"
@echo "y1 == $(y1)"
@echo "x2 == $(x2)"
@echo "x3 == $(x3)"
编写好以后在终端输入make test1,可以得到如下结果
首先我们来对比y与y1的输出,可以看到x采用简单赋值,后续x的改变并没有影响到y,而x1采用递归赋值,一旦x1改变将影响整个文件中与x1有关的变量
2.Makefile目标文件搜索
一个工程可能存在多个源文件,这些源文件不一定在同一个目录下,如果不在一个目录下,按之前Makefile的写法就会出错,我们需要告诉Makefile文件工程源文件在哪?因此Makefile引入了目标文件搜索的功能。
常见的搜索方式有VPATH与vpath,这两个一看仅仅只有大小写的区别,但实际上功能大不相同。首先是VPATH,你可以把它理解为一个环境变量,它的用法如下
VPATH := 搜索路径
#例子
#如果Makefile在当前文件夹没有找到需要文件,则去src文件夹下寻找
VPATH := src
#多个路径之间需要:或者空格隔开,下面这句话表示先搜索hjl文件夹,再搜索src文件夹
VPATH := hjl src
当Makefile在当前文件下找不到需要的文件时,会访问到VPATH变量中的目录下寻找该文件,相当于VPATH给Makefile提供了更多搜索位置
vpath相当于加了条件的VPATH,使用语法如下
#用法一
vpath 条件 路径
#例子
#从hjl文件夹搜索test.c文件
vpath test.c hjl
#多个搜索路径的情况,路径之间用空格或冒号隔开
vpath test.c hjl:src
#用法二
vpath
#清除所有已设置的文件路径
3.伪目标
在之前的例子中,我们在Makefile使用了clean命令,在终端中输入make clean,它会执行clean语句之下的命令,方便我们进行操作。伪目标在Makefile文件中起到了命令替代的作用,用一个简单语句代替原本复杂的命令,简化书写。此外,伪目标要避免与文件重名,一般在Makefile文件中可以用.PYTHON来指明一个目标为伪目标
.PYTHON : clean
clean:
rm *.o
4.函数
Makefile为了方便用户使用,提供了一些函数给用户使用,使用语法如下
$(<function> <arguments>)
or
${<function> <arguments>}
function为函数名,argument为函数参数
参考跟我一起写Makefile