UNIX编程实用工具与库函数详解
立即解锁
发布时间: 2025-08-22 01:29:22 阅读量: 1 订阅数: 7 


UNIX系统编程:通信、并发与线程精解
### UNIX编程实用工具与库函数详解
#### 1. Makefiles的使用
Make工具允许用户增量式地重新编译一组程序模块,既方便又能避免错误。使用Make时,需要在描述文件中指定模块之间的依赖关系,Make工具会根据这个文件来判断是否有需要更新的内容。
描述文件指定了目标和其他组件之间的依赖关系,以 `#` 开头的行是注释。依赖关系的形式如下:
```plaintext
target:
components
TAB
rule
```
第一行称为依赖项,第二行称为规则。描述文件中规则行的第一个字符必须是制表符(TAB)。一个依赖项后面可以跟一个或多个规则行。
默认的描述文件名是 `makefile` 和 `Makefile`。当用户不带额外参数输入 `make` 命令时,Make工具会在当前目录中查找 `makefile` 或 `Makefile` 作为描述文件。
以下是一些示例:
- **示例A.16**:编译 `client.c` 程序,使用重启库、UICI库和UICI名称库,并使用 `getnameinfo` 和 `getaddrinfo` 函数。
```bash
cc -DREENTRANCY=REENTRANT_POSIX -o client client.c restart.c uiciname.c uici.c
```
在某些系统上可能还需要额外的库。
- **示例A.17**:可执行文件 `mine` 依赖于目标文件 `mine.o` 和 `minelib.o`。
```plaintext
mine:
mine.o minelib.o
cc -o mine mine.o minelib.o
```
这个依赖关系表明,如果 `mine.o` 或 `minelib.o` 自 `mine` 最后一次更改后被修改过,就会执行规则 `cc -o mine mine.o minelib.o` 来更新目标 `mine`。
- **示例A.18**:一个Makefile描述文件有三个目标。
```plaintext
my:
my.o mylib.o
cc -o my my.o mylib.o
my.o:
my.c myinc.h
cc -c my.c
mylib.o:
mylib.c myinc.h
cc -c mylib.c
```
目标 `my` 依赖于目标 `my.o` 和 `mylib.o`。只需输入 `make` 命令即可完成所需的更新。
有时,用有向图来可视化描述文件的依赖关系会很有帮助。使用图节点(无重复)来表示目标和组件。如果目标A依赖于B,则从节点A向节点B绘制一条有向弧。一个合适的描述文件的图应该没有环。
描述文件还可以包含以下形式的宏定义:
```plaintext
NAME = value
```
当 `$(NAME)` 出现在描述文件中时,Make会在处理前将其替换为 `value`。宏定义中不要使用制表符。
#### 2. Make命令的使用技巧
- **指定目标**:Make命令允许在命令行中指定目标名称。在这种情况下,Make只会更新指定的目标。例如,以下命令会使Make只更新目标 `my`:
```bash
make my
```
这里的 `my` 被解释为默认描述文件(当前目录中的 `makefile` 或 `Makefile`)中的一个目标,而不是描述文件本身。
- **使用自定义描述文件**:如果描述文件的名称不是 `makefile` 或 `Makefile`,可以使用 `-f` 选项。例如,以下命令会从描述文件 `mymake` 中更新目标 `target1`:
```bash
make -f mymake target1
```
#### 3. 调试工具
##### 3.1 lint工具
lint工具用于查找C源文件中的错误和不一致性。它会进行类型检查,尝试检测无法到达的语句,并指出可能浪费资源或不可移植的代码。lint还能检测各种常见错误,如使用 `=` 而不是 `==`,或在 `scanf` 参数中遗漏 `&`。
以下是一些使用示例:
- **练习A.22**:在示例A.18的描述文件中添加以下行来对源文件进行lint检查:
```plaintext
lintall:
lint my.c mylib.c > my.lint
```
输入 `make lintall` 命令对程序进行lint检查,lint的输出会保存在 `my.lint` 文件中。
- **练习A.23**:对以下lint消息的解释:
```plaintext
implicitly declared to return int:
(14) strtok
```
这个lint消息警告程序在源文件的第14行使用 `strtok` 时没有包含与之关联的 `string.h` 头文件。由于缺乏相反的信息,编译器假设 `strtok` 返回 `int`,但实际上 `strtok` 返回 `char*`。缺少头文件可能会在执行时导致灾难性的结果。
- **练习A.24**:对以下lint消息的解释:
```plaintext
(5) warning: variable may be used before set: p
```
这个消息通常在程序在设置指针值之前使用该指针时出现,例如以下代码段:
```c
char *p;
scanf("%s", p);
```
指针 `p` 没有指向合适的字符缓冲区。代码可能会编译通过,但程序在执行时可能会产生段错误。
##### 3.2 调试器
调试器是用于监控和控制其他程序执行的运行时程序。在UNIX环境中常见的调试器有 `dbx`、`adb`、`sdb` 和 `debug`。
使用调试器的步骤如下:
1. 用 `-g` 选项编译程序。例如,编译 `my.c` 程序:
```bash
cc -g -o my my.c
```
2. 在 `dbx` 调试器下运行程序:
```bash
dbx my
```
调试器会给出以下提示:
```plaintext
(dbx)
```
可以输入 `help` 获取命令列表,输入 `run` 运行程序。使用 `stop` 设置停止点,在输入 `run` 之前输入 `trace` 可以在变量值改变时开启跟踪。
许多程序员,尤其是初学者,发现调试器对于解决指针问题很有用。一些调试器有图形用户界面,使用起来更方便。但标准调试器在并发环境中作用较小,因为进程交互或时间因素可能会改变程序的行为。线程调试器的使用也有限。调试器可以帮助找到特定的执行错误,但不能替代程序测试计划。对函数调用进行良好的错误捕获可能是最有价值的调试策略。
##### 3.3 truss工具
对于运行时调试,如果系统支持,`truss` 命令很有用。它会生成一个特定进程运行时所进行的系统调用和传递的信号的跟踪信息。使用 `-f` 选项可以跟踪该进程所有子进程的调用。需要注意的是,`truss` 命令不是POSIX的一部分,并非所有系统都支持。
例如,假设系统上安装了一个名为 `dvips` 的程序,它会访问 `psfonts.map` 文件。你已经将 `psfonts.map` 的副本放在了主目录的 `bin` 子目录中,但运行程序时收到了 “unable to open file” 错误消息。可以尝试执行以下命令(从C shell):
```bash
truss dvips -f t.dvi |& grep psfonts.map
```
`truss` 程序会运行命令 `dvips -f t.dvi`,`grep` 会显示包含 `psfonts.map` 的输出行。`|&` 参数会将 `truss` 的标准输出和标准错误都通过管道传递给 `grep` 的标准输入。输出可能如下所示:
```plaintext
open("./psfonts.map", O_RDONLY, 0666)
Err#2 ENOENT
open("/usr/local/tex/dvips/psfonts.map", O_RDONLY, 0666) Err#2 ENOENT
```
这表明程序首先在当前目录中查找 `psfonts.map`,然后在 `/usr/local/tex/dvips` 目录中查找。将 `psfonts.map` 复制到 `/usr/local/tex/dvips` 目录中,问题应该就可以解决。
##### 3.4 性能分析工具
大多数C编译器都有用于分析程序性能的选项。性能分析工具可以收集基本块的执行时间和函数调用频率等统计信息。可以查阅 `prof`、`gprof`、`monitor`、`profil` 和 `tcov` 以及 `cc` 的手册页以获取更多关于性能分析的信息。
#### 4. 标识符、存储类和链接类
程序员常常对关键字 `static` 的含义感到困惑,部分原因是C语言以两种不同的方式使用这个词。主要需要记住以下几点:
| static是否应用 | 声明位置 | static修改的内容 | 存储类 | 链接类 |
| ---- | ---- | ---- | ---- | ---- |
| 是 | 块内 | 存储类 | 静态 | 无 |
| 否 | 块内 | 存储类 | 自动 | 无 |
| 是 | 块外 | 链接类 | 静态 | 内部 |
| 否 | 块外 | 链接类 | 静态 | 外部 |
- **应用于函数**:如果 `static` 应用于一个函数,那么该函数只能从定义它的文件中调用。
- **变量定义在块外**:变量在程序的整个生命周期内都存在。如果应用了 `static`,该变量只能从包含其定义的文件中访问;否则,该变量可以在程序的任何地方访问。
- **变量定义在块内**:变量只能在块内访问。如果应用了 `static`,变量在程序的整个生命周期内都存在,并且在执行离开块时保留其值;否则,变量在进入块时创建,在执行离开块时销毁,并且在使用前需要显式初始化。
这些规则基于C语言中标识符的作用域和链接的概念。根据ISO C标准,“标识符可以表示对象、函数、结构体、联合体或枚举的标签或成员、typedef名称、标签名称、宏名称或宏参数”。这里主要讨论与变量和函数相关的标识符。
标识符只能在称为其作用域的程序文本区域内使用。如果两个不同的实体由同一个标识符表示,它们的作用域必须不相交,或者一个作用域必须完全包含在另一个作用域内。在内部作用域中,另一个实体被隐藏,不能通过该标识符引用。
作用域从标识符声明开始。如果声明发生在块内,标识符具有块作用域,作用域在块结束时结束;如果声明发生在任何块之外,标识符具有文件作用域,作用域在声明它的文件结束时结束。
由于链接的原因,多次声明的标识符可能引用同一个对象。每个标识符都有外部、内部或无三种链接类。程序中具有外部链接的特定标识符的声明引用同一个实体;文件中具有内部链接的特定标识符的声明表示同一个实体;每个具有无链接的标识符声明表示一个唯一的实体。
函数标识符默认具有外部链接,这意味着它可以在程序的任何文件中被引用。在定义它的文件之外引用它需要函数原型。可以使用 `static` 限定符将函数的链接类设置为内部,从而使其对其他文件不可见。
对象(如变量)的标识符的链接类与其存储类(也称为存储时长)相关。存储时长决定了对象的生命周期,即程序执行过程中保证为对象保留存储的部分。有三种存储时长:静态、自动和动态分配。
动态分配的对象的生命周期从成功调用 `malloc` 或相关函数开始,到对象被显式释放或程序终止时结束。其他对象的生命周期由相应标识符的声明决定。
在任何块之外声明的对象的标识符具有静态存储类。具有静态存储类的对象在程序的整个生命周期内都存在,它们只初始化一次,并保留其最后存储的值。如果声明中没有显式初始化,它们将被初始化为0。与函数一样,这些标识符默认具有外部链接,但可以使用 `static` 限定符将其链接类设置为内部。
在块内声明的对象的标识符没有链接,每个标识符表示一个唯一的对象。这些标识符默认具有自动存储时长。具有自动存储类的对象在进入块时创建,在执行离开块时销毁。这些对象默认不初始化,并且在执行离开块后不一定保留其最后存储的值。如果通过递归或多线程进入块,每次进入块都会创建一个不同的对象。具有自动存储类的变量称为自动变量。
在块内声明的对象的标识符可以使用 `static` 限定符设置为静态存储时长。这样,对象在程序的整个生命周期内都存在,并保留其最后存储的值。如果通过递归或多线程进入块,将使用同一个对象。
具有静态存储时长标识符的对象通常称为静态变量,具有自动存储时长标识符的对象称为自动变量。
#### 5. 重启库
重启库是一组函数,当它们由于可能的临时事件而未完成时会自动重启。该库主要处理两种类型的事件:信号中断和不完整的I/O。
例如,许多库函数(如 `read` 和 `write`)在被信号中断且尚未进行任何I/O操作时会返回 `-1` 并将 `errno` 设置为 `EINTR`,这并不是真正的错误,而是程序在处理阻塞I/O时处理信号时发生的自然事件。当这些函数返回 `-1` 且 `errno` 设置为 `EINTR` 时,库函数会重启。
一些函数(如 `write`)可能在满足完整请求之前返回。当请求写入 `n` 字节时,只要写入的字节数大于零,`write` 调用就被认为是成功的。如果在写入请求的字节数之前捕获到信号,或者I/O缓冲区已满(如写入管道或网络连接时),`write` 函数可能会返回一个小于 `n` 的正值。通常,程序必须处理这种情况并写入剩余的字节。重启库中的函数通过写入剩余字节来简化用户代码。
以下是重启库中函数的完整列表:
| 原型 | 描述 |
| ---- | ---- |
| `int r_close(int fildes)` | 类似于 `close` |
| `int r_dup2(int fildes, int fildes2)` | 类似于 `dup2` |
| `int r_open2(const char *path, int oflag)` | 类似于使用两个参数调用 `open` |
| `int r_open3(const char *path, int oflag, mode_t mode)` | 类似于使用三个参数调用 `open` |
| `ssize_t r_read(int fd, void *buf, size_t size)` | 类似于 `read` |
| `pid_t r_wait(int *stat_loc)` | 类似于 `wait` |
| `pid_t r_waitpid(pid_t pid, int *stat_loc, int options)` | 类似于 `waitpid` |
| `ssize_t r_write(int fd, void *buf, size_t size)` | 类似于 `write`,但如果写入的字节数少于 `size` 会重启(可能的返回值只有 `size` 和 `-1`) |
| `struct timeval add2currenttime(double seconds)` | 返回一个 `struct timeval` 结构,对应于当前时间加上 `seconds` 秒 |
| `int copyfile(int fromfd, int tofd)` | 将字节从一个打开的文件描述符复制到另一个文件描述符,直到文件结束或出现错误 |
| `ssiz
0
0
复制全文
相关推荐










