Linux网络编程第2章Linux编程环境


前言: 在Linux上开发的时候,我们首先要了解Linux下的编译器熟知,知道GCC的编译、链接过程,知道怎么写Makefile文件,知道怎么使用GDB进行调试代码。

2.1 Linux环境下的编辑器

2.1.1 Vim使用简介

1.Vim安装
UNIX系统下最通用的文本编辑器。首先进行安装。

# sudo apt-get install vim

2.Vim编辑器的模式
Vim主要分为普通模式和插入模式。
在普通模式中,输入的值就代表一个命令。比如在在普通模式下输入h键,光标会向左移动一个字符的位置。
输入i键会切换到插入模式,进行文本的编辑。当输入esc键的时候,会从插入模式切换到普通模式。

2.1.2 使用Vim建立文件

1.建立一个文件

# vim hello.c

2.进入插入模式
输入i键,在窗口下端会显示“–INSERT–”,这表示当前模式为插入模式。
在输入文本的时候,最下边有一个指示框,告诉用户正在编辑文件的一些信息,例如:
在这里插入图片描述
表示当前模式为插入模式,光标在第6行第11个字符位置上。
3.文本输入
在编辑区输入以下文本

#include<stdio.h>

int mian(void)
{
	printf("hello world!\n");
	return 0;
}

4.退出vim
当插入完成之后,按esc键返回到普通模式。然后输入“:wq”。退出编辑器。

发现当前目录下已经存在一个名为hello.c的文件。输入的wq是“保存后退出”的意思:q表示退出,w表示保存。当不想保存所作的修改时,输入“:”键后,再输入“q!”,Vim会直接退出,不保存所作修改。q!是强制退出的意思。

Vim有一个学习的帮助工具——vimtutor,它是很有帮助的一个工具,初学者可以使用它进行Vim的练习。输入“vimtutor”后,可以按照它的指示由浅入深地进行学习。

2.1.3 使用Vim编辑文本

Vim的编辑命令有很多,本节选取几个经常使用的命令作为介绍。如何在Vim下移动光标,进行删除字符、复制、查找、跳转等操作。

1.移动光标h、j、k、l

Vim在普通模式下移动光标是需要按特定的键,进行左、下、上、右光标移动的字符分别为h、j、k、l这四个字符。
在这里插入图片描述
按h键,光标左移一个字符的位置;按l键,光标右移一个字符的位置;按k键;光标上移一行;按j键,光标下移一行。

当然还可以用方向键移动光标,但必须将手从字母键位置上移动到方向键上,这样会减慢输入速度。而且,有一些键盘是没有方向键的,或者需要特殊的操作才能使用方向键(例如必须按组合键)。所以,知道用h、j、k、l字符移动光标的用法是很有帮助的。

2.删除字符x、dd、u、CTRL+R

要删除一个字符可以使用x,在普通模式下,将光标移到需要删除的字符上面然后按x键。例如hello.c的第一行输入有一个错误:

在这里插入图片描述

将光标移动到I上,然后按x键,切换输入模式到插入模式,输入i,对include的修正完成了。

要删除一整行可以使用dd命令,删除一行后,它后面的一行内容会自动向上移动一行。使用这个命令的时候要注意输入d的个数,两个d才是一个命令,在实际使用过程中经常将d的输入个数弄混淆。

恢复删除可以使用u。当删除了不应该删除的东西后,u命令可以取消之前的删除。例如,用dd命令删除一行,再按u键,恢复被删除的该行字符。

Ctrl+r是一个特殊的命令,它为取消一个命令,可以使用它对u命令造成的后果进行弥补。例如使用u命令撤销了之前的输入,重新输入字符是很麻烦的,而使用Ctrl+r可以十分方便地将之前使用u命令撤销输入的字符重新找回。

3.复制粘贴p、y

Vim的粘贴命令是p,它的作用是将内存中的字符复制到当前光标的后面。
例如当我们使用dd删除一行字符时,使用u键进行了恢复,那么这时内存中的字符就是刚才恢复的字符。当使用p键时,那么将在光标后面复制内存中的字符。
y命令(即yank)是复制命令,将指定的字符串复制到内存中,yw命令(即yank words)用于复制单词,可以指定复制的单词数量,y2w复制两个单词。例如下一行代码:

#include <stdio.h>

光标位于此行的头部,当输入y2w时字符串#include就复制到内存中,按p键后,此行如下:

##include include<stdio.h>

我们同样可以使用yy进行复制整行字符,然后按p进行粘贴。

4.查找字符串“/”
在普通模式下,首先输入一个"/"键,那么后面的字母就是要搜索的关键词。

/hello 

那么就代表的是在文本中搜索hello词。查找其他的匹配字符串可以输入字符“n”向下移到一个匹配的字符串上,输入字符“N”则会向上移到一个匹配的字符串上。

5.跳到某一行g
在普通模式中,当我们需要跳转到第6行的时候。有两种实现方式。
方法一,使用“:n”,n为跳转的行数。

:6

此刻光标便跳转到了第6行。
方法二,使用nG,n为跳转的行数。

6G

此刻便跳转到了第6行。注意其中的G为大写。

2.1.4 Vim的格式设置

1.设置缩进
需要对C语言继续缩进,那么需要设定cindent选项;如果需要设定下一行的缩进长度可以设置shiftwidth选项。如。

:set cindent shiftwidth=4

设定了这一选项之后,当输入一行语句后,Vim会自动在下一行进行缩进。
在这里插入图片描述
动缩进还能提前发现代码中的错误。例如当输入了一个“}”后,如果发现比预想中的缩进多,那可能缺少了一个“}”。用%命令可以查找与“}”相匹配的“{”。
2.设置tab键的空格数量
使用命令“:set tabstop=n”可以设置tab键对应的空格数量。例如:

:set tabstop=4

那么将tab键的宽度设置为了4个空格。
3.设置行号
在程序中设置行号使程序更加一目了然,设置行号的命令是“:set number”,按下Enter键后程序每行代码的头部会有一个行号的数值。

2.1.5 Vim配置文件.vimrc

我们可以根据自己的习惯来更改自己的vimrc配置文件。这样每次在使用vim进行编辑的时候都可以自动加载配置文件了。
Vim启动的时候会根据~/.vimrc文件配置vi的设置。
例如:

# vim ~/.vimrc

在这里插入图片描述
再次启动Vim,对缩进宽度、Tab键的宽度都进行了设定,并且会自动显示行号.

2.1.6 使用其他编辑器

在使用其他编辑器编辑文件的时候,注意要保存成UNIX格式的。这主要是换行符导致的,在windows下的换行为“回车+换行”。而UNIX环境下的换行为单个的换行。在Linux下用Vim查看会发现每行的末尾有一个很奇怪的“~”。如果没有保存为UNIX的格式,在Linux下可以使用dos2UNIX转换。

dos2unix命令是用来将DOS格式的文本文件转换成UNIX格式的。

# dos2unix hello.c

运行完之后,显示dos2unix: 正在转换文件 hello.c 为Unix格式…

2.2 Linux下的GCC编译器工具集

2.2.1 GCC简介

GCC是Linux下的编译工具集,是GNU Compiler Collection的缩写,包含gcc、g++等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如ar、nm等。
GCC工具集可以编译很多语言c、c++、Objective-C、Pascal、Fortran、Java、Ada等语言都可以编译。GCC还可以进行交叉编译。
GCC支持默认扩展名策略,表2.1是GCC下默认文件扩展名的含义。
表2.1 文件扩展名含义
表2.1 文件扩展名含义
GCC下有很多编译器,可以支持C语言、C++语言等多种语言,表2.2是常用的几个编译器。
表2.2 GCC编译器含义
在这里插入图片描述
进行程序编译的时候,头文件路径和库文件路径是编译器默认查找的地方,参见表2.3。
表2.3 默认路径
在这里插入图片描述

2.2.2 编译程序的基本知识

GCC编译器对程序的编译如图2.1所示,分为4个阶段:预编译、编译和优化、汇编和链接。GCC的编译器可以将这4个步骤合并成一个。
在这里插入图片描述
源文件、目标文件和可执行文件是编译过程中经常用到的名词。源文件通常指存放可编辑代码的文件,如存放C、C++和汇编语言的文件。目标文件是指经过编译器的编译生成的CPU可识别的二进制代码,但是目标文件一般不能执行,因为其中的一些函数过程没有相关的指示和说明。可执行文件就是目标文件与相关的库链接后的文件,它是可以执行的。

预编译过程将程序中引用的头文件包含进源代码中,并对一些宏进行替换。

编译过程将用户可识别的语言翻译成一组处理器可识别的操作码,生成目标文件,通常翻译成汇编语言,而汇编语言通常和机器操作码之间是一种一对一的关系。GNU中有C/C++编译器GCC和汇编器as。

所有的目标文件必须用某种方式组合起来才能运行,这就是链接的作用。目标文件中通常仅解析了文件内部的变量和函数,对于引用的函数和变量还没有解析,这需要将其他已经编写好的目标文件引用进来,将没有解析的变量和函数进行解析,通常引用的目标是库。链接完成后会生成可执行文件。

2.2.3 单个文件编译成执行文件

使用GCC编译器编译单个文件很简单,使用gcc命令再加上源文件就可以了。不指定生成文件名的话,自动生成a.out的可执行文件。自动编译的过程包括头文件扩展、目标文件编译,以及链接默认的系统库生成可执行文件,最后生成系统默认的可执行程序a.out。
例如:

# gcc hello.c

GCC将采取默认步骤,先将C文件编译成目标文件,然后将目标文件链接成可执行文件,最后删除目标文件。上述命令没有指定生成执行文件的名称,GCC将生成默认的文件名a.out。运行结果如下:

# ./a.out
hello world!

如果希望生成指定的可执行文件名,选项-o可以使编译程序生成指定文件名,例如将上述程序编译输出一个名称为test的执行程序:

# gcc -o test hello.c

2.2.4 编译生成目标文件

目标文件是指经过编译器的编译生成的CPU可识别的二进制代码,因为其中一些函数过程没有相关的指示和说明,目标文件不能执行。

在2.2.3节中介绍了直接生成可执行文件的编译方法,在这种编译方法中,中间文件作为临时文件存在,在可执行文件生成后,会删除中间文件。在很多情况下需要生成中间的目标文件,用于不同的编译目标。

GCC的-c选项用于生成目标文件,这一选项将源文件生成目标文件,而不是生成可执行文件。默认情况下生成的目标文件的文件名和源文件的名称一样,只是扩展名为.o。例如,下面的命令会生成一个名字为hello.o的目标文件:

# gcc -c hello.c

如果需要生成指定的文件名,可以使用-o选项。下面的命令将源文件hello.c编译成目标文件,文件名为test.o:

# gcc -c -o test.o hello.c

可以用一条命令编译多个源文件,生成目标文件,这通常用于编写库文件或者一个项目中包含多个源文件。例如一个项目包含file1.c、file2.c和file3.c,下面的命令可以将源文件生成3个目标文件:file1.o、file2.o和file3.o:

# gcc -c file1.c file2.c file3.c

2.2.5 多文件编译

GCC可以自动编译链接多个文件,不管是目标文件还是源文件,都可以使用同一个命令编译到一个可执行文件中。
1. 源文件string.c
文件string.c的内容如下。文件中主要包含了用于计算字符串长度的函数StrLen()。StrLen()函数的作用是计算字符串的长度,输入参数为字符串的指针,输出数值为计算字符串长度的计算结果。StrLen()函数将字符串中的字符与’\0’进行比较并进行字符长度计数,获得字符串的长度。
在这里插入图片描述
2. 源文件main.c
在文件main.c中是main()函数的代码,如下代码所示。main()函数调用Strlen()函数计算字符串Hello Dymatic的长度,并将字符串的长度打印出来。
在这里插入图片描述
3. 编译运行
下面的命令将两个源文件中的程序编译成一个执行文件,文件名为test。

# gcc -o test string.c main.c

执行编译出来的可执行文件test,程序的运行结果如下:

# ./test
String length is:13

当然可以先将源文件编成目标文件,然后进行链接。例如,下面的过程先将string.c和main.c源文件编译成目标文件string.o和main.o,然后将string.o和main.o链接生成test:

# gcc -c string.c main.c
# gcc -o test string.o main.o

2.2.6 预处理

在C语言程序中,通常需要包含头文件并会定义一些宏。预处理过程将源文件中的头文件包含进源文件中,并且将文件中定义的宏进行扩展。

一句话就是对#的进行扩展处理,将头文件和宏定义进行扩展填充。

编译程序时选项-E告诉编译器进行预编译操作。例如如下命令将文件string.c的预处理结果显示在计算机屏幕上:

# gcc -E string.c

如果需要指定源文件预编译后生成的中间结果文件名,需要使用选项-o。例如,下面的代码将文件string.c进行预编译,生成文件string.i。string.i内容如下:
在这里插入图片描述
可以发现之前定义的宏ENDSTRING,已经被替换成了“\0”。

预处理之后是.i文件。

2.2.7 编译成汇编语言

编译过程将用户可识别的语言翻译成一组处理器可识别的操作码,通常翻译成汇编语言。汇编语言通常和机器操作码之间是一对一的关系。

生成汇编语言的GCC选项是-S,默认情况下生成的文件名和源文件一致,扩展名为.s。例如,下面的命令将C语言源文件string.c编译成汇编语言,文件名为string.s。


# gcc -S string.c

下面是编译后的汇编语言文件string.s的内容。其中,第1行内容是C语言的文件名,第3行和第4行是文件中的函数描述,标签StrLen之后的代码用于实现字符串长度的计算。
在这里插入图片描述
总结:
编译预处理使用-E 之后生成的是.i文件
编译、优化使用-S之后生成.s文件(汇编语言)
汇编使用-c之后生成.o目标文件
链接生成可执行文件

2.2.8 生成和使用静态链接库

静态库是obj文件的一个集合,通常静态库以“.a”为后缀。静态库由程序ar生成,现在静态库已经不像之前那么普遍了,这主要是由于程序都在使用动态库。

静态库的优点是可以在不用重新编译程序库代码的情况下,进行程序的重新链接,这种方法节省了编译过程的时间(在编译大型程序的时候,需要花费很长时间)。但是由于现在系统的强大,编译的时间已经不是问题。静态库的另一个优势是开发者可以提供库文件给使用的人员,不用开放源代码,这是库函数提供者经常采用的手段。当然这也是程序模块化开发的一种手段,使每个软件开发人员的精力集中在自己的部分。在理论上,静态库的执行速度比共享库和动态库要快(1%~5%)。

总结一下用静态链接库的好处:后缀通常是.a,由ar生成。不用重新编译链接静态库,静态库的执行速度要更快一些。

  1. 生成静态链接库
    生成静态库,或者将一个obj文件加到已经存在的静态库的命令为“ar库文件obj文件1 obj文件2”。创建静态库的最基本步骤是生成目标文件,这点前面已经介绍过。然后使用工具ar对目标文件进行归档。工具ar的-r选项,可以创建库,并把目标文件插入到指定库中。例如,将string.o打包为库文件libstr.a的命令为:
# ar -rcs libstr.a string.o
  1. 使用静态链接库
    在编译程序的时候经常需要使用函数库,例如经常使用的C标准库等。GCC链接时使用库函数和一般的obj文件的形式是一致的,例如对main.c进行链接的时候,需要使用之前已经编译好的静态链接库libstr.a,命令格式如
# gcc -o test main.c libstr.a

也可以使用命令“-l库名”进行,库名是不包含函数库和扩展名的字符串。例如编译main.c链接静态库libstr.a的命令可以修改为:

# gcc -o test main.c -lstr

上面的命令将在系统默认的路径下查找str函数库,并把它链接到要生成的目标程序上。可能系统会提示无法找到库文件str,这是由于str库函数没有在系统默认的查找路径下,需要显示指定库函数的路径,例如库文件和当前编译文件在同一目录下:

# gcc -o test main.c -L ./ -lstr

注意:在使用-l选项时,-o选项的目的名称要在-l链接的库名称之前,否则gcc会认为-l是生成的目标而出错。

2.2.9 生成动态链接库

动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。

动态链接库的名称有别名(soname)、真名(realname)和链接名(linker name)。别名由一个前缀lib,然后是库的名字,再加上一个后缀“.so”构成。真名是动态链接库的真实名称,一般总是在别名的基础上加上一个小版本号、发布版本等构成。除此之外,还有一个链接名,即程序链接时使用的库的名字。在动态链接库安装的时候,总是复制库文件到某个目录下,然后用一个软链接生成别名,在库文件进行更新的时候,仅仅更新软链接即可。

  1. 生成动态链接库
    生成动态链接库的命令很简单,使用-fPIC选项或者-fpic选项。-fPIC和-fpic选项的作用是使得gcc生成的代码是位置无关的,例如下面的命令将string.c编译生成动态链接库:
# gcc -shared -Wl,-soname,libstr.so -o libstr.so.1 string.c

其中,选项“-soname,libstr.so”表示生成动态库时的别名是libstr.so;“-o libstr.so.1”选项则表示是生成名字为libstr.so.1的实际动态链接库文件;-shared告诉编译器生成一个动态链接库。
生成动态链接库之后一个很重要的问题就是安装,一般情况下将生成的动态链接库复制到系统默认的动态链接库的搜索路径下,通常有/lib、/usr/lib、/usr/local/lib,放到以上任何一个目录下都可以。

  1. 动态链接库的配置
    动态链接库不能随意使用,要在运行的程序中使用动态链接库,需要指定系统的动态链接库搜索的路径,让系统找到运行所需的动态链接库才可以。系统中的配置文件/etc/ld.so.conf是动态链接库的搜索路径配置文件。在这个文件内,存放着可被Linux共享的动态链接库所在目录的名字(系统目录/lib、/usr/lib除外),多个目录名间以空白字符(空格、换行等)或冒号或逗号分隔。查看系统中的动态链接库配置文件的内容:
# cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf

Ubuntu的配置文件将目录/etc/ld.so.conf.d中的配置文件包含进来,对这个目录下的文件进行查看:

# ls /etc/ld.so.conf.d/
  1. 动态链接库管理命令
    为了让新增加的动态链接库能够被系统共享,需要运行动态链接库的管理命令ldconfig。ldconfig命令的作用是在系统的默认搜索路径,和动态链接库配置文件中所列出的目录里搜索动态链接库,创建动态链接装入程序需要的链接和缓存文件。搜索完毕后,将结果写入缓存文件/etc/ld.so.cache中,文件中保存的是已经排好序的动态链接库名字列表。ldconfig命令行的用法如下,其中选项的含义参见表2.4。
ldconfig 参数

在这里插入图片描述

如果想知道系统中有哪些动态链接库,可以使用ldconfig的-p选项来列出缓存文件中的动态链接库列表。下面的命令中表明在系统缓存中共有682个动态链接库。

# ldconfig -p	//列出当前系统中的动态链接库

使用ldconfig命令,默认情况下并不将扫描的结果输出。使用-v选项会将ldconfig在运行过程中扫描到的目录和共享库信息输出到终端,用户可以看到运行的结果和中间的信息。在执行ldconfig后,将刷新缓存文件/etc/ld.so.cache。

# ldconfig -v

当用户的目录并不在系统动态链接库配置文件/etc/ld.so.conf中指定的时候,可以使用ldconfig命令显示指定要扫描的目录,将用户指定目录中的动态链接库放入系统中进行共享。命令格式的形式为:

# ldconfig 目录名

这个命令将ldconfig指定的目录名中的动态链接库放入系统的缓存/etc/ld.so.cache中,从而可以被系统共享使用。下面的代码将扫描当前用户的lib目录,将其中的动态链接库加入系统:

# ldconfig ~/lib

如果在运行上述命令后,再次运行ldconfig而没有加参数,系统会将/lib、/usr/lib及/etc/ld.so.conf中指定目录中的动态库加入缓存,这时候上述代码中的动态链接库可能不被系统共享了。

  1. 使用动态链接库
    在编译程序时,使用动态链接库和静态链接库是一致的,使用“-l库名”的方式,在生成可执行文件的时候会链接库文件。例如下面的命令将源文件main.c编译成可执行文件test,并链接库文件libstr.a或者libstr.so:
# gcc -o test main.c -L ./ -lstr

-L指定链接动态链接库的路径,-lstr链接库函数str。但是运行test一般会出现如下问题:
在这里插入图片描述
这是由于程序运行时没有找到动态链接库造成的。程序编译时链接动态链接库和运行时使用动态链接库的概念是不同的,在运行时,程序链接的动态链接库需要在系统目录下才行。有以下几种办法可以解决此问题。
2. [ ] 将动态链接库的目录放到程序搜索路径中,可以将库的路径加到环境变量LD_LIBRARY_PATH中实现,例如:

# export LD_LIBRARY_PATH=/example/ex02 :LD_LIBRARY_PATH

将存放库文件libstr.so的路径/example/ex02加入到搜索路径中,再运行程序就没有之前的警告了。
3. [ ] 另一种方法是使用ld-Linux.so.2来加载程序,命令格式为:

# /lib/ld-Linux.so.2 --library-path  路径 程序名

加载test程序的命令为:

# /lib/ld-Linux.so.2 --library-path /example/ex02 test

注意:如果系统的搜索路径下同时存在静态链接库和动态链接库,默认情况下会链接动态链接库。如果需要强制链接静态链接库,需要加上“-static”选项,即上述的编译方法改为如下的方式:

# gcc -o test main.c -static -lstr

2.2.10 动态加载库

动态加载库和一般的动态链接库所不同的是,一般动态链接库在程序启动的时候就要寻找动态库,找到库函数;而动态加载库可以用程序的方法来控制什么时候加载。动态加载库主要有函数dlopen()、dlerror()、dlsym()和dlclose()。

  1. 打开动态库dlopen()函数
    函数dlopen()按照用户指定的方式打开动态链接库,其中参数filename为动态链接库的文件名,flag为打开方式,一般为RTLD_LASY,函数的返回值为库的指针。其函数原型如下:
void* dlopen(const char* filename, int flag);

例如,下面的代码使用dlopen打开当前目录下的动态库libstr.so。

void* phandle =dlopen("./libstr.so",PTLD_LAZY);
  1. 获得函数指针dlsym()
    使用动态链接库的目的是调用其中的函数,完成特定的功能。函数dlsym()可以获得动态链接库中指定函数的指针,然后可以使用这个函数指针进行操作。函数dlsym()的原型如下:
void* dlsym(void* handle,char* symbol);

其中参数handle为dlopen()打开动态库后返回的句柄,参数symbol为函数的名称,返回值为函数指针。

  1. 使用动态加载库的一个例子
    下面是一个动态加载库使用的例子。首先使用函数dlopen()来打开动态链接库,判断是否正常打开,可以使用函数dlerror()判断错误。如果上面的过程正常,使用函数dlsym()来获得动态链接库中的某个函数,可以使用这个函数来完成某些功能。其代码如下:
    在这里插入图片描述
    编译上述文件的时候需要链接动态库libdl.so,使用如下的命令将上述代码编译成可执行文件testdl。命令将main.c编译成可执行文件testdl,并链接动态链接库libdl.so
# gcc -o testdl main.c libstr.so -ldl

执行文件testdl的结果为:

# ./testdl
string length is:13

使用动态加载库和动态链接库的结果是一致的。

2.2.11 GCC常用选项

除了之前介绍的基本功能外,GCC的选项配置是编译时很重要的选择,例如头文件路径、加载库路径、警告信息及调试等。本节将对常用的选项进行介绍。

  • -DMACRO选项
    定义一个宏,在多种预定义的程序中会经常使用。如下代码根据系统是否定义Linux宏来执行不同的代码。使用-D选项可以选择不同的代码段,例如-DOS_LINUX选项将执行代码段①。
#ifdef OS_LINUX
...代码段1
#else
...代码段2
#endif
  • -Idir:将头文件的搜索路径扩大,包含dir目录。

  • -Ldir:将链接时使用的链接库搜索路径扩大,包含dir目录。gcc都会优先使用共享程序库。

  • -static:仅选用静态程序库进行链接,如果一个目录中静态库和动态库都存在,则仅选用静态库。

  • -g:包括调试信息。

  • -On:优化程序,程序优化后执行速度会更快,程序的占用空间会更小。通常gcc会进行很小的优化,优化的级别可以选择,即n。最常用的优化级别是2。

  • -Wall:打开所有gcc能够提供的、常用的警告信息。

  1. GCC的常用选项以及含义
    表2.5中是GCC的常用选项和含义,主要列出了警告选项、代码检查、ANSI兼容等。可以在编译程序的时候对GCC的选项进行设置,编写质量高的代码。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述注意:在编写代码的时候,不好的习惯会造成程序执行过程中发生错误。在一个比较大的项目中,当程序运行起来后再查找这些错误是很困难的。因此一种好的习惯是使用编译选项将代码的警告信息显示出来,并对代码进行改正。例如,打开编译选项-Wall和-W来显示所有的警告信息,甚至更严格一些,打开-Werror将编译时的警告信息作为错误信息来处理,中断编译。

2.2.12 编译环境的搭建

目前最新的GCC编译器的版本为gcc-4.8.0。在安装Ubuntu的时候,默认情况下会安装GCC。读者可以使用which命令来查看系统中是否已经安装了GCC:

# which gcc

如果不存在,使用apt进行升级,获得gcc包并且安装:

# apt-get install gcc

如果读者对C++感兴趣可以安装g++。在编译器安装完毕后,可以使用GCC进行程序的编译。

2.3 Makefile文件简介

当源文件变多的时候,使用GCC编译太麻烦了,所以使用了一种管理工程的功能,可以方便地进行程序的编译,对更新的文件进行重新编译。

2.3.1 一个多文件的工程例子

有一个工程中的文件列表如图2.2所示。工程中共有5个文件,在add目录中有add_int.c和add_float.c,两个文件分别计算整型和浮点型的相加;在sub目录下有文件sub_int.c和sub_float.c,分别计算整型和浮点型的相减;顶层目录有main.c文件负责整个程序。
在这里插入图片描述
工程中的代码分别存放在add/add_int.c、add/add_float.c、add/add.h、sub/sub_int.c、sub/sub_float.c、sub/sub.h和main.c中。

  1. 文件main.c
    文件main.c的代码如下。在main()函数中调用整数、浮点的加减运算函数进行数值计算。
    在这里插入图片描述
  2. 加操作
    文件add.h的代码如下,包含整数和浮点数的求和函数声明。
    在这里插入图片描述文件add_float.c的代码如下,函数add_float()进行浮点型数值的相加计算。
    在这里插入图片描述
    文件add_int.c的代码如下,函数add_int()进行整数型数值的相加计算。
    在这里插入图片描述
  3. 减操作
    文件sub.h的代码如下,包含整数和浮点数的相减函数声明:
    在这里插入图片描述
    文件sub_int.c的代码如下,函数sub_int()进行整型的相减计算。
    在这里插入图片描述文件sub_float.c的代码如下,函数sub_float()进行浮点型的相减计算。
    在这里插入图片描述

2.3.2 多文件工程的编译

  1. 多文件的Makefile
    我们使用make进行项目管理,需要一个Makefile文件,make在进行编译的时候,从Makefile文件中读取设置情况,进行解析后运行相关的规则。make程序查找当前目录下的文件Makefile或者makefile,按照其规则运行。例如,建立一个如下规则的Makefile文件。
    在这里插入图片描述
  2. 多文件的编译
    编译多文件的项目,在上面Makefile文件编写完毕后,运行make命令:
    在这里插入图片描述
    在add目录下生成了add_int.o和add_float.o两个目标文件,在sub目录下生成了sub_int.o和sub_float.o两个文件,在主目录下生成了main.o目标文件,并生成了cacu最终文件。

默认情况下会执行Makefile中的第一个规则,即cacu相关的规则,而cacu规则依赖于多个目标文件add_int.o、add_float.o、sub_int.o、sub_float.o、main.o。编译器会先生成上述目标文件后执行下述命令:

# (CC) -o $(TARGET) $(OBJS) $(CFLAGS)

上述命令将多个目标文件编译成可执行文件cacu,即:
在这里插入图片描述
命令make clean会调用clean相关的规则,清除编译出来的目标文件,以及cacu。例如:
在这里插入图片描述
clean规则会执行“- ( R M ) (RM) (RM)(TARGET)$(OBJS)”命令,将定义的变量扩展后为:
在这里插入图片描述

2.2.3 Makefile的规则

Makefile的框架是由规则构成的。make命令执行时先在Makefile文件中查找各种规则,对各种规则进行解析后运行规则。规则的基本格式为:
在这里插入图片描述

  • TARGET:规则所定义的目标。通常规则事最后生成的可执行文件或者为了生成可执行文件所产生的中间的的目标文件。
  • DEPENDENDS:执行此规则所依赖的文件。
  • COMMAND:实现规则所执行的命令行。

在执行的过程中,比如当要实现第一条规则的时候,发现所依赖的目标文件不存在,那么就执行产生目标文件的规则。规则之间事可以嵌套的。

执行的规则是从第一个规则开始分析的,从上往下依次嵌套执行完毕。当把规则clean放在第一个的时候,再执行make命令就不是生成目标文件了,而是清理文件。如果要生成文件的话需要使用如下命令:

# make cacu
  1. 模式匹配
    在上面的Makefile中,main.o规则的书写方式如下:
main.o:main.c add/add.h sub/sub.h
	gcc -c -o main.o main.c -Iadd -Isub

有一种简便的方法可以实现与上面相同的功能

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

这种方法的规则main.o中依赖项中的“%o:%c”的作用是将TARGET域的.o的扩展名替换为.c,即将main.o替换为main.c。而命令行的 < 表 示 依 赖 项 的 结 果 , 即 m a i n . c ; <表示依赖项的结果,即main.c; <main.c@表示TARGET域的名称,即main.o。

2.3.4 Makefile使用变量

我们在上面一节中通过对每个规则进行添加删除文件,很容易漏掉一些,也很麻烦。所以就使用变量对文件进行总结和替代。我们对文件进行处理的时候直接对变量操作即可。

  1. Makefile中的用户自定义变量
    使用Makefile进行规则定义的时候,用户可以定义自己的变量,称为用户自定义变量。例如,可以用变量来表示上述的文件名,定义OBJS变量表示目标文件:
    在这里插入图片描述
    调用OBJS的时候前面加上$,并且将变量的名称用括号括起来。例如,使用gcc的默认规则进行编译,cacu规则可以采用如下的形式:
    在这里插入图片描述用CC变量表示gcc,用CFLAGS表示编译的选项,RM表示rm–f,TARGET表示最终的生成目标cacu。
    在这里插入图片描述
    之前冗长的Makefile可以简化成如下方式:
    在这里插入图片描述
  2. Makefile中的预定义变量
    在Makefile中有一些已经定义的变量,用户可以直接使用这些变量,不用进行定义。在进行编译的时候,某些条件下Makefile会使用这些预定义变量的值进行编译。经常采用的预定义变量如表2.6所示
    在这里插入图片描述
    在Makefile中经常用变量CC来表示编译器,其默认值为cc,即使用cc命令来进行C语言程序的编译;在进行程序删除的时候经常使用命令RM,它的默认值为rm –f。

另外还有CFLAGS等默认值是调用编译器时的选项默认配置,例如修改后的Makefile生成main.o时,没有指定编译选项,make程序自动调用了文件中定义的CFLAGS选项-Iadd-Isub-O2来增加头文件的搜索路径,在所有的目标文件中都采用了此设置。经过简化后,之前的Makefile可以采用如下形式:
在这里插入图片描述
上面的Makefile中clean目标中的 ( R M ) (RM) (RM)(TARGET)$(OBJS)之前的符号“-”表示当操作失败时不报错,命令继续执行。如果当前目录不存在cacu时,它会继续删除其他的目标文件。例如下面的clean规则在没有cacu文件时会报错。

  1. Makefile中的自动变量
    Makefile中的变量除了用户自定义变量和预定义变量外,还有一类自动变量。Makefile中的编译语句中经常会出现目标文件和依赖文件,自动变量代表这些目标文件和依赖文件。表2.7中是一些常见的自动变量。
    在这里插入图片描述
    按照表2.7中的说明对Makefile进行重新编写,Makefile的代码如下:
CFLAGS = -Iadd -Isub -O2
OBJS = add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o
TARGET = cacu
$(TARGET):$(OBJS)
	$(CC) -o $@ $^ $(CFLAGS)
clean:
	-$(RM) $(TARGET) $(OBJS)

2.3.5 搜索路径

在系统中加入搜索路径

VPATH= path1:path2

使用:进行分割开,make的搜索路径包含path1和path2目录。

2.3.6 自动推导规则

没啥可讲的

2.3.7 递归make

2.4 GDB调试

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值