Linux 编程指南
1. 编译与链接程序
要将程序编译并链接成名为
hello
的可执行程序,可使用以下命令:
g++ -o hello hello.C
此命令会创建
hello
可执行文件,运行方式如下:
./hello
程序将显示以下输出:
Hello! This is Linux!
2. GCC 选项探索
gcc
命令的基本语法如下:
gcc options filenames
每个选项以连字符(
-
)开头,通常有长名称,如
-funsigned-char
或
-finline-functions
。不过,许多常用选项是短名称,例如
-c
仅用于编译,
-g
用于生成调试信息(使用 GNU 调试器
gdb
调试程序时需要此信息)。
在终端窗口中输入以下命令,可查看所有 GCC 选项的摘要:
man gcc
然后可以浏览常用的 GCC 选项。通常,无需显式提供 GCC 选项,因为默认设置适用于大多数应用程序。以下是一些常用的 GCC 选项:
| Option | Meaning |
| ---- | ---- |
| -ansi | 仅支持 ANSI 标准的 C 语法。(此选项会禁用一些 GNU C 特定的功能,如
__asm__
和
__typeof__
关键字。)与
g++
一起使用时,仅支持 ISO 标准的 C++。 |
| -c | 仅编译并生成目标文件。 |
| -DMACRO | 将宏定义为字符串 “1”。 |
| -DMACRO=DEFN | 将宏定义为 DEFN,其中 DEFN 是某个文本字符串。 |
| -E | 仅运行 C 预处理器。 |
| -fallow-single-precision | 以单精度执行所有数学运算。 |
| -fpcc-struct-return | 在内存中返回所有结构体和联合体的值,而不是在寄存器中。(这种返回值的方式效率较低,但至少与其他编译器兼容。) |
| -fPIC | 生成适用于共享库的位置无关代码(PIC)。 |
| -freg-struct-return | 尽可能在寄存器中返回结构体和联合体的值。 |
| -g | 生成调试信息。(GNU 调试器可以使用此信息。) |
| -I DIRECTORY | 搜索指定目录以查找使用
#include
预处理器指令包含的文件。 |
| -L DIRECTORY | 搜索指定目录以查找库。 |
| -l LIBRARY | 链接时搜索指定的库。 |
| -o FILE | 生成指定的输出文件(用于指定可执行文件的名称)。 |
| -00(两个零) | 不进行优化。 |
| -O 或 -O1(字母 O) | 优化生成的代码。 |
| -O2(字母 O) | 比
-O
进行更多的优化。 |
| -O3(字母 O) | 进行比
-O2
更多的优化。 |
| -Os(字母 O) | 为大小进行优化(以减少代码总量)。 |
| -pedantic | 如果使用任何非 ANSI 标准的扩展,则生成错误。 |
| -pg | 向程序添加额外的代码,以便在运行时,该程序生成
gprof
程序可用于显示程序各部分计时细节的信息。 |
| -shared | 生成共享对象文件(通常用于创建共享库)。 |
| -UMACRO | 取消定义指定的宏。 |
| -v | 显示 GCC 版本号。 |
| -w | 不生成警告消息。 |
| -W1, OPTION | 将 OPTION 字符串(包含多个用逗号分隔的选项)传递给链接器。例如,要创建名为
libXXX.so.1
的共享库,使用以下标志:
-Wl,-soname,libXXX.so.1
。 |
3. GNU make 实用工具
当应用程序由多个源文件组成时,手动输入
gcc
命令来编译和链接文件会变得很繁琐。而且,当你更改单个源文件中的某些内容时,你不想编译每个文件。这时,GNU make 实用工具就派上用场了。
make 实用工具通过读取和解释 makefile 来工作,makefile 是一个文本文件,描述了构建特定程序所需的文件,以及如何编译和链接这些文件来构建程序。每当你更改一个或多个文件时,make 会确定哪些文件需要重新编译,并发出相应的命令来编译这些文件并重新构建程序。
3.1 makefile 名称
默认情况下,GNU make 按以下顺序查找具有以下名称之一的 makefile:
- GNUmakefile
- makefile
- Makefile
在 Unix 系统中,习惯使用
Makefile
作为 makefile 的名称,因为它在目录列表中靠近开头,其中大写名称出现在小写名称之前。
从互联网下载软件时,通常会在源文件旁边找到一个
Makefile
。要构建软件,只需在 shell 提示符下输入
make
即可;make 会处理构建软件所需的所有步骤。请注意,有时在运行
make
之前,你可能需要运行配置脚本、安装额外的软件包或添加库。
如果你的 makefile 没有标准名称(如
Makefile
),则必须使用
-f
选项与 make 一起指定 makefile 的名称。例如,如果你的 makefile 名为
myprogram.mak
,则必须使用以下命令行运行 make:
make -f myprogram.mak
3.2 makefile 内容
对于由多个源文件和头文件组成的程序,makefile 指定以下内容:
- make 创建的项目 — 通常是目标文件和可执行文件。通常使用术语“目标”来指代 make 必须创建的任何项目。
- 创建目标所需的文件或其他操作。
- 执行哪些命令来创建每个目标。
假设你有一个名为
form.C
的 C++ 源文件,其中包含以下预处理器指令:
#include "form.h" // Include header file
目标文件
form.o
显然依赖于源文件
form.C
和头文件
form.h
。除了这些依赖关系外,还必须指定 make 如何将
form.C
文件转换为目标文件
form.o
。假设你希望 make 使用以下选项调用
g++
(因为源文件是 C++):
- -c(仅编译)
- -g(生成调试信息)
- -O2(更多优化)
在 makefile 中,可以用以下规则表达这些选项:
# This a comment in the makefile
# The following lines indicate how form.o depends
# on form.C and form.h and how to create form.o.
form.o: form.C form.h
g++ -c -g -O2 form.C
在这个例子中,第一个非注释行将
form.o
显示为目标,
form.C
和
form.h
显示为依赖文件。
依赖关系后面的行指示如何从其依赖项构建目标。这一行必须以制表符开头。否则,make 命令将以错误消息退出,当你在文本编辑器中查看 makefile 时,你无法区分制表符和空格,这会让你感到困惑。现在你知道了这个秘密,解决方法是将违规行开头的空格替换为单个制表符。
使用 make 的好处是它可以防止不必要的编译。毕竟,可以从 shell 脚本运行
g++
(或
gcc
)来编译和链接构成应用程序的所有文件,但 shell 脚本会编译所有文件,即使这些编译是不必要的。另一方面,GNU make 仅在目标的一个或多个依赖项自上次构建目标以来发生更改时才构建目标。make 通过检查目标和依赖项的最后修改时间来验证这种更改。
make 将目标视为要实现的目标名称;目标不必是文件。例如,可以有这样的规则:
clean:
rm -f *.o
此规则指定了一个名为
clean
的抽象目标,它不依赖于任何东西。这个依赖关系语句表示,要创建目标
clean
,GNU make 会调用命令
rm -f *.o
,该命令会删除所有扩展名为
.o
的文件(即目标文件)。因此,创建名为
clean
的目标的效果是删除目标文件。
3.3 变量(或宏)
除了从依赖项构建目标的基本功能外,GNU make 还包含许多功能,使你可以轻松表达依赖关系和从依赖项构建目标的规则。例如,如果你需要使用 GCC 以相同的选项编译大量 C++ 文件,为每个文件输入选项会很繁琐。可以通过在 make 中定义变量或宏来避免这种重复任务,如下所示:
# Define macros for name of compiler
CXX= g++
# Define a macro for the GCC flags
CXXFLAGS= -O2 -g -mcpu=i686
# A rule for building an object file
form.o: form.C form.h
$(CXX) -c $(CXXFLAGS) form.C
在这个例子中,
CXX
和
CXXFLAGS
是 make 变量。(GNU make 更喜欢称它们为变量,但大多数 Unix make 实用工具称它们为宏。)
在 makefile 中的任何位置使用变量时,以美元符号(
$
)开头,后跟括号内的变量。GNU make 会将变量的所有出现替换为其定义;因此,它会将
$(CXXFLAGS)
的所有出现替换为字符串
-O2 -g -mcpu=i686
。
GNU make 有几个具有特殊含义的预定义变量,如下表所示:
| Variable | Meaning |
| ---- | ---- |
| $% | 存档目标的成员名称。例如,如果目标是
libDisp.a(image.o)
,则
$%
是
image.o
。 |
| $* | 目标文件的名称,不包括扩展名。 |
| $+ | 所有依赖文件的名称,包括重复的依赖项,按出现顺序列出。 |
| $< | 第一个依赖文件的名称。 |
| $? | 所有比目标新的依赖文件的名称(名称之间有空格)。 |
| $@ | 目标的完整名称。例如,如果目标是
libDisp.a(image.o)
,则
$@
是
libDisp.a
。 |
| $^ | 所有依赖文件的名称,名称之间有空格。从依赖文件名中删除重复项。 |
| AR | 存档维护程序的名称(默认值:
ar
)。 |
| ARFLAGS | 存档维护程序的标志(默认值:
rv
)。 |
| AS | 将汇编语言转换为目标代码的汇编程序的名称(默认值:
as
)。 |
| ASFLAGS | 汇编器的标志。 |
| CC | C 编译器的名称(默认值:
cc
)。 |
| CFLAGS | 传递给 C 编译器的标志。 |
| CO | 从 RCS 中提取文件的程序的名称(默认值:
co
)。 |
| COFLAGS | RCS
co
程序的标志。 |
| CPP | C 预处理器的名称(默认值:
$(CC) -E
)。 |
| CPPFLAGS | C 预处理器的标志。 |
| CXX | C++ 编译器的名称(默认值:
g++
)。 |
| CXXFLAGS | 传递给 C++ 编译器的标志。 |
| FC | FORTRAN 编译器的名称(默认值:
f77
)。 |
| FFLAGS | FORTRAN 编译器的标志。 |
| LDFLAGS | 编译器调用链接器
ld
时的标志。 |
| RM | 删除文件的命令的名称(默认值:
rm -f
)。 |
3.4 示例 makefile
如果使用 GNU make 的预定义变量及其内置规则,编写 makefile 会很容易。例如,考虑一个从三个 C 源文件(
xdraw.c
、
xviewobj.c
和
shapes.c
)和两个头文件(
xdraw.h
和
shapes.h
)创建可执行文件
xdraw
的 makefile。假设每个源文件都包含其中一个头文件。基于这些事实,示例 makefile 可能如下所示:
#########################################################
# Sample makefile
# Comments start with '#'
#
#########################################################
# Use standard variables to define compile and link flags
CFLAGS= -g -O2
# Define the target "all"
all: xdraw
OBJS=xdraw.o xviewobj.o shapes.o
xdraw: $(OBJS)
# Object files
xdraw.o: Makefile xdraw.c xdraw.h
xviewobj.o: Makefile xviewobj.c xdraw.h
shapes.o: Makefile shapes.c shapes.h
这个 makefile 依赖于 GNU make 的隐式规则。将
.c
文件转换为
.o
文件使用内置规则。定义变量
CFLAGS
会将标志传递给 C 编译器。
将目标
all
定义为第一个目标是有原因的:如果在命令行中不指定任何目标运行 GNU make(请参阅以下部分描述的 make 语法),该命令将构建 makefile 中找到的第一个目标。通过将第一个目标
all
定义为
xdraw
,可以确保即使不显式将其指定为目标,make 也会构建这个可执行文件。Unix 程序员传统上使用
all
作为第一个目标的名称,但目标的名称并不重要;重要的是它是 makefile 中的第一个目标。
3.5 运行 make
通常,只需在 shell 提示符下输入以下命令即可运行 make:
make
以这种方式运行时,GNU make 按顺序查找名为
GNUmakefile
、
makefile
或
Makefile
的文件。如果 make 找到其中一个 makefile,它将构建该 makefile 中指定的第一个目标。但是,如果 make 没有找到合适的 makefile,它将显示以下错误消息并退出:
make: *** No targets specified and no makefile found. Stop.
如果你的 makefile 名称与默认名称不同,则必须使用
-f
选项指定 makefile。使用此选项的 make 命令语法如下:
make -f filename
其中
filename
是 makefile 的名称。
即使你的 makefile 具有默认名称(如
Makefile
),你可能也想从 makefile 中定义的多个目标中构建特定的目标。在这种情况下,运行 make 时必须使用以下语法:
make target
例如,如果 makefile 包含名为
clean
的目标,可以使用以下命令构建该目标:
make clean
另一种特殊语法可以覆盖 make 变量的值。例如,GNU make 使用
CFLAGS
变量来保存编译 C 文件时使用的标志。在调用 make 时,可以覆盖此变量的值。以下是如何将
CFLAGS
定义为选项
-g -O2
的示例:
make CFLAGS="-g -O2"
除了这些选项外,GNU make 还接受几个命令行选项,如下表所示:
| Option | Meaning |
| ---- | ---- |
| -b | 忽略给定的变量,但接受该变量以与其他版本的 make 兼容。 |
| -C DIR | 在读取 makefile 之前切换到指定的目录。 |
| -d | 打印调试信息。 |
| -e | 允许环境变量覆盖 makefile 中同名变量的定义。 |
| -f FILE | 将 FILE 作为 makefile 读取。 |
| -h | 显示 make 选项列表。 |
| -i | 忽略构建目标时执行的命令中的所有错误。 |
| -I DIR | 搜索指定目录以查找包含的 makefile。(在 makefile 中包含文件的功能是 GNU make 独有的。) |
| -j NUM | 指定 make 可以同时运行的作业数。 |
| -k | 即使在构建其中一个目标时发生错误,也继续构建不相关的目标。 |
| -l LOAD | 如果平均负载至少为 LOAD(浮点数),则不启动新作业。 |
| -m | 忽略给定的变量,但接受该变量以与其他版本的 make 兼容。 |
| -n | 打印要执行的命令,但不执行它们。 |
| -o FILE | 即使文件比其依赖项旧,也不重新构建名为 FILE 的文件。 |
| -p | 显示 make 的变量数据库和隐式规则。 |
| -q | 不运行任何内容,但如果所有目标都是最新的,则返回 0(零),如果有任何内容需要更新,则返回 1,如果发生错误,则返回 2。 |
| -r | 去除所有内置规则。 |
| -R | 去除所有内置变量和规则。 |
| -s | 静默工作(执行命令时不显示命令)。 |
| -t | 更改文件的时间戳。 |
| -v | 显示 make 的版本号、许可信息和版权声明。 |
| -w | 在处理 makefile 之前和之后显示工作目录的名称。 |
| -W FILE | 假设指定的文件已被修改(与
-n
一起使用,以查看修改该文件时会发生什么)。 |
4. GNU 调试器
尽管 make 可以自动完成程序的构建过程,但当程序运行不正常或突然因错误消息退出时,这只是编程中最不需要担心的部分。此时,你需要一个调试器来找出程序错误的原因。Linux 包含了功能强大的 GNU 调试器
gdb
,它具有命令行界面。
gdb
与其他调试器一样,能让你执行典型的调试任务,例如:
- 设置断点,使程序在指定行停止。
- 查看程序中变量的值。
- 逐行执行程序。
- 修改变量以尝试纠正错误。
gdb
调试器可以调试 C 和 C++ 程序。
4.1 准备调试程序
如果你想使用
gdb
调试程序,必须确保编译器在可执行文件中生成并包含调试信息。调试信息包含程序中变量的名称,以及可执行文件中的地址与源文件中代码行的映射关系。
gdb
需要这些信息来执行其功能,例如在执行指定的源代码行后停止。
为确保可执行文件为调试做好了充分准备,可在 GCC 或 G++ 中使用
-g
选项。你可以在 makefile 中定义
CFLAGS
变量来实现这一点:
CFLAGS= -g
4.2 运行 gdb
调试程序最常见的方法是使用以下命令运行
gdb
:
gdb progname
其中
progname
是程序的可执行文件的名称。
progname
运行后,
gdb
会显示以下消息并提示你输入命令:
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)
你可以在
(gdb)
提示符下输入
gdb
命令。一个有用的命令是
help
,它会显示命令列表,如下所示:
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands
Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)
要退出
gdb
,输入
q
然后按回车键。
gdb
有大量的命令,但你只需要几个就能快速找出错误的原因。以下是常用的
gdb
命令:
| 命令 | 作用 |
| ---- | ---- |
|
break NUM
| 在指定的行号
NUM
处设置断点。(调试器会在断点处停止。) |
|
bt
| 显示所有栈帧的跟踪信息。(此命令会显示到目前为止的函数调用序列。) |
|
clear FILENAME: NUM
| 删除源文件
FILENAME
中特定行号
NUM
处的断点。例如,
clear xdraw.c:8
会清除文件
xdraw.c
第 8 行的断点。 |
|
continue
| 继续运行正在调试的程序。(在程序因信号或断点停止后使用此命令。) |
|
display EXPR
| 每次程序停止时,显示表达式
EXPR
(由程序中定义的变量组成)的值。 |
|
file FILE
| 加载指定的可执行文件
FILE
进行调试。 |
|
help NAME
| 显示名为
NAME
的命令的帮助信息。 |
|
info break
| 显示当前断点的列表,包括每个断点被触发的次数信息。 |
|
info files
| 显示正在调试的文件的详细信息。 |
|
info func
| 显示所有函数的名称。 |
|
info local
| 显示当前函数的局部变量信息。 |
|
info prog
| 显示正在调试的程序的执行状态。 |
|
info var
| 显示所有全局和静态变量的名称。 |
|
kill
| 终止正在调试的程序。 |
|
list
| 列出源代码的一部分。 |
总结
本文介绍了 Linux 编程中的编译与链接、GCC 选项、GNU make 实用工具以及 GNU 调试器
gdb
的使用。通过掌握这些知识,你可以更高效地进行 Linux 编程和调试工作。以下是整个流程的 mermaid 流程图:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(编写程序):::process --> B(编译与链接):::process
B --> C{是否使用 make}:::process
C -->|是| D(编写 makefile):::process
C -->|否| E(手动编译链接):::process
D --> F(运行 make):::process
E --> F
F --> G{程序是否正常运行}:::process
G -->|否| H(使用 gdb 调试):::process
G -->|是| I(程序完成):::process
H --> F
希望这些内容能帮助你更好地理解和掌握 Linux 编程的相关技巧。在实际应用中,不断练习和实践,你将更加熟练地运用这些工具和方法。
超级会员免费看
1339

被折叠的 条评论
为什么被折叠?



