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

用户根目录下创建一个工作文件夹: C_Program,所有的 C 语言练习都保存到这个工作文件夹下

VIM编辑器设置

VI 编辑器默认 TAB 键为 8 空格,我们改成 4 空格,用 vi 打开文件/etc/vim/vimrc,在此文件最后面输入如下代码:

set ts=4

VIM 编辑器默认是不显示行号的,不显示行号不利于代码查看,我们设置 VIM 编辑器显示行号,同样是通过在文件/etc/vim/vimrc 中添加代码来实现,在文件最后面加入下面一行代码

set nu

可以使用“cat”命令查看代码是否编写成功

GCC 编译器在我们 Ubuntu 的时候就已经默认安装好了,可以通过如下命令查看 GCC 编译器的版本号:

gcc -v

Ubuntu 自带的 GCC 编译器是针对 X86 架构的,因此只能编译在 X86 架构 CPU 上运行的程序。如果想要编译在 ARM上运行的程序就需要针对 ARM 的 GCC 编译器,也就是交叉编译器

gcc是先编译然后链接成可执行文件,即生成a.out

gcc [选项] [文件名字]

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

GCC 编译器的编译流程是:预处理、编译、汇编和链接
预处理就是展开所有的头文件、替换程序中的宏、解析条件编译并添加到文件中。

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

汇编就是将汇编语言文件编译成二进制目标文件。

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

Q:C语言如何引入外部函数

外部函数的定义方式是在函数的返回值类型前面添加extern关键字,表明该函数可以被其他的源文件调用。当有源文件要调用其他源文件中的外部函数时,需要在本文件中使用extern关键字声明要调用的外部函数,示例代码如下:

extern int add(int x,int y); //add()函数是定义在其他源文件中的外部函数

C 头文件

头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。

在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。前面我们已经看过 stdio.h 头文件,它是编译器自带的头文件。

引用头文件相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。

A simple practice in C 或 C++ 程序中,建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件。

引用头文件的语法

使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:

#include <file>

这种形式用于引用系统头文件。它在系统目录的标准列表中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。

#include "file"

这种形式用于引用用户头文件。它在包含当前文件的目录中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。

只引用一次头文件

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

这种结构就是通常所说的包装器 #ifndef。当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。

多文件编译

文件名一定要叫做“Makefile”!!!区分大小写的哦!

常规: gcc main.c calcu.c input.c -o main

先编译不进行链接,然后再链接时.o文件不会被删除

错误来源一般有两点:

1、 Makefile 中命令缩进没有使用 TAB 键!

2、 VI/VIM 编辑器使用空格代替了 TAB 键

enter image description here

命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
make 命令会为 Makefile 中的每个以 TAB 开始的命令创建一个 Shell 进程去执行。

例如:

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,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为 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 的功能就是完成工程的清理,“make clean”的执行过程

首先查找目标,第一次执行make命令时,会在makefile中依次查找目标,如main.发现main没有,然后找依赖文件,依赖文件main.o等的依赖文件也不存在,查找main.c等,不存在此目标则执行命令.此时.o文件有了,再链接成可执行文件,总的来说先向下找再原路返回,

Makefile 变量

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

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

Makefile 中可以写注释,注释开头要用符号“#”
Makefile 中变量的引用方法是“$(变量名)”

变量如何连续赋值

在引入一个变量B,将其赋值给A,变量B可以进行连续赋值

name = zzk
curname = $(name)
name = zuozhongkai
print:
	@echo curname: $(curname)

在 Makefile 要输出一串字符的话使用“echo”
第 6 行中的“echo”前面加了个“@”符号,因为 Make 在执行的过程中会自动输出命令执行过程,在命令前面加上“@”的话就不会输出命令执行过程

赋值符“:=”

“:=” 不会使用后面定义的变量,只能使用前面已经定义好的。

Makefile 中的模式规则

“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的文件,类似与通配符, a.%.c 就表示以 a.开头,以.c 结束的所有文件

%.o : %.c
	#命令

命令我们需要借助另外一种强大的变量—自动化变量。

enter image description here

7 个自动化变量中,常用的三种: $@、 <和^

objects = main.o input.o calcu.o
main: $(objects)
	gcc -o main $(objects)
	
#声明伪目标
.PHONY : clean
%.o : %.c
	gcc -c $<
clean:
	rm *.o
	rm main

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

Makefile 条件判断

条件关键字有 4 个: ifeq、 ifneq、 ifdef 和 ifndef,这四个关键字其实分为两对、 ifeq 与ifneq、 ifdef 与 ifndef,先来看一下 ifeq 和 ifneq, ifeq 用来判断是否相等, ifneq 就是判断是否不相等

enter image description here

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

Makefile 函数使用

不支持我们自定义函数

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

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

1、函数 subst substitute

函数 subst 用来完成字符串替换

$(subst <from>,<to>,<text>)
#此函数的功能是将字符串<text>中的<from>内容替换为<to>
$(subst zzk,ZZK,my name is zzk)
#返回值就是my nameis ZZK

2、函数 patsubst pattern substitude

$(patsubst <pattern>,<replacement>,<text>)
$(patsubst %.c,%.o,a.c b.c c.c)
#替换完成以后的字符串为“a.o b.o c.o”

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

3、函数 dir

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

$(dir <names…>)
$(dir </src/a.c>)
#提取文件“/src/a.c”的目录部分,也就是“/src”。即绝对路径

4、函数 notdir

函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名

$(notdir <names…>)
$(notdir </src/a.c>)
#提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。

5、函数 foreach

foreach 函数用来完成循环

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

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

6、函数 wildcard

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

$(wildcard PATTERN…)
$(wildcard *.c)
#上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。