Linux入门(三)-Linux_C编程

Linux_C编程入门

编写代码包括两部分:代码编写和编译。在 Linux下这两部分是分开的,比如我们用 VIM 进行代码编写,编写完成以后再使用 GCC 编译器进行编译。

Ubuntu 下有一些可以进行编程的工具,但是大多都只是编辑器,只能进行代码编辑。如果要编译的话就需要用到 GCC 编译器,使用 GCC 编译器肯定就要接触到 Makefile。在 Ubuntu 下进行 C 语言的编辑和编译、GCC和 Makefile的使用和编写。这些 Linux 下进行 C 编程的基本方法为 ARM 裸机和 Linux 驱动学习打基础。

一、编写C程序

代码编写工具很多,比如 vim 编辑器、 emacs 编辑器、 vscode 编辑器。本文主要记录使用vim编辑器来编写代码。

1.设置vim编辑器

在使用 vim 编辑器之前先设置一下编辑器的格式:

设置 TAB 键为 4 字节:VI 编辑器默认 TAB 键为 8 空格,改成 4 空格,用 vi 打开文件 /etc/vim/vimrc,在此文件最后面输入代码:set  ts=4。
vim 编辑器显示行号:vim 编辑器默认是不显示行号的,不利于代码查看。设置 VIM 编辑器显示行号,同样是通过在文件/etc/vim/vimrc 中添加代码实现,在文件最后面加入下面一行代码:set  nu。

2.编写C程序

设置好 vim 编辑器以后就可以正式开始编写代码了,进入前面创建工程文件夹里面,使用 vim 指令创建一个名为“main.c”的文件,然后在里面输入代码。

二、编译C程序

1.gcc编译器

Ubuntu 下的 C 语言编译器是 GCC,GCC 编译器在我们 Ubuntu 的时候就已经默认安装好了,可以通过如下命令查看 GCC 编译器的版本号:gcc -v。“Target: x86_64-linux-gnu”一行,说明 Ubuntu 自带的 GCC 编译器是针对 X86 架构的,因此只能编译在 X86 架构 CPU 上运行的程序。如果想要编译在 ARM上运行的程序就需要针对ARM的GCC编译器,也就是交叉编译器! ARM 开发,在后面要安装针ARM 架构的GCC交叉编译器。不同的目标架构,其 GCC 编译器是不同的。

gcc 命令格式:gcc  [选项]  [文件名字]

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

GCC 编译器和其它编译器一样,不仅能够检测出错误类型,而且标记除了错误发生在哪个文件、 哪一行, 方便我们去修改代码。

2. gcc编译器编译过程

GCC 编译器是命令模式的,因此需要输入命令来使用 gcc 编译器来编译文件,输入命令:gcc main.c。使用 gcc 编译器来编译 main.c 这个 c 文件。

当编译完成以后会生成一个 a.out 文件,这个 a.out 就是编译生成的可执行文件,执行此文件实现我们编写的代码的功能,执行的方法:使用命令“./+可执行文件”,比如本例程就是命令: ./a.out。a.out 这个文件的命名是 GCC 编译器自动命名的,我们可以在使用 gcc 命令的时候加上 -o 来指定生成的可执行文件名字。

GCC 编译器的编译流程是:

预处理、编译、汇编和链接。预处理就是展开所有的头文件、替换程序中的宏、解析条件编译并添加到文件中。编译是将经过预编译处理的代码编译成汇编代码,也就是我们常说的程序编译。汇编就是将汇编语言文件编译成二进制目标文件。链接就是将汇编出来的多个二进制目标文件链接在一起,形成最终的可执行文件,链接的时候还会涉及到静态库和动态库等问题。
上面例程都只有一个文件,而且文件非常简单,因此可以直接使用 gcc 命令生成可执行文件,并没有先将 c 文件编译成.o 文件,然后再链接在一起。

三、make工具和Makefile文件

1.make工具和Makefile文件的引入

        当源码文件比较多的时候就不适合通过直接输入gcc命令来编译,这时候就需要一个自动化的编译工具。使用命令 “ gcc  main.c  calcu.c  input.c -o  main ” 编译三个.c文件看起来很简单,但这个工程只有三个文件,如果几千上万个文件呢?第二点就是只有一个文件被修改了以后,使用上面的命令编译的时候所有的文件都会重新编译,这几万个文件编译一次所需要的时间很长。

        最好的办法肯定是哪个文件被修改了,只编译这个被修改的文件即可,其它没有修改的文件就不需要再次重新编译了,为此我们改变我们的编译方法,如果第一次编译工程,我们先将工程中的文件都编译一遍,然后后面修改了哪个文件就编译哪个文件,命令如下:

gcc -c main.c       
gcc -c input.c
gcc -c calcu.c
gcc main.o input.o calcu.o -o main

前三行分别是将 main.c、 input.c 和 calcu.c 编译成对应的.o 文件,所以使用了“-c”选项,“-c”选项我们上面说了,是只编译不链接。
最后一行命令是将编译出来的所有.o文件链接成可执行文件main。假如我们现在修改了 calcu.c 这个文件,只需将 caclue.c 这一个文件重新编译成.o文件,然后在将所有的.o文件链接成可执行文件即可,只需要下面两条命令即可:

gcc -c calcu.c
gcc main.o input.o calcu.o -o main

但是这样就又有一个问题,如果修改的文件一多,自己可能都不记得哪个文件修改过了,
然后忘记编译,然后出现问题,为此我们需要这样一个工具:

1、如果工程没有编译过,那么工程中的所有.c 文件都要被编译并且链接成可执行程序。
2、如果工程中只有个别 C 文件被修改了,那么只编译这些被修改的 C 文件即可。
3、如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的 C 文件,并且链接成可执行文件。

引入Makefile可实现上述功能。

2.何为make,Makefile

make:一般说gun make,是一个软件,用于将源代码文件编译为可执行的二进制文件,make工具主要用于完成自动化编译。make工具编译的时候需要Makefile文件提供编译文件。

利用make工具可以自动完成编译工作,这些工作包括:

如果修改了某几个源文件,则只重新编译这几个源文件。

如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。

利用这种自动编译可简化开发工作,避免不必要的重新编译。比如有几万个文件,只修改了其中某一个文件的某一条语句,全部重新编译浪费时间。make工具通过使用一个称为Makefile的文件来完成并自动维护编译工作。

Makefile:指明了编译规则。描述哪些文件需要编译、哪些需要重新编译的文件就叫做 Makefile,Makefile 就跟脚本文件一样, Makefile 里面还可以执行系统命令。

以前一直使用 IDE(集成开发环境)对 Makefile 这个东西进行了封装,提供给大家的是经过封装后的图形界面。在 IDE 中添加要编译的C文件,点击按钮就完成了编译。而在 Linux 下用的最多的是 GCC 编译器,这是个没有 UI(图形界面)的编译器,因此 Makefile 就需要我们自己来编写。在 Linux 下要懂得Makefile,通过 Makefile 你就能了解整个工程的处理过程。

3. Makefile基本语法:Makefile 里面是由一系列的规则组成的,这些规则格式如下:

目标…... : 依赖文件集合……
        命令 1                命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
        命令 2
        ……

而make 命令会为 Makefile 中的每个以 TAB 开始的命令创建一个 Shell 进程去执行。

举例:下面这条规则的目标是 main。而main.o、input.o 和calcu.o 是生成 main 的依赖文件,如果要更新目标 main,就必须先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也必须更新,“更新”就是执行一遍规则中的命令列表。

main:main.o  input.o  calcu.o

        gcc  -o  main  main.o  input.o  calcu.o    //命令列表

下面代码中一共有 5 条规则:1~2 行为第一条规则, 3~4 行为第二条规则, 5~6 行为第三条,7~8 行为第四条,10~12 为第五条,make 命令执行这个Makefile 时执行步骤如下:首先,更新第一条规则中的main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为 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,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行。

make clean:执行以后就会删除当前目录下所有的.o 文件以及main,因此clean的功能就是完成工程的清理。

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

总结一下 Make 的执行过程:
1、 make 命令会在当前目录下查找以 Makefile(makefile 其实也可以)命名的文件。
2、当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
3、当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标

4.Makefile变量

跟 C 语言一样,Makefile 也支持变量的,Makefile 中的变量都是字符串。

#Makefile 变量的使用
objects = main.o input.o calcu.o
main: $(objects)
    gcc -o main $(objects)

上面例子中,第 1 行是注释, Makefile 中可以写注释,注释开头用符号“#”,而不是 C 语言中的“//”等!第 2 行我们定义了一个变量 objects,并且给这个变量进行了赋值,其值为字符串“main.o  input.o  calcu.o”,第 3 和 4行使用到了变量 objects,Makefile 中变量的引用方法是“$(变量名)”,本例中的“$(objects)”就是使用变量 objects。

在例中在定义变量 objects 的时候使用“=”对其进行了赋值, Makefile变量的赋值符还有其它两个“:=”和“?=”。变量追加符“+=”。

延迟赋值符“=”:使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,如下代码:

name = lyp
curname = $(name)
name = lllyyyppp

print:
    @echo curname: $(curname)

第 1 行定义了一个变量 name,变量值为“lyp”,第 2 行也定义了一个变量curname, curname的变量值引用了变量name,按照我们C语言的经验此时curname的值就是“lyp”。第 3 行将变量 name 的值改为了“lllyyyppp”,第 5、6 行是输出变量 curname的值。在 Makefile 要输出一串字符的话使用“echo”,和 C 语言中的“printf”一样,第 6 行中“echo”前面加了个“@”符号,因为 Make 在执行的过程中会自动输出命令执行过程,在命令前面加上“@”的话就不会输出命令执行过程。使用命令“make print”来执行上述代码。


curname的值不是“lyp”,而是“lllyyyppp”,也就是变量“name”最后一次赋值的结果,赋值符“=”借助另外一个变量,可以将变量的真实值推到后面去定义。变量的真实值取决于它所引用的变量的最后一次有效值。

立刻赋值符“:=”:用上面代码测试赋值符“:=”,修改上面中的第 2 行,将其中的“=”改为“:=”。

curname := $(name)

再执行“make  print”此时的 curname 是lyp,不是lllyyyppp 了。因为赋值符“:=”,不会使用后面定义的变量,只能使用前面已经定义好的,这就是“=”和“:=”两个的区别。

空赋值符“?=”:比如下面代码

curname ?= lllyyyppp

上述代码的意思是,如果变量 curname 在前面没有被赋值,那么此变量即刻赋值为“lllyyyppp”,如果前面已经赋过值了,那么就使用前面赋的值。(赋值前看看前面别人是否已经给变量赋值)

变量追加符“+=”:Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进去,此时就要使用到符号“+=”,比如如下所示代码:

objects = main.o inpiut.o
objects += calcu.o

一开始变量 objects 的值为“main.o input.o”,后面给他追加了一个“calcu.o”,因此变量 objects 变成了“main.o input.o calcu.o”,这就是变量的追加。

5.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 文件。

模式规则中,至少在规则的目标定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,类似于shell脚本的 * 通配符。比如 “%.c” 就是所有的以.c 结尾的文件,类似于通配符, a.%.c 就表示以 a.开头,以.c 结束的所有文件。当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,使用方法如下:

%.o : %.c
    命令

因此上面代码中的 Makefile 可以改为如下形式:

main: main.o input.o calcu.o
    gcc -o main main.o input.o calcu.o
%.o : %.c
    #命令

clean:
    rm *.o
    rm main

修改以后的 Makefile 还不能运行,第4 行的命令还没写,第4 行的命令我们需要借助—自动化变量。

6.自动化变量

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

常用的三种: $@         $<        $^

我们使用自动化变量来完成上面示例代码 Makefile,最终的完整代码如下:

objects = main.o input.o calcu.o
main: $(objects)
    gcc -o main $(objects)

%.o : %.c        //makefile存在的默认规则:.o文件默认使用.c文件来进行编译
    gcc -c $<    //因此,这两句代码删掉之后程序也没什么区别

clean:
    rm *.o
    rm main
7.伪目标

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

clean:
    rm *.o
    rm main

若没有创建文件 clean ,工作目录下永远都不会存在文件 clean,当“make clean”后,后面“rm *.o”和“rm main”总是会执行。

可当工程文件多了之后,若不小心在工作目录下创建了一个名为“clean”的文件,那就不一样了,当执行“make clean”的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,我们预先设想的清理工程的功能就无法完成。为避免此问题,就可以将 clean 声明为伪目标,声明方式如下面代码中的  .PHONT:clean。声明 clean 为伪目标以后不管当前目录下是否存在名为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行。

objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
    
.PHONY : clean

%.o : %.c
    gcc -c $<

clean:
    rm *.o
    rm main
8.条件判断

在 C 语言中我们通过条件判断语句来根据不同的情况来执行不同的分支, Makefile 也支持条件判断,两种语法如下:

<条件关键字>
    <条件为真时执行的语句>
endif
<条件关键字>
    <条件为真时执行的语句>
else
    <条件为假时执行的语句>
endif

条件关键字有 4 个: ifeq、 ifneq、 ifdef 和 ifndef。这四个关键字分为两对: ifeq 与ifneq, ifdef 与 ifndef。

ifeq 和 ifneq用法: ifeq 用来判断是否相等, ifneq 就是判断是否不相等, ifeq 用法如下:

ifeq (<参数 1>, <参数 2>)
ifeq ‘<参数 1 >’ ,‘ <参数 2>’
ifeq “<参数 1>” , “<参数 2>”
ifeq “<参数 1>” , ‘<参数 2>’
ifeq ‘<参数 1>’ , “<参数 2>”

都是用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和“参数 2”可以为函数返回值。 ifneq 的用法类似,用来比较“参数 1”和“参数 2”是否不相等,如果不相等的话就为真。

ifdef 和 ifndef 的用法:ifdef   <变量名>
如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是一个函数的返回值。 ifndef 用法类似,但是含义与 ifdef 相反。

9.Makefile函数

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

$(函数名 参数集合)  或者  ${函数名 参数集合}
调用函数和调用普通变量一样,使用符号“$”来标识。参数集合是函数的多个参数,参数之间以逗号 “,” 隔开,函数名和参数之间以“空格”分隔开,函数的调用以“$”开头。

几个常用的Makefile函数:

函数 subst:用来完成字符串替换,调用形式:$(subst  <from>,<to>,<text>)
此函数的功能是将字符串<text>中的<from>内容替换为<to>,函数返回被替换以后的字符串,比如:$(subst  lyp,LYP,my name is lyp),把字符串“my name is lyp”中的“lyp”替换为“LYP”,替换完成以后的字符串为“my nameis LYP”。

函数 patsubst:用来完成模式字符串替换,使用方法:$(patsubst  <pattern>,<replacement>,<text>)
此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就用<replacement>来替换<text>, <pattern>可用通配符“%”,表示任意长度字符串,函数返回值是替换后的字符串。<replacement>中也可用通配符“%”,那么<replacement>中的“%”将是<pattern>中的那个“%”所代表的字符串。比如:$(patsubst  %.c,%.o,a.c b.c c.c),将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字符串为“a.o b.o c.o”。

函数 dir :用来获取目录,使用方法:$(dir  <names…>)
此函数用来从文件名序列<names>中提取出目录部分,返回值是文件名序列<names>的目录部分,比如:$(dir </src/a.c>),提取文件 “/src/a.c” 的目录部分,也就是“/src”。

函数 notdir :去除文件中的目录部分,也就是提取文件名,用法:$(notdir  <names…>)
此函数用与从文件名序列<names>中提取出文件名非目录部分,比如:$(notdir  </src/a.c>),提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。

函数 foreach :用来完成循环,用法如下:$(foreach  <var>, <list>,<text>)
此函数的意思就是把参数<list>中的单词逐一取出来放到参数<var>中,然后再执行运算<text>所包含的表达式。每次<text>都会返回一个字符串,循环的过程中,<text>中所包含的每个字符串会以空格隔开,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串将会是函数foreach函数的返回值。

函数 wildcard:类似规则中“%”,通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,通配符不会自动展开,此时就要用到函数 wildcard,使用方法:$(wildcard  PATTERN…)。比如:$(wildcard   *.c)上面的代码是用来获取当前目录下所有的.c 文件。

写在最后:希望本文可以帮助到各位读者,文章如有不足,欢迎大家指出,如果文章帮到你了,请一定帮忙点个赞哦。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值