3.Linux之GCC及makefile学习(入门)

一、在Linux中怎么写代码

这个问题比较棘手,在网上大致查了一下,觉得最好的办法就是现在windows里面写好了,如何放到Linux里面去编译。如果要在Linux里面写代码,可以使用Linux版的sublime,vscode等。

二、GCC

转自:https://www.linuxprobe.com/linux-gcc-command.html

//test.c
int main(void)
{
    printf("Hello World!\n");
    return 0;
}

我们可以用下面的指令一步编译到位:

gcc test.c -o test

实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)。

2.1 预处理

gcc -E test.c

cc的-E选项,可以让编译器在预处理后停止,并输出预处理结果。我们看看输出结果是什么:
在这里插入图片描述
预处理只是将include等添加到代码中,以及宏编译等的处理,不会检查语法错误。
在这里插入图片描述
在这里插入图片描述

这个是直接输出预编译结果到命令行,也可以用-o指令输出到文本中:

gcc -E test.c -o test.i

2.2 编译为汇编代码(Compilation)

预处理之后,可直接对生成的test.i文件编译,生成汇编代码:

gcc -S test.i -o test.s

gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。
也可以直接对原test.c文件进行编译,gcc -S test.c

这一步处理编译成汇编语言以外,还会检测语法错误,并输出到命令行中。

2.3 汇编(Assembly)

对于上一小节中生成的汇编代码文件test.s,gas汇编器负责将其编译为目标文件,如下:

gcc -c test.s -o test.o

同样的,也可直接对.c文件操作

2.4 连接

gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序test

gcc test.o -o test

执行

./test

在这里插入图片描述

2.5 多个程序文件的编译

通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用GCC能够很好地管理这些编译单元。假设有一个由test1.c和 test2.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序test,可以使用下面这条命令:

gcc test1.c test2.c -o test

如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:

gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc test1.o test2.o -o test

四、Makefile

转自:https://www.cnblogs.com/smartxuchao/p/6440533.html
http://www.ruanyifeng.com/blog/2015/02/make.html

一个例子

Makefile 介绍
首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。我们的规则是:
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

代码示例:

/* main.c */   
#include "mytool1.h"   
#include "mytool2.h"   
int main(int argc,char **argv)   
{   
mytool1_print("hello")mytool2_print("hello")}   
  
/* mytool1.h */   
#ifndef _MYTOOL_1_H   
#define _MYTOOL_1_H   
void mytool1_print(char *print_str)#endif   
  
/* mytool1.c */   
#include "mytool1.h"   
void mytool1_print(char *print_str)   
{   
printf("This is mytool1 print %s ",print_str)}   
  
/* mytool2.h */   
#ifndef _MYTOOL_2_H   
#define _MYTOOL_2_H   
void mytool2_print(char *print_str)#endif   
/* mytool2.c */   
#include "mytool2.h"   
void mytool2_print(char *print_str)   
{   
printf("This is mytool2 print %s ",print_str)}  

由于这个程序比较短,我们可以这样编译

gcc -c main.c 
gcc -c mytool1.c 
gcc -c mytool2.c 
gcc -o main main.o mytool1.o mytool2.o

或者直接这样:

gcc main.c mytool1.c mytool2.c -o test.out

在这里插入图片描述
这样的话我们也可以产生main程序,而且也不是很麻烦。但是如果我们考虑一下如果有一天我们修改了其中的一个文件(比如说mytool1.c)那么我们难道还要重新输入上面的命令?也许你会说,这个很容易解决啊,我写一个 SHELL脚本,让它帮我去完成不就可以了。是的对于这个程序来说,是可以起到作用的。但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译?

为此,聪明的程序员们想出了一个很好的工具来做这件事情,这就是make。我们只要执行以下make,就可以把上面的问题解决掉。在我们执行make之前,我们要先编写一个非常重要的文件–Makefile。对于上面的那个程序来说,可能的一个Makefile的文件是:

这是上面那个程序的Makefile文件:

//文件名:Makefile
main:main.o mytool1.o mytool2.o   
       gcc main.o mytool1.o mytool2.o -o main 
main.o:main.c mytool1.h mytool2.h   
       gcc -c main.c   
mytool1.o:mytool1.c mytool1.h   
       gcc -c mytool1.c   
mytool2.o:mytool2.c mytool2.h   
       gcc -c mytool2.c  
clean:       
       rm -rf *.o main  

有了这个Makefile文件,不论我们什么时候修改了源程序当中的什么文件,我们只要执行make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件它连理都不想去理的。
在这里插入图片描述
那么Makefile是如何编写的?在Makefile中#开始的行都是注释行。Makefile中最重要的是描述文件的依赖关系的说明。一般的格式是:

<target> : <prerequisites> 
[tab]  <commands> //注意这里必须是TAB,不能是空格

第一行表示的是依赖关系。第二行是规则。

例如上面的那个Makefile文件的前两行。

main:main.o mytool1.o mytool2.o 

表示我们的目标(target)main的依赖对象(components)是main.o mytool1.omytool2.o。 当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令。就象我们的上面那个Makefile第二行所说的一样要执行 gcc-o main main.o mytool1.o mytool2.o 注意规则一行中的TAB表示那里是一个TAB键。

如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下make clean就可以了。

Makefile教程

可以直接看这个博客,由浅入深很详细:http://www.ruanyifeng.com/blog/2015/02/make.html,下面主要是记录核心部分和使用方法。

4.1 make 执行过程

1)make 在当前目录下找 "Makefile"或"makefile"的文件

2)如果找到,则会找文件中第一个目标文件(target)。如上例子中main

3)如果 main 命令的执行,依赖后面命令执行所产生的文件,则先执行后面命令

4)当main 命令需要的文件生成完毕,则执行main 命令

4.2 Makefile格式

<target> : <prerequisites> 
[tab]  <commands> //注意这里必须是TAB,不能是空格

上面第一行冒号前面的部分,叫做"目标"(target),冒号后面的部分叫做"前置条件"(prerequisites);第二行必须由一个tab键起首,后面跟着"命令"(commands)。

"目标"是必需的,不可省略;"前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。

每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建。

一般来说是这样使用的:

$ make <target>

这条语句就会根据<target>所需要的依赖和命令进行构建,如果<target>需要其他依赖,则会在makefile里面查找对应的<target>生成所需要的依赖。

如果Make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。

4.3 target

一个目标(target)就构成一条规则。目标通常是文件名,指明Make命令所要构建的对象,比如上文的 a.txt 。目标可以是一个文件名,也可以是多个文件名,之间用空格分隔。

除了文件名,目标还可以是某个操作的名字,这称为"伪目标"(phony target)。

clean:
      rm *.o

上面代码的目标是clean,它不是文件名,而是一个操作的名字,属于"伪目标 ",作用是删除对象文件。

$ make  clean

但是,如果当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。

为了避免这种情况,可以明确声明clean是"伪目标",写法如下。

.PHONY: clean
clean:
        rm *.o temp

声明clean是"伪目标"之后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。像.PHONY这样的内置目标名还有不少,可以查看手册

4.4 prerequisites

前置条件通常是一组文件名,之间用空格分隔。它指定了"目标"是否重新构建的判断标准:只要有一个前置文件不存在,或者有过更新(前置文件的last-modification时间戳比目标的时间戳新),"目标"就需要重新构建。

如果需要生成多个文件,往往采用下面的写法。

source: file1 file2 file3

上面代码中,source 是一个伪目标,只有三个前置文件,没有任何对应的命令。执行make source命令后,就会一次性生成 file1,file2,file3 三个文件。这比下面的写法要方便很多。

$ make file1
$ make file2
$ make file3

4.5 commands

命令(commands)表示如何更新目标文件,由一行或多行的Shell命令组成。它是构建"目标"的具体指令,它的运行结果通常就是生成目标文件。

需要注意的是,每行命令在一个单独的shell中执行。这些Shell之间没有继承关系。

var-lost:
    export foo=bar
    echo "foo=[$$foo]"

上面代码执行后(make var-lost),取不到foo的值。因为两行命令在两个不同的进程执行。一个解决办法是将两行命令写在一行,中间用分号分隔。

var-kept:
    export foo=bar; echo "foo=[$$foo]"

另一个解决办法是在换行符前加反斜杠转义。

var-kept:
    export foo=bar; \
    echo "foo=[$$foo]"

4.6 Makefile文件的语法

1 注释
#后的内容表示注释

2 echo
正常情况下,make会打印每条命令,然后再执行,这就叫做回声(echoing)。在命令的前面加上@,就可以关闭回声。例如

test:
    @# 这是测试

现在再执行make test,就不会有任何输出。

由于在构建过程中,需要了解当前在执行哪条命令,所以通常只在注释和纯显示的echo命令前面加上@。

3 通配符
通配符(wildcard)用来指定一组符合条件的文件名。Makefile 的通配符与 Bash 一致,主要有星号(*)、问号(?)和 […] 。比如, *.o 表示所有后缀名为o的文件。

4 变量和赋值符
Makefile 允许使用等号自定义变量。

txt = Hello World
test:
    @echo $(txt)

上面代码中,变量 txt 等于 Hello World。调用时,变量需要放在 $( ) 之中。

调用Shell变量,需要在美元符号前,再加一个美元符号,这是因为Make命令会对美元符号转义。

test:
    @echo $$HOME

有时,变量的值可能指向另一个变量。

v1 = $(v2)

上面代码中,变量 v1 的值是另一个变量 v2。这时会产生一个问题,v1 的值到底在定义时扩展(静态扩展),还是在运行时扩展(动态扩展)?如果 v2 的值是动态的,这两种扩展方式的结果可能会差异很大。

为了解决类似问题,Makefile一共提供了四个赋值运算符 (=、:=、?=、+=),它们的区别请看StackOverflow

VARIABLE = value
# 在执行时扩展,允许递归扩展。

VARIABLE := value
# 在定义时扩展。

VARIABLE ?= value
# 只有在该变量为空时才设置值。

VARIABLE += value
# 将值追加到变量的尾端。

4.7 此外Makefile里面还有一些自带的变量用法、函数用法、条件语句用法等,可以参考前面的博客,或自行百度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

朽木白露

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

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

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

打赏作者

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

抵扣说明:

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

余额充值