Hello World !
我们所说的编写代码包括两部分:代码编写和编译,在Windows下可以使用Visual Studio来完成这两部,可以在 Visual Studio 下编写代码然后直接点击编译就可以了。但是在 Linux 下这两部分是分开的,比如我们用 VIM 进行代码编写,编写完成以后再使用 GCC 编译器进行编译,其中代码编写工具很多,比如 VIM 编辑器、Emacs 编辑器、VScode 编辑器等等,本教程使用 Ubuntu 自带的 VIM 编辑器。先来编写一个最简单的“Hello World”程序,把 Linux 下的 C 编程完整的走一遍。
我们 先点击 gcc -v 观察我们 Linux 下安装的 gcc版本
如果 我们并没有安装gcc 的话 我们可以点击
使用 sudo apt install gcc 进行安装
当我们安装完毕 可以点击 查看 所属的 gcc -v version
我们可以观察到 上述 记录的是 x86_64 这说明 它只适用于 x86 的 架构之下
然后下面的是版本信息
点击 gcc main.c 开始编译
编译完成之后会生成 一个 a.out 的 执行文件
我们输入代码 ./a.out 就可以执行这个代码
最后成功输出 Hello World !
a.out 是 gcc 自动产生的名字 我们可以通过 设定gcc main.c -o main 的方法 生成指定所需的可执行文件
GCC 编译器
gcc 命令
在上一小节我们已经使用过 GCC 编译器来编译 C 文件了,我们使用的是 gcc 命令,gcc
命令格式如下:
gcc [选项] [文件名字]
主要选项如下:
-c 只编译不链接为可执行文件,编译器将输入的.c 文件编译为.o 的目标文件。
-o<输出文件名> 用来指定编译结束以后的输出文件名,如果不使用这个选项的话
GCC 默认编译出来的可执行文件名字为 a.out。
-g 添加调试信息,如果要使用调试工具(如 GDB)的话就必须加入此选项,此选项指示编
译的时候生成调试所需的符号信息。
-O 对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进
行优化,这样产生的可执行文件执行效率就高。
-O2 比-O 更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。
下一节介绍 ------ 编译错误警告
我们先创建一个文件夹 3.2 在里面写入错误的代码信息 main.c
gcc编译器 会帮我们找到错误在哪里
编译流程GCC 编译器的编译流程是:预处理、汇编、编译和链接。预处理就是对程序中的宏定义等相关的内容先进行前期的处理。汇编是先将 C 文件转换为汇编文件。当 C 文件转换为汇编文件以后就是文件编译了,编译过程就是将 C源文件编译成.o结尾的目标文件。编译生成的.o文件不能直接执行,而是需要最后的链接,如果你的工程有很多个 c 源文件的话最终就会有很多.o 文件,将这些.o 文件链接在一起形成完整的一个可执行文件。上一小节演示的例程都只有一个文件,而且文件非常简单,因此可以直接使用 gcc 命令生成可执行文件,并没有先将 c 文件编译成.o 文件,然后在链接在一起。
我们使用 gcc进行编译 将数据三个 .c文件打包成 一个main文件进行 配置
我们点击 ./ main 执行 我们打包完成的 文件
如果文件 很多 我们会并不知道 哪些完成了编译 哪些还没有开始编译
所以我们需要做到是 使用makefile 完成功能
在同一.c .h文件夹下 创建 Makefile 注意大小写
编辑完 在终端输入 make 可以直接完成大批量的编译
如果没有 发现 makefile
可以点击 sudo apt install make
我们在此处会发现 Makefile 会帮助我们省下大量的时间
Makefile 语法
Makefile 规则格式
Makefile 里面是由一系列的规则组成的,这些规则格式如下:
目标…… : 依赖文件集合……
命令 1
命令 2
……
比如下面这条规则:
main : main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
这条规则的目标是 main,main.o、input.o 和 calcu.o 是生成 main 的依赖文件,如果要更新
目标 main,就必须要先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目
标也必须更新,“更新”就是执行一遍规则中的命令列表。
我们先来看看
我们上面写的Makefile
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o : main.c
gcc -c main.c
input.o : input.c
gcc -c input.c
calcu.o : calcu.c
gcc -c calcu.c
clean :
rm *.o
rm main
main : main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
我们观察 这条指令 会发现
这条的目标是 main main.o、input.o 和 calcu.o 是生成 main 的依赖文件
如果要更新目标 main,就必须要先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也必须更新,“更新”就是执行一遍规则中的命令列表。命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
make 命令会为 Makefile 中的每个以 TAB 开始的命令创建一个 Shell 进程去执行。
1 main: main.o input.o calcu.o
2 gcc -o main main.o input.o calcu.o
3 main.o: main.c
4 gcc -c main.c
5 input.o: input.c
6 gcc -c input.c
7 calcu.o: calcu.c
8 gcc -c calcu.c
9
10 clean:
11 rm *.o
12 rm main
上述代码中一共有 5 条规则,1~2 行为第一条规则,3~4 行为第二条规则,5~6 行为第三条规则,7~8 行为第四条规则,10~12 为第五条规则,make 命令在执行这个 Makefile 的时候其执行步骤如下:首先更新第一条规则中的 main,第一条规则的目标成为默认目标,只要默认目标更新了那么就完成了 Makefile 的工作,完成了整个 Makefile 就是为了完成这个工作。在第一次编译的时候由于 main 还不存在,因此第一条规则会执行,第一条规则依赖于文件 main.o、input.o和 calcu.o 这个三个.o 文件,这三个.o 文件目前还都没有,因此必须先更新这三个文件。make会查找以这三个.o 文件为目标的规则并执行。以 main.o 为例,发现更新 main.o 的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc –c main.c”,这行命令很熟悉了吧,就是不链接编译 main.c,生成 main.o,其它两个.o 文件同理。最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行clean的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o文件以及 main,因此 clean 的功能就是完成工程的清理
Makefile 变量
跟 C 语言一样 Makefile 也支持变量的,先看一下前面的例子: main: main.o input.o calcu.o gcc -o main main.o input.o calcu.o上述 Makefile 语句中,main.o input.o 和 calcue.o 这三个依赖文件,我们输入了两遍,我们这个 Makefile 比较小,如果 Makefile 复杂的时候这种重复输入的工作就会非常费时间,而且非常容易输错,为了解决这个问题,Makefile 加入了变量支持。不像 C 语言中的变量有 int、char等各种类型,Makefile中的变量都是字符串!类似 C语言中的宏。使用变量将上面的代码修改
对于整个赋值 和 Makefile 的了解
1.赋值符“=”
这是没有加上 @ 的 结果
:= 变量结果 固定了 并不会 对上面的结果变化 产生影响
3、赋值符“?=”
“?=”是一个很有用的赋值符,比如下面这行代码:curname ?= zuozhongkai上 述 代 码 的 意 思 就 是 , 如 果 变 量 curname 前 面 没 有 被 赋 值 , 那 么 此 变 量 就 是“zuozhongkai”,如果前面已经赋过值了,那么就使用前面赋的值。
4、变量追加“+=”
Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串
进去,此时就要使用到符号“+=”,比如如下所示代码:
objects = main.o inpiut.o
objects += calcu.o
一开始变量 objects 的值为“main.o input.o”,后面我们给他追加了一个“calcu.o”,因
此变量 objects 变成了“main.o input.o calcu.o”,这个就是变量的追加。
Makefile 编写规则优化
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o: main.c
gcc -c main.c
input.o: input.c
gcc -c input.c
calcu.o: calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
上述 Makefile 中第 3~8 行是将对应的.c 源文件编译为.o 文件,每一个 C 文件都要写一个对应的规则,如果工程中 C 文件很多的话显然不能这么做。为此,我们可以使用 Makefile 中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的文件,类似与通配符,a.%.c 就表示以 a.开头,以.c 结束的所有文件。
这样观察上面的 语句 我们可以修改为
先变成一个大的变量 方便后面对后面直接调用这个大的整体 ‘
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o : %.c
#命令
clean:
rm *.o
rm main
objects = main.o input.o calcu.o
/以前没有这句话
main: $(objects)
main是主要的文件 : 后面objects是 依赖文件
然后是 gcc 开始编译目标文件
gcc -o main $(objects)
编译我们自己设置的main型文件
“示例代码 3.4.3.2”中第 5、6 这两行代码替代了“示例代码 3.4.3.1”中的 3~8 行代码,
修改以后的 Makefile 还不能运行,因为第 6 行的命令我们还没写呢,第 6 行的命令我们需要
借助另外一种强大的变量—自动化变量。
Makefile 自动化变量
上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生成对应的目标呢?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中
我们可以得到常用的自动化变量
常用的是 $@ $< 依 赖文件集合中的第一个文件,如果依赖文件是以模式 ( 即“ ^ 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%”)定义的,那么“ 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“<”就是符合模式的一系列的文件集合。
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o : %.c
gcc -c $<
clean:
rm *.o
rm main
重新看一下理解了 本来想说 -c 这么老套 还在用吗
为什么不直接点击 -o 后来 这里才知道
gcc -c 是 只编译成 .o 文件 不进行链接
而
gcc -o 是 在这里的做法是 将 所有编译完成的.o 文件链接成可执行文件
在这里分开写 那么当我们修改了.c 文件的时候
整个文件处理速度就会大大提升
我们来看这句话
这句话这么说
main : main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
我们会发现 main 这个 像不像 我们之前创建的 gcc -o main main.c 文件
还有一件事
顺序的问题
上面是用这两种方式书写的
我希望后面的书写 统一 改写成
gcc -o main main.o
这个写法需要 gcc -c main.c
帮助我们先规划到 main.o文件上来
其实之前更著名的是 直接用 gcc -o main main.c
发现也可以 这里 是直接用main.c
这里是 题外话
我们现在 观察 我们现在要处理的问题
main : main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main 就是我们为了 方便归纳 我们自行设置的
main 被称为 目标文件 main.o 被称为 依赖文件 就是 main 的出现就是依赖于后面这个形式
如果 main 更新 那么 就要更新 依赖文件
main.o: main.c
gcc -c main.c
然后下面诉说的 main.o 是 我们的目标文件 main.c 是我们生成 main.o 所依赖的文件
gcc -c main.c
接下来 才是叙述我们的 关键问题
%.o : %.c
#命令
意思是 所有的.o 都依赖于 .c
这里所说的 gcc -c $<
$< 指的就是 如果 依赖文件是以模式(%) 定义的 那么就使用这其中的所有文件
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o : %.c
gcc -c $<
clean:
rm *.o
rm main
这就是 为什么式子是这么写的 如今豁然开朗
Makefile 伪目标
Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不
代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的
命令。
.PHONY : clean
在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的
命令。
即使clean 并不在目录之下 ,也会执行 这样的 clean过程
上述代码第 5 行声明 clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行
Makefile 条件判断在 C 语言中我们通过条件判断语句来根据不同的情况来执行不同的分支,Makefile 也支持条件判断,
Makefile 函数使用
Makefile 支持函数,类似 C 语言一样,Makefile 中的函数是已经定义好的,我们直接使
用,不支持我们自定义函数。make 所支持的函数不多,但是绝对够我们使用了,函数的用法
如下:
$(函数名 参数集合)
或者
函数名参数集合可以看出,调用函数和调用普通变量一样,使用符号“ {函数名 参数集合} 可以看出,调用函数和调用普通变量一样,使用符号“ 函数名参数集合可以看出,调用函数和调用普通变量一样,使用符号“”来标识。参数集合是函数的多
个参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以
“ ”开头。 1 、函数 s u b s t 函数 s u b s t 用来完成字符串替换,调用形式如下: ”开头。 1、函数 subst 函数 subst 用来完成字符串替换,调用形式如下: ”开头。1、函数subst函数subst用来完成字符串替换,调用形式如下:(subst ,)此函数的功能是将字符串中的内容替换为
,函数返回被替换以后的字符串,比如如下示例:$(subst zzk,ZZK,my name is zzk)把字符串“my name is zzk”中的“zzk”替换为“ZZK”,替换完成以后的字符串为“my name is ZZK”。
2、函数 patsubst
函数 patsubst 用来完成模式字符串替换,使用方法如下:
$(patsubst ,, )
此函数查找字符串 中的单词是否符合模式,如果匹配就用来
替换掉,可以使用包括通配符“%”,表示任意长度的字符串,函数返回值就是替
换后的字符串。如果中也包涵“%”,那么中的“%”将是
中的那个“%”所代表的字符串,比如:
$(patsubst %.c,%.o,a.c b.c c.c)
将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后
的字符串为“a.o b.o c.o”。
3、函数 dir
函数 dir 用来获取目录,使用方法如下:
$(dir
此函数用来从文件名序列中提取出目录部分,返回值是文件名序列的目
录部分,比如:
$(dir )
提取文件“/src/a.c”的目录部分,也就是“/src”。
4、函数 notdir
函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名,用法如下:
$(notdir
此函数用与从文件名序列中提取出文件名非目录部分,比如:
$(notdir )
提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。
5、函数 foreach
foreach 函数用来完成循环,用法如下:
( f o r e a c h < v a r > , < l i s t > , < t e x t > ) 此函数的意思就是把参数 < l i s t > 中的单词逐一取出来放到参数 < v a r > 中,然后再执行 < t e x t > 所包含的表达式。每次 < t e x t > 都会返回一个字符串,循环的过程中, < t e x t > 中所包含的每个字符串会以空格隔开,最后当整个循环结束时, < t e x t > 所返回的每个字符串所组成的整个字符串将会是函数 f o r e a c h 函数的返回值。 6 、函数 w i l d c a r d 通配符“ (foreach , ,
(foreach<var>,<list>,<text>)此函数的意思就是把参数<list>中的单词逐一取出来放到参数<var>中,然后再执行<text>所包含的表达式。每次<text>都会返回一个字符串,循环的过程中,<text>中所包含的每个字符串会以空格隔开,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串将会是函数foreach函数的返回值。6、函数wildcard通配符“(wildcard PATTERN…)比如:$(wildcard *.c)上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。 中的单词逐一取出来放到参数中,然后再执行