编译器的使用
首先安装编译器(如果你跟着实验三做了的话,可以跳过这一步):
# apt-get install build-essential
之后执行 which
指令确认 gcc
已经安装:
$ which gcc
应该输出类似 /usr/bin/gcc
这样的内容。如果没有输出任何内容,说明你没有安装 gcc
.
编译和运行
不要在 /home
下自行建立任何东西!家目录指 /home/<你的用户名>/
下。所以之后凡是文档中出现“在 home 目录下新建”一律视为在家目录下新建即可。
要编译 C 语言程序,可以使用如下指令:
$ gcc <编译参数...?> <源程序...>
要将源程序编译输出到一个文件当中,在编译参数中添加 -o <文件名?=a.out>
. 如果目标程序文件存在,则会覆盖对应文件。
要执行当前目录下的可执行文件,需要显式指明该程序在当前目录下,如:
$ ./a.out
默认一些编译警告是关闭的。要让编译器显示全部警告,可在编译参数中添加 -Wall
.
有时我们需要独立编译各个文件,最后再进行汇总。这时可以在编译参数中添加 -c
要求编译器不自动运行链接器而是生成目标文件 (Object file).
有时我们需要指定引入外部的头文件(尽管理论上可以通过 #include "file.h"
并扔在和 file.c
相同目录下来避免,但是这样显得不美观),则可以在编译参数中添加 -I<路径>
表示在编译时也应包含此路径下的头文件。
我们可以汇总多个目标文件生成一个可执行文件,直接将源程序替换成所有 .o
文件即可。
有时我们希望能够将代码创建为一个静态链接库以便日后使用而不需要重新编译整个源码,那么我们可以首先在编译参数中添加 -fPIC
表示要求生成地址无关代码(Position Independet Code),并输出为目标文件 (-c
). 当我们得到对应的 .o
文件后,再使用 ar
工具整合目标文件,如下:
$ ar rcs <输出文件> <目标文件...>
$ ar rcs libsomething.a lib.o lib2.o lib3.o
我们约定输出文件的扩展名为 .a
. 要在其他程序编译时使用这个库,则需要在编译参数中添加 -L<库文件查找路径>
包含对应 .a
文件所在目录,并指定 -l<不带扩展名和 lib 前缀的库文件名>
进行链接,如:
$ gcc -L<库文件路径> -l<库名> <源代码...>
$ gcc -L./libraries -lsomething mycode.c
现代编译器通常具有优化功能,要让编译器对代码在速度方面进行优化,可以在编译选项中添加 -O<数字>
来进行优化,数字越高代表优化等级越高(编译时间越长),有意义的数字为 0 到 3 (包含),其中 0 表示不进行任何优化;要希望编译器对代码在可执行文件大小方面进行优化,则使用 -Os
.
要保留调试信息以便之后使用调试器调试,则可以在编译选项中添加 -g
.
GDB 的使用
首先确定是否已经安装了 GDB:
$ which gdb
应该输出类似 /usr/bin/gdb
的信息。如果没有输出,则使用 apt
安装 gdb
:
# apt-get install gdb
GDB 是主流调试器之一(截至发稿时)。要调试一个程序,首先,在程序编译时保留调试信息( -g
),然后使用如下命令将可执行文件加载到 gdb
内:
gdb <文件名>
注意到 gdb
是没有图形界面的(说实话编译器都没有图形界面。你在 VS 里面用的调试器的图形界面也是 VS 自己解析调试器输出画出来的),需要使用命令方式。当然你也可以配置 VS Code 使用 gdb
, 或者使用 CLion, 或者自己找个 GDB 图形前端。
要打印源代码,用 list
指令,如下:
(gdb) list <行号?|函数名?>
如果不指定行号或函数名,则会默认打印当前执行位置附近的代码。
要在某一行添加一个断点,用 break
指令,如下:
(gdb) break <行号|函数名|地址>
这个指令会输出断点的位置及其编号。
要删除某一个断点,用 clear
指令,如下:
(gdb) clear <行号|函数名|地址>
要开始运行加载的程序,用 run
指令,如下:
(gdb) run <参数?>
如果指定了参数,则这些参数将作为被调试程序的运行参数。
当程序进入断点后将会被暂停,此时 (gdb)
提示符出现并等待操作,你也可以使用 [^c]
手动暂停程序。要继续执行程序,用 c
指令(continue
指令的简写);要单步调试 (step into),用 s
指令(step
指令的简写);要单行调试 (step over), 用 n
指令(next
指令的简写);要快速执行到当前函数结尾 (step out), 用 fin
指令(finish
指令的缩写)。这些指令都没有参数。
如果你觉得打字手疼,什么都不打直接按回车表示重复上一条指令。
要打印某个变量的值,用 display
指令,如下:
(gdb) display <变量名|表达式>
这里的表达式可以是一个 C 语言中的表达式,如:
(gdb) display i &% 5
(gdb) display arr[i]
(gdb) display myStruct->member
要退出 GDB, 使用 quit
指令。
make + Makefile
Makefile 由于历史原因,对白字符非常敏感。所以要注意。
按照约定,Makefile 的名字就叫 Makefile
. Linux 下文件是大小写敏感的。
请确定自己已经安装了 make
. 方式不再赘述。
一个 Makefile 的基本格式如下:
<变量>=<值>
<目标名称>: <依赖项目...>
<命令>
<...>
命令之前,必须使用 Tab 缩进而不能使用空格。要调用 make
开始编译,用如下格式:
$ make <目标名?>
如果不指定目标名,则会尝试编译第一个不以 .
开头的目标。
make
的好处在于如果你的 Makefile
写得正确的话,可以不需要每次都编译所有内容而只进行增量式更新;而且 make
还可以自动解析目标依赖;这些特性对于大工程而言十分必要。
make
还包含一些特殊变量:
-
$<
- 第一个依赖项目名 -
$^
- 所有依赖项目名 -
$@
- 目标名
目标名可以包含通配符,这样可以避免重复写一些无谓的代码。下面是一个用到了这种能力的 Makefile 示例(不要复制粘贴!手打!记得用 TAB):
SRC=main.c linkstack.c
OBJ=$(SRC:.c=.o) # 你可以用这个方式替换后缀名,你甚至可以将 HEAD 也做这样的替换
OUT=myprogram
all: $(OBJ)
gcc $(OBJ) -o $(OUT)
%.o: %.c %.h
# 注意到这里的 % 用于通配文件名
gcc -c $< -o $@
clean:
rm -rf $(OBJ)
git 和 github
首先,git
又是一个可以单独写书的工具。当然,我们只会用到这个工具的一小部分,不会的反正可以 Google. 如果你没法 Google, 就看廖雪峰吧。本着不重复他人的精神,请点开廖雪峰的网站链接,谢谢。