编程工具介绍
1. 共享库问题与 LD_LIBRARY_PATH 变量
在编程过程中,共享库可能会带来诸多问题,而环境变量
LD_LIBRARY_PATH
是这些问题的首要根源。当我们在
LD_LIBRARY_PATH
中写入以冒号分隔的目录列表时,
ld.so
程序会先在这些目录中搜索库,再去其他地方查找。这是一种在无法访问程序源代码或不想重新编译程序时,确保程序正常运行的低成本方法。
然而,这种方法存在弊端。绝对不能在系统启动文件或程序编译时设置
LD_LIBRARY_PATH
的值。因为动态链接器每次找到这个变量时,都需要多次搜索其中指定的每个目录,这不仅会严重降低系统性能,还可能导致库冲突和错误的链接,因为这些目录会为系统中运行的每个程序进行搜索。
如果必须使用
LD_LIBRARY_PATH
来运行没有源代码的程序(如 Mozilla 等不想编译的应用程序),建议使用包装脚本。例如,若程序
/opt/crummy/bin/crummy.bin
需要使用
/opt/crummy/lib
目录中的共享库,可创建一个名为
crummy
的包装脚本,内容如下:
#!/bin/sh
LD_LIBRARY_PATH=/opt/crummy/lib
export LD_LIBRARY_PATH
exec /opt/crummy/bin/crummy.bin $@
避免使用
LD_LIBRARY_PATH
可以避免大部分共享库问题。但有时,程序员还会遇到另一个重要问题,即库的 API 变更可能导致已安装程序出错。预防此类问题,可采用一致的方法安装共享库,如使用
-Wl,-rpath
选项在运行环境中创建链接路径,或仅使用静态库版本。
2. make 工具
对于由多个源文件组成或需要特殊编译选项的程序,手动编译十分不便。为解决这一问题,Unix 系统中出现了标准的
make
工具,用于管理编译过程。许多系统工具都依赖
make
,所以在使用 Unix 系统时,有必要了解它。
make
工具虽庞大,但原理相对简单。若在程序目录中看到
Makefile
或
makefile
文件,就意味着可以使用
make
系统。通过执行
make
命令,可尝试编译程序。
make
系统的核心是目标(target),它可以是文件(如
.o
文件、可执行文件等)或标签。有些目标可能依赖于其他目标,如创建可执行文件前需准备好所有目标文件,这些被依赖的目标称为依赖项(dependencies)。构建目标时,
make
会遵循规则(rule),例如将
.c
源文件转换为
.o
目标文件。
make
自身有一些内置规则,也可自定义。
2.1 示例 Makefile 文件
以下是一个简单的
Makefile
,用于根据
main.c
和
aux.c
源文件构建名为
myprog
的程序:
# 目标文件
OBJS=aux.o main.o
all: myprog
myprog: $(OBJS)
$(CC) -o myprog $(OBJS)
-
以
#开头的行是注释。 -
OBJS是一个宏定义,将两个目标文件的名称赋值给它,后续可通过$(OBJS)引用。 -
all是默认目标,执行make命令时会默认执行它。 -
all目标依赖于myprog目标,而myprog目标依赖于$(OBJS)展开后的aux.o和main.o文件。
执行
make
命令时,会显示以下命令:
$ make
cc -c -o aux.o aux.c
cc -c -o main.o main.c
cc -o myprog aux.o main.o
下面是该
Makefile
的依赖关系图:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(all):::process --> B(myprog):::process
B --> C(aux.o):::process
B --> D(main.o):::process
C --> E(aux.c):::process
D --> F(main.c):::process
2.2 内置规则
make
系统如何知道将
aux.c
转换为
aux.o
呢?这是因为
make
有内置规则。当需要使用
.o
文件时,它会自动根据
.c
文件创建,并知道如何正确调用
cc
编译器进行转换。
2.3 程序的最终构建
构建
myprog
程序的最后一步,是在准备好
$(OBJS)
中的两个目标文件后,执行以下命令(
$(CC)
会被替换为编译器名称):
$(CC) -o myprog $(OBJS)
需要注意的是,该行命令前必须有一个制表符,每个命令需单独占一行。若出现类似
Makefile:7: *** missing separator. Stop.
的错误,说明
Makefile
文件可能损坏,因为制表符是命令分隔符,缺失或其他错误会导致该问题。
2.4 更新机制
make
系统的最后一个原则是目标应随其依赖项更新。例如,连续两次执行
make
命令,第二次会显示
make: Nothing to be done for ‘all’.
,因为
make
检查规则后发现
myprog
文件已存在,且自上次调用以来依赖项未改变。可进行如下小实验:
1. 执行
touch aux.c
命令更新
aux.c
文件的时间戳。
2. 再次执行
make
命令,
make
会发现
aux.c
比
aux.o
新,因此会重新编译
aux.c
。
3. 由于
myprog
依赖于
aux.o
,而
aux.o
现在比
myprog
新,所以
make
会重新创建
myprog
。这是
make
系统中常见的链式反应示例。
2.5 命令行参数和选项
了解
make
系统的命令行参数和选项,可让它完成很多有用的工作:
- 指定目标名称:在命令行中指定目标名称,如
make aux.o
,可仅创建
aux.o
文件。
- 定义宏:可在命令行中定义宏,如
make CC=clang
,让
make
使用
clang
编译器而非默认的
cc
编译器。这在测试预处理器定义或库时特别有用,尤其是
CFLAGS
和
LDFLAGS
宏。
- 不使用
Makefile
:若
make
的内置规则足以构建目标,可直接执行
make
命令并指定目标名称。例如,对于简单的
bla.c
源文件,执行
make bla
命令,
make
会尝试执行
cc bla.o -o bla
。但这种方式仅适用于最简单的 C 语言程序,若程序需要库或特殊的头文件目录,必须编写
Makefile
。不使用
Makefile
执行
make
命令,在处理非 C 语言程序(如 Fortran、lex 或 yacc)且不清楚编译器或工具如何工作时很有用,即使
make
无法完成目标,也可能提供足够的提示。
此外,还有两个常用选项:
-
-n
:打印构建目标所需的命令,但不执行。
-
-f 文件名
:让
make
从指定文件而非
Makefile
或
makefile
中读取数据。
2.6 标准宏和变量
make
系统使用许多特殊的宏和变量,本书统一用宏来描述构建目标过程中不改变的内容。常见宏如下:
| 宏名称 | 描述 |
| ---- | ---- |
|
CFLAGS
| C 编译器选项,构建
.c
文件的目标代码时传递给编译器 |
|
LDFLAGS
| 类似
CFLAGS
,但用于存储创建可执行文件时传递给链接器的选项 |
|
LDLIBS
| 若不想将库名选项与
LDFLAGS
中的搜索路径合并,可将库名选项放在此宏中 |
|
CC
| C 编译器名称,默认为
cc
|
|
CPPFLAGS
| C 预处理器选项,
make
调用预处理器时会传递这些选项 |
|
CXXFLAGS
| GNU
make
用于存储 C++ 编译器选项的宏 |
make
系统的变量在构建过程中可能会改变,以下是常见变量:
| 变量名称 | 描述 |
| ---- | ---- |
|
$@
| 在规则内部,该变量表示目标名称 |
|
$*
| 该变量展开为当前目标的基本名称,如创建
bla.o
目标时,它存储
bla
名称 |
需注意,GNU
make
有许多扩展、内置规则和功能,在 Linux 系统中使用没问题,但移植到 Sun 或 BSD 系统时可能会有意外情况。
2.7 常见编译目标
大多数
Makefile
包含标准目标,用于完成与整个程序编译相关的部分任务:
-
clean
:几乎所有
Makefile
都有该目标,执行
make clean
通常会删除所有目标文件和可执行文件,以便重新开始编译。例如,为
myprog
程序的
Makefile
添加如下规则:
clean:
rm -f $(OBJS) myprog
-
distclean:使用 GNUautotools创建的Makefile通常有此目标,会删除所有非原始发行版的内容,包括Makefile本身。某些情况下,程序员可能选择使用realclean目标而不删除可执行文件。 -
install:该目标会将编译好的程序和文件复制到系统中指定的安装位置。此操作有风险,建议先执行make -n install检查实际安装内容。 -
test或check:部分Makefile会添加此目标,用于检查程序编译后是否正常运行。 -
depend:该目标通过特殊的-M选项调用编译器来创建依赖关系,可能会修改Makefile本身。虽不常用,但遇到要求调用此目标的程序时,必须执行。 -
all:始终是Makefile的第一个目标,很多文档描述的是该目标而非可执行程序本身。
2.8 Makefile 文件的组织
创建
Makefile
虽有多种风格,但通常遵循一些通用原则:
- 宏定义部分:将库和头文件选项按不同包分组,例如:
MYPACKAGE_INCLUDES=-I/usr/local/include/mypackage
MYPACKAGE_LIB=-L/usr/local/lib/mypackage -lmypackage
PNG_INCLUDES=-I/usr/local/include
PNG_LIB=-L/usr/local/lib -lpng
CFLAGS=$(CFLAGS) $(X_INCLUDES) $(PNG_INCLUDES)
LDFLAGS=$(LDFLAGS) $(X_LIB) $(PNG_LIB)
-
目标文件分组:通常根据可执行文件分组目标文件。假设要创建
boring和trite两个可执行文件,每个文件有自己的.c文件且都依赖util.c文件,Makefile可如下编写:
UTIL_OBJS=util.o
BORING_OBJS=$(UTIL_OBJS) boring.o
TRITE_OBJS=$(UTIL_OBJS) trite.o
PROGS=boring trite
all: $(PROGS)
boring: $(BORING_OBJS)
$(CC) -o $@ $(BORING_OBJS) $(LDFLAGS)
trite: $(TRITE_OBJS)
$(CC) -o $@ $(TRITE_OBJS) $(LDFLAGS)
将两个可执行文件合并到一个目标不太实用,因为可能会阻碍规则迁移到其他
Makefile
,或删除单个可执行文件或一组文件。而且这种依赖关系不真实,若
Makefile
中只有一个规则处理两个程序,会导致
boring
依赖
trite.c
,
trite
依赖
boring.c
,重新构建一个程序时需重新构建两个程序。
若需为目标文件准备特殊规则,应将其放在构建可执行程序规则之前。若多个可执行文件使用同一个目标文件,规则应放在所有相关程序规则之前。
3. 调试器
在 Linux 系统中,标准的调试器是
gdb
,也有用户友好的界面,如 Eclipse IDE 和 Emacs。为了能对程序进行全面调试,编译程序时需向编译器传递
-g
选项,让编译器在可执行文件中保存符号表和调试器所需的其他信息。
使用以下命令启动
gdb
调试器调试
program
程序:
$ gdb program
屏幕会显示
(gdb)
提示符,在调试器命令行中输入以下命令,可启动加载的
program
程序并传递选项:
(gdb) run opcje
若程序无问题,会正常运行并结束;若出现问题,调试器会停止程序,打印调用栈和导致问题的部分代码,并返回
(gdb)
命令行。代码片段可能提示问题原因,还可使用
print
命令打印变量值(该命令也适用于数组和 C 语言结构体),以更精确地找出错误原因:
(gdb) print zmienna
断点(breakpoint)功能可在程序任意位置暂停执行。使用以下命令设置断点,其中
plik
是源文件名称,
numer_wiersza
是文件中的行号:
(gdb) break plik:numer_wiersza
使用以下命令让调试器继续执行程序:
(gdb) continue
使用以下命令删除断点:
(gdb) clear plik:numer_wiersza
综上所述,掌握这些编程工具和调试器的使用方法,能有效提高编程效率和解决问题的能力。在实际开发中,根据具体需求灵活运用这些工具,可更好地完成程序的编译、调试和维护工作。
编程工具介绍(续)
4. 工具综合运用案例
为了更好地理解上述编程工具的使用,我们来看一个综合运用的案例。假设我们要开发一个简单的 C 语言项目,包含多个源文件,并且需要使用一些外部库,同时要进行调试和编译管理。
4.1 项目需求与结构
我们的项目名为
myproject
,包含以下源文件和需求:
-
main.c
:项目的主程序入口。
-
utils.c
:包含一些通用的工具函数。
- 需要链接
math.h
库进行数学计算。
- 要对代码进行调试。
项目的目录结构如下:
myproject/
├── main.c
├── utils.c
├── utils.h
└── Makefile
4.2 编写 Makefile 文件
根据前面介绍的
Makefile
编写原则,我们来创建
Makefile
文件:
# 宏定义
CC = gcc
CFLAGS = -g -Wall -I.
LDFLAGS = -lm
# 目标文件
OBJS = main.o utils.o
# 默认目标
all: myproject
# 生成可执行文件
myproject: $(OBJS)
$(CC) -o myproject $(OBJS) $(LDFLAGS)
# 生成目标文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理目标
clean:
rm -f $(OBJS) myproject
-
CC:指定使用gcc作为编译器。 -
CFLAGS:添加-g选项用于调试,-Wall显示所有警告信息,-I.指定当前目录为头文件搜索路径。 -
LDFLAGS:添加-lm链接数学库。 -
OBJS:定义目标文件列表。 -
all:默认目标,生成myproject可执行文件。 -
%.o: %.c:通用规则,将.c文件编译为.o文件。 -
clean:清理目标,删除所有目标文件和可执行文件。
4.3 编译项目
在项目根目录下执行
make
命令,
make
工具会根据
Makefile
的规则进行编译:
$ make
gcc -g -Wall -I. -c main.c -o main.o
gcc -g -Wall -I. -c utils.c -o utils.o
gcc -o myproject main.o utils.o -lm
4.4 调试项目
编译完成后,若发现程序有问题,可使用
gdb
进行调试。启动
gdb
调试
myproject
程序:
$ gdb myproject
在
gdb
提示符下,可设置断点、运行程序、打印变量等操作。例如,在
main.c
的第 10 行设置断点,然后运行程序:
(gdb) break main.c:10
(gdb) run
程序会在第 10 行暂停,此时可使用
print
命令查看变量值:
(gdb) print some_variable
5. 工具使用的注意事项
在使用上述编程工具时,有一些注意事项需要牢记:
-
LD_LIBRARY_PATH 变量
:尽量避免使用,若必须使用,使用包装脚本,防止系统性能下降和库冲突。
-
Makefile 文件
:
- 命令前必须使用制表符,否则会导致错误。
- 合理组织
Makefile
,将相关选项和目标文件分组,便于维护。
- 注意目标的依赖关系,避免出现不必要的重新编译。
-
调试器
:
- 编译时添加
-g
选项,确保调试信息完整。
- 合理使用断点和打印变量功能,快速定位问题。
6. 总结
本文介绍了编程过程中常用的工具,包括共享库问题与
LD_LIBRARY_PATH
变量的处理、
make
工具的使用、调试器
gdb
的操作,以及工具的综合运用案例和注意事项。通过掌握这些工具,程序员可以更高效地进行程序的编译、调试和维护工作。
以下是这些工具的使用流程总结:
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(编写代码):::process --> B(创建 Makefile):::process
B --> C(编译程序):::process
C --> D{程序是否正常运行?}:::process
D -- 是 --> E(程序发布):::process
D -- 否 --> F(使用 gdb 调试):::process
F --> G(修改代码):::process
G --> B
在实际开发中,根据项目的需求和复杂度,灵活运用这些工具,不断积累经验,提高编程技能和解决问题的能力。希望本文能为广大程序员在编程工具的使用上提供有益的参考。
超级会员免费看

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



