《UNIX环境高级编程(第三版)》第一章重难点详解
一、章节核心定位
第一章是全书的"入门地基",核心是帮读者建立UNIX系统的底层认知框架------从"内核如何提供服务"到"应用如何调用接口",所有重难点都围绕"理论→接口→代码实践"展开。本章核心重难点共5个:UNIX体系结构与系统调用 、文件与目录操作(万物皆文件) 、文件描述符与I/O模型 、进程控制基础(fork/exec/waitpid) 、信号与出错处理。每个重难点都结合书中核心代码,分步拆解原理与实操。
二、重难点1:UNIX体系结构(理解"内核-库-应用"的协作逻辑)
1.1 核心概念:四层模型与关键区别
UNIX系统从底层到上层分为 内核→公用函数库→shell→应用程序,核心是搞懂"系统调用"和"库函数"的本质区别:
- 内核:系统核心,控制硬件(CPU/内存/磁盘),提供"系统调用接口"(如
open/read),是应用访问硬件的唯一入口。 - 公用函数库:基于系统调用封装(如
printf底层调用write),简化编程,减少重复开发。 - shell:特殊应用(如bash),解析用户命令,通过
fork/exec启动其他程序,是用户与系统的"中介"。
1.2 关键代码:无缓冲I/O的内核直接交互(对应图1-4)
代码功能:从标准输入复制数据到标准输出,直接调用内核提供的无缓冲I/O函数,直观体现"应用→内核"的交互流程。
c
#include "apue.h" // 书中自定义头文件:包含标准头文件+错误处理函数
#define BUFFSIZE 4096 // 缓冲区大小:匹配磁盘块大小(4096字节),优化I/O效率
int main(void) {
int n;
char buf[BUFFSIZE]; // 自定义缓冲区:存储读取的数据
// 循环读取标准输入(STDIN_FILENO=0),写入标准输出(STDOUT_FILENO=1)
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) {
// 若写入字节数≠读取字节数,说明出错
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error"); // 自定义错误处理:打印系统错误信息
}
// 若read返回负值,说明读取出错
if (n < 0)
err_sys("read error");
exit(0); // 正常终止进程:内核自动关闭所有打开的文件描述符
}
1.3 分步拆解代码(核心难点突破)
步骤1:头文件与缓冲区定义
apue.h:书中自定义头文件,封装了<unistd.h>(系统调用头文件)、<stdio.h>等标准头文件,还定义了err_sys等错误处理函数(避免重复写perror)。BUFFSIZE=4096:关键优化点!多数UNIX文件系统的磁盘块大小是4096字节,设置缓冲区为该值可减少read/write的调用次数(系统调用需切换用户态→内核态,成本高)。若设为1字节,会触发百万次系统调用,效率极低。
步骤2:文件描述符的默认值
STDIN_FILENO(0)、STDOUT_FILENO(1):POSIX标准定义的符号常量(在<unistd.h>中),替代硬编码0/1,提升可读性和可移植性。- 为什么不用
0/1直接写?:若后续代码修改了文件描述符分配,硬编码会出错,符号常量可避免此问题。
步骤3:read/write函数的核心逻辑
read函数原型:ssize_t read(int fd, void *buf, size_t nbytes)- 返回值:>0表示实际读取字节数;=0表示文件尾端;<0表示出错。
- 循环条件
n > 0:只要还有数据可读,就持续读取,直到文件尾端。
write函数原型:ssize_t write(int fd, const void *buf, size_t nbytes)- 必须检查返回值:若返回值≠n(如磁盘满),说明写入不完整,需处理错误。
步骤4:错误处理函数err_sys
- 作用:封装
perror和exit,打印"错误信息+系统错误描述"(如"write error: No space left on device"),简化错误处理代码。 - 书中所有实例均使用此类自定义函数,后续章节会统一讲解(附录B有实现)。
1.4 重难点总结
- 核心难点:理解"无缓冲I/O"的本质------每次
read/write都直接调用内核,缓冲区由用户手动管理。 - 关键结论:缓冲区大小需匹配磁盘块大小(4096字节是通用最优值),减少系统调用次数是UNIX I/O性能优化的核心思路。
三、重难点2:文件与目录(理解"万物皆文件"与路径解析)
2.1 核心概念:文件系统与路径规则
- 层次结构:以根目录(
/)为起点,目录是"包含目录项的特殊文件",每个目录项含"文件名+指向i节点的指针"(i节点存储文件属性:权限、大小等)。 - 路径规则:
- 绝对路径:以
/开头(如/etc/passwd),从根目录解析。 - 相对路径:不以
/开头(如doc/memo),从当前工作目录解析。 - 特殊目录项:
.=当前目录,..=父目录(根目录的..等于.)。
- 绝对路径:以
- 文件名限制:仅
/和空字符不可用,POSIX推荐用字母、数字、.、-、_(保证可移植性)。
2.2 关键代码:列出目录内容(对应图1-3)
代码功能:模拟ls命令核心逻辑,读取指定目录的所有文件名,体现"目录作为特殊文件"的操作规则。
c
#include "apue.h"
#include <dirent.h> // 目录操作专用头文件:包含opendir/readdir/closedir原型
int main(int argc, char *argv[]) {
DIR *dp; // 目录流指针:类似FILE,内核维护目录读取状态
struct dirent *dirp; // 存储单个目录项信息:含文件名d_name、i节点号d_ino
// 步骤1:检查命令行参数(需传入1个目录名,如./a.out /etc)
if (argc != 2)
err_quit("usage: ls directory_name"); // 无系统错误,仅打印用法并退出
// 步骤2:打开目录:返回DIR指针,失败则报错(如权限不足、不是目录)
if ((dp = opendir(argv[1])) == NULL)
err_sys("can't open %s", argv[1]);
// 步骤3:循环读取目录项:readdir返回下一个目录项,NULL表示目录尾端或出错
while ((dirp = readdir(dp)) != NULL)
printf("%s\n", dirp->d_name); // 打印目录项中的文件名
// 步骤4:关闭目录流,释放内核资源
closedir(dp);
exit(0);
}
2.3 分步拆解代码(核心难点突破)
步骤1:目录操作函数族的特殊性
- 为什么不能用
read直接读目录?:目录是内核保护的特殊文件,直接写/读会破坏目录结构,必须用opendir/readdir/closedir专用函数。 - 函数分工:
opendir:打开目录,初始化"目录读取位置"(从起始处开始),返回DIR指针(类似open返回文件描述符)。readdir:读取下一个目录项,填充struct dirent,关键成员d_name(文件名,以\0结尾)。closedir:关闭目录流,释放内核资源(类似close关闭文件描述符)。
步骤2:错误场景处理
- 场景1:传入路径不是目录(如
./a.out /dev/tty)→opendir返回NULL,err_sys打印"can't open /dev/tty: Not a directory"。 - 场景2:无权限打开目录(如
./a.out /etc/ssh/private)→opendir返回NULL,err_sys打印"can't open /etc/ssh/private: Permission denied"。 err_quit与err_sys的区别:err_quit仅打印用法,不涉及系统错误;err_sys打印系统错误描述(依赖errno)。
步骤3:目录项的排序问题
- 代码输出的文件名不是字母顺序:
readdir按目录在磁盘上的存储顺序读取,而ls命令会额外排序,这是ls的扩展功能,核心目录读取逻辑就是本代码的逻辑。
2.4 重难点总结
- 核心难点:理解"目录是特殊文件"------仅内核可修改,用户只能通过专用函数读取,且目录项不存储文件属性(属性存在i节点中,通过
stat函数获取)。 - 关键结论:路径解析的核心是"绝对路径从根开始,相对路径从当前目录开始",
.和..是目录的默认项,保证路径解析的灵活性。
四、重难点3:进程与进程ID(理解"程序→进程"的转换)
3.1 核心概念:程序与进程的区别
- 程序:磁盘上的可执行文件(如
/bin/ls),静态存在。 - 进程:程序的"执行实例"(如运行
ls时创建的进程),动态存在,一个程序可对应多个进程(如多次运行./a.out)。 - 进程ID(PID):每个进程的唯一非负整数标识,由内核分配,
getpid()获取当前进程PID,fork()创建子进程时,父进程获子PID,子进程获0。
3.2 关键代码:打印进程ID(对应图1-6)
代码功能:获取并打印当前进程PID,理解PID的唯一性和数据类型强制转换的必要性。
c
#include "apue.h"
int main(void) {
// 打印进程ID:getpid()返回pid_t类型,强制转换为long保证可移植性
printf("hello world from process ID %ld\n", (long)getpid());
exit(0);
}
3.3 分步拆解代码(核心难点突破)
步骤1:pid_t数据类型与强制转换
pid_t:UNIX系统定义的基本数据类型(在<sys/types.h>中),长度随系统变化(32位/64位)。- 为什么强制转换为
long?:printf的%ld格式符要求参数为long类型,若pid_t长度小于long(如32位系统),不转换会导致格式不匹配,打印错误。这是UNIX编程"可移植性"的关键细节。
步骤2:运行结果分析
bash
$ ./a.out
hello world from process ID 851 # 第一次运行的PID
$ ./a.out
hello world from process ID 854 # 第二次运行的PID(不同)
- PID唯一性:每次运行程序创建新进程,内核分配新PID,0通常为内核进程,用户进程PID从1开始(
init进程)。
3.4 重难点总结
- 核心难点:
pid_t的可移植性处理------不能直接用int接收,必须强制转换为long或用%zd(C99),避免跨平台兼容问题。 - 关键结论:进程是程序的动态执行实例,PID是其唯一标识,后续进程控制(
fork/exec)均依赖PID。
五、重难点4:进程控制基础(fork+exec+waitpid协作)
4.1 核心概念:进程控制三要素
fork():创建子进程,复制父进程地址空间(代码、数据、堆栈),但共享打开的文件描述符,"一次调用,两次返回"(父进程返子PID,子进程返0)。exec():替换当前进程映像(如将./a.out替换为/bin/ls),成功则不返回,失败才返回-1,需与fork配合实现"创建子进程→执行新程序"。waitpid():父进程等待子进程终止,获取子进程终止状态(正常/信号终止),避免"僵尸进程",options=0表示阻塞等待。
4.2 关键代码:简易shell(对应图1-7)
代码功能:模拟shell核心逻辑------读取用户命令,创建子进程执行命令,父进程等待子进程终止,体现fork+exec+waitpid的协作。
c
#include "apue.h"
#include <sys/wait.h> // 包含waitpid()原型
int main(void) {
char buf[MAXLINE]; // 存储用户输入命令(MAXLINE在apue.h中定义为1024)
pid_t pid; // 存储子进程PID
int status; // 存储子进程终止状态
printf("%% "); // 打印shell提示符(%%转义为%)
// 步骤1:读取用户输入:fgets从标准输入读一行,NULL表示文件尾端(Ctrl+D)
while (fgets(buf, MAXLINE, stdin) != NULL) {
// 步骤2:去除换行符:fgets读取的行以\n结尾,替换为\0(exec要求命令以\0结尾)
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
// 步骤3:创建子进程
if ((pid = fork()) < 0) {
err_sys("fork error"); // fork失败(如系统资源不足)
} else if (pid == 0) { // 子进程:执行用户命令
// execlp:从PATH环境变量搜索命令,参数格式:(命令名, 命令名, NULL)
execlp(buf, buf, (char *)0);
// 若execlp返回,说明执行失败(如命令不存在)
err_ret("couldn't execute: %s", buf);
exit(127); // 退出子进程,返回错误状态码(127=shell约定"命令未找到")
}
// 步骤4:父进程:等待子进程终止
if ((pid = waitpid(pid, &status, 0)) < 0)
err_sys("waitpid error");
printf("%% "); // 打印下一个提示符
}
exit(0);
}
4.3 分步拆解代码(核心难点突破)
步骤1:fork的"一次调用,两次返回"
- 父进程:
fork()返回子PID(>0),进入"等待子进程"逻辑。 - 子进程:
fork()返回0,进入"执行命令"逻辑,调用execlp替换自身映像。 - 关键注意:子进程执行
execlp成功后,原代码(err_ret/exit)不会执行;只有execlp失败才会执行后续代码------这是exec的"替换特性"。
步骤2:execlp函数的参数规则
- 原型:
int execlp(const char *file, const char *arg, ..., (char *)0) - 第一个参数:命令名(如
date),execlp会从PATH环境变量(如/bin:/usr/bin)搜索可执行文件路径(如/bin/date)。 - 可变参数:从第二个参数开始是"命令参数列表",必须以
(char *)0结尾(标记参数结束),例如执行ls -l需传入execlp("ls", "ls", "-l", (char *)0)。
步骤3:waitpid的作用
- 阻塞等待:
options=0表示父进程暂停执行,直到子进程终止。 - 终止状态:
status参数存储子进程终止原因(如正常退出码、被信号终止的信号码),后续第8章会详解解析方法。
步骤4:运行结果分析
bash
$ ./a.out
% date # 用户输入命令
Sat Jan 21 19:42:07 EST 2012 # 子进程执行date的输出
% who # 用户输入命令
sar console Jan 11 14:59 # 子进程执行who的输出
% ^D # 按Ctrl+D,fgets返回NULL,程序退出
- 限制:该程序暂不支持命令参数(如
ls -l会报错),需后续扩展参数解析逻辑(分割buf中的命令与参数)。
4.4 重难点总结
- 核心难点:
fork的地址空间复制(写时复制优化)、exec的进程映像替换、waitpid的僵尸进程预防------三者协作是UNIX进程控制的核心。 - 关键结论:shell的本质就是"读取命令→fork子进程→exec执行命令→waitpid等待"的循环,本代码是shell的最小实现。
六、重难点5:信号与出错处理(异步通知与统一错误接口)
5.1 核心概念:信号与errno规则
- 信号:内核向进程发送的"异步事件通知"(如
SIGINT=Ctrl+C终止进程、SIGFPE=浮点异常),进程可"忽略""默认处理"或"捕捉"(注册自定义函数)。 errno规则:- 系统函数出错时,设置
errno为特定值(如EACCES=权限不足、ENOENT=文件不存在)。 - 成功时
errno不清除,仅当函数返回错误时,errno才有效。 - 所有
errno常量≠0,strerror(errno)可转换为可读错误信息。
- 系统函数出错时,设置
5.2 关键代码1:信号捕捉(对应图1-10)
在"简易shell"基础上添加信号捕捉,使按下Ctrl+C(SIGINT)时不终止程序,仅打印提示。
c
#include "apue.h"
#include <sys/wait.h>
// 步骤1:定义信号捕捉函数:SIGINT信号发生时调用
static void sig_int(int signo) {
printf("\ninterrupt\n"); // 打印提示
printf("%% "); // 重新打印提示符
fflush(stdout); // 冲洗标准输出(行缓冲需手动冲洗,避免提示不显示)
}
int main(void) {
char buf[MAXLINE];
pid_t pid;
int status;
// 步骤2:注册信号捕捉函数:捕捉SIGINT,失败则报错
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal error");
printf("%% ");
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
execlp(buf, buf, (char *)0);
err_ret("couldn't execute: %s", buf);
exit(127);
}
if ((pid = waitpid(pid, &status, 0)) < 0)
err_sys("waitpid error");
printf("%% ");
fflush(stdout); // 冲洗提示符
}
exit(0);
}
代码拆解(核心难点)
signal()函数:原型void (*signal(int signo, void (*func)(int)))(int),作用是"为signo信号注册func处理函数"。- 行缓冲问题:标准输出默认是行缓冲,若无换行符(如
printf("%% ")),缓冲不会自动冲洗,需手动调用fflush(stdout)确保提示显示。 - 捕捉效果:未添加捕捉时,Ctrl+C终止程序;添加后,程序打印
interrupt并继续运行,体现"信号的可定制性"。
5.3 关键代码2:出错信息打印(对应图1-8)
代码功能:演示strerror与perror的使用,理解errno与出错信息的映射。
c
#include "apue.h"
#include <errno.h> // 包含errno定义与出错常量
int main(int argc, char *argv[]) {
// 1. strerror:将errno值(此处直接传EACCES)转换为出错信息字符串
fprintf(stderr, "EACCES: %s\n", strerror(EACCES));
// 2. perror:基于当前errno值,打印"msg: 出错信息"
errno = ENOENT; // 手动设置errno为ENOENT(文件不存在)
perror(argv[0]); // argv[0]是程序名(如./a.out),符合UNIX惯例
exit(0);
}
代码拆解(核心难点)
strerror():接收errno值,返回指向错误信息的指针(如"Permission denied"),需用fprintf打印到标准错误(stderr)。perror():先打印msg+": ",再打印errno对应的错误信息+换行符,无需手动传errno。- 标准错误(
stderr):与标准输出(stdout)分离,即使stdout被重定向(如./a.out > log),出错信息仍显示在终端,避免错误信息丢失。
5.4 重难点总结
- 核心难点:信号的异步性(捕捉函数需可重入)、
errno的有效性规则、行缓冲的冲洗问题。 - 关键结论:UNIX出错处理的核心是"统一接口"------用
strerror/perror解析errno,用自定义错误函数(err_sys/err_quit)简化代码,提高可读性。
七、章节总结与实践建议
1. 核心知识框架
第一章的所有重难点都围绕"如何与UNIX内核交互"展开,核心逻辑链:
体系结构(四层模型)→ 接口(系统调用/库函数)→ 实践(文件I/O/进程控制/信号)
2. 必做实践
- 编译运行所有代码:需先安装
apue库(书中源码可从www.apuebook.com下载),编译命令如gcc -o mycopy mycopy.c -lapue。 - 修改代码测试:
- 更改
BUFFSIZE为1、1024、8192,观察I/O效率差异(用time ./mycopy < infile > outfile计时)。 - 在简易shell中添加参数解析,支持
ls -l等带参数的命令。
- 更改
- 调试错误场景:故意传入错误路径(如
./myls /dev/tty),观察错误处理函数的输出。
3. 后续衔接
- 第2章(UNIX标准):解释"可移植性"的底层逻辑,呼应本章的
long强制转换、sysconf获取系统限制。 - 第3章(文件I/O):深入
open/lseek/dup等函数,扩展本章无缓冲I/O的细节。 - 第8章(进程控制):详解
fork的写时复制、僵尸进程处理,扩展本章简易shell的功能。
《UNIX环境高级编程(第三版)》第一章 中等难度多选题(5题)
题目1:关于UNIX体系结构与系统调用、库函数的区别,下列说法正确的有( )
A. UNIX体系结构从底层到上层依次为:内核→公用函数库→shell→应用程序
B. 系统调用是内核提供的底层接口,执行时需切换用户态与内核态,是应用访问硬件的唯一合法入口
C. 所有库函数均依赖系统调用实现,例如strcpy底层会调用write系统调用
D. 应用程序可直接调用系统调用(如read/write),无需通过库函数中转
E. 库函数通过缓冲机制减少系统调用次数,因此效率通常低于系统调用
题目2:下列关于UNIX文件描述符与无缓冲I/O的说法,正确的有( )
A. 标准输入、标准输出、标准错误的文件描述符常量分别为STDIN_FILENO(0)、STDOUT_FILENO(1)、STDERR_FILENO(2),定义于<unistd.h>
B. read函数返回值为0表示读取出错,返回值为-1表示到达文件尾端
C. 无缓冲I/O的核心特征是"无用户态缓冲",每次read/write调用均直接触发内核系统调用
D. 缓冲区大小BUFFSIZE设为4096字节(多数UNIX磁盘块大小)可减少系统调用次数,优化I/O效率
E. 若write函数返回值小于传入的字节数,说明发生致命错误,无法通过重试恢复
题目3:关于fork函数创建子进程的特性,下列说法正确的有( )
A. fork调用一次,返回两次:父进程返回子进程PID(正整数),子进程返回0,出错返回-1
B. 子进程会完全复制父进程的地址空间(代码、数据、堆栈),且不共享任何资源
C. 子进程调用exec函数族后,进程映像会被新程序替换,原fork后的代码段不再执行
D. 父进程若未调用wait/waitpid等待子进程终止,子进程终止后会成为僵尸进程,占用PID资源
E. 父子进程的执行顺序由父进程决定,父进程一定先于子进程执行
题目4:关于UNIX信号与出错处理的描述,下列说法正确的有( )
A. 信号是内核向进程发送的异步事件通知,例如SIGINT由Ctrl+C触发,SIGFPE由浮点异常触发
B. 进程对信号的处理方式包括"忽略信号""默认处理""捕捉信号(注册自定义函数)"三类,其中硬件异常信号(如SIGFPE)建议忽略以避免程序崩溃
C. signal函数用于注册信号捕捉函数,若注册失败返回SIG_ERR,需结合err_sys函数处理错误
D. 自定义信号处理函数中使用printf打印无换行符的提示时,需手动调用fflush(stdout),因标准输出默认是行缓冲
E. errno变量在函数成功执行时会自动清零,因此可通过errno是否为0判断函数是否出错
题目5:关于UNIX文件系统的路径名与目录规则,下列说法正确的有( )
A. 绝对路径以"/"开头,从根目录解析;相对路径不以"/"开头,从当前工作目录解析,例如".../doc/memo"是相对路径
B. 文件名可包含任意字符,仅禁止"/"(路径分隔符)和空字符(\0),POSIX推荐使用字母、数字、".""-""_"以保证可移植性
C. 读取目录内容可直接使用read函数,无需调用opendir/readdir/closedir专用函数
D. 目录中的"."表示当前目录,"..."表示父目录,根目录(/)的"..."仍指向根目录自身
E. 路径名"/etc/passwd"是相对路径,"./test"是绝对路径
题目详解
题目1 正确答案:ABD
- A正确:第一章1.2节明确UNIX四层体系结构的核心层级关系,内核提供基础服务,公用函数库简化编程,shell是命令解释器,应用程序基于上层接口实现功能(对应图1-1)。
- B正确:系统调用是内核暴露的底层接口,直接操控硬件资源,符合"应用→内核"的交互逻辑,是应用访问硬件的唯一合法路径,执行时需切换用户态与内核态。
- C错误 :并非所有库函数都依赖系统调用,例如纯内存操作的
strcpy(字符串复制)、atoi(字符串转整数)等,无需调用内核服务,仅在用户态完成。 - D正确 :第一章1.5节的无缓冲I/O示例(图1-4)直接调用
read/write系统调用,未依赖任何库函数,证明应用可直接使用系统调用。 - E错误:系统调用因需切换用户态与内核态,存在上下文切换开销,效率低于库函数;库函数通过缓冲减少系统调用次数,执行效率更高。
题目2 正确答案:ACD
- A正确 :第一章1.5节明确三个标准文件描述符的常量及数值(0、1、2),定义于
<unistd.h>头文件,替代硬编码以提升可移植性。 - B错误 :第一章1.5节说明
read返回值规则:>0为实际读取字节数,=0为文件尾端,<0为出错(需结合errno判断错误类型)。 - C正确:无缓冲I/O的定义即"不设用户态缓冲",每次I/O操作直接调用内核接口,触发系统调用,与标准I/O的缓冲机制形成对比。
- D正确 :第一章1.5节提到,
BUFFSIZE匹配磁盘块大小(4096字节)可减少系统调用次数(系统调用切换成本高),是图1-4程序的核心优化思路。 - E错误 :
write返回值小于字节数可能是临时错误(如磁盘暂时忙碌),并非致命错误,可通过重试恢复;仅返回-1时表示明确出错。
题目3 正确答案:ACD
- A正确 :第一章1.6节明确
fork的核心特性"一次调用,两次返回",通过返回值区分父子进程,出错时返回-1(如系统资源不足)。 - B错误:子进程复制父进程地址空间(采用写时复制优化,仅修改时实际复制),但共享打开的文件描述符、信号掩码等资源,并非完全独立。
- C正确 :
exec函数族会替换进程的完整映像(代码、数据、堆栈),成功后原fork后的代码段被覆盖,不再执行;仅当exec失败时才会执行后续错误处理代码。 - D正确:第一章1.6节提到,僵尸进程的产生原因是子进程终止后,父进程未回收其终止状态,内核会保留子进程的PID和终止信息。
- E错误:父子进程是独立的执行流,执行顺序由内核调度算法决定,无固定顺序,无法确保父进程先执行。
题目4 正确答案:ACD
- A正确 :第一章1.9节定义信号为"异步事件通知",
SIGINT(中断信号)和SIGFPE(浮点异常信号)是典型示例。 - B错误:硬件异常信号(如除零、内存越界)忽略后可能导致进程状态异常,甚至破坏系统稳定性,不建议忽略,第一章1.9节明确此类信号不推荐忽略。
- C正确 :第一章1.9节的信号捕捉示例(图1-10)中,
signal函数的原型为void (*signal(int signo, void (*func)(int)))(int),注册失败返回SIG_ERR,书中实例均通过err_sys处理此类错误。 - D正确 :标准输出默认是行缓冲,无换行符时缓冲不会自动冲洗,需手动调用
fflush(stdout)确保提示及时显示(如图1-10的sig_int函数设计)。 - E错误 :第一章1.7节明确
errno的规则:"仅函数出错时有效,成功时不清除原值",因此不能通过errno是否为0判断函数是否出错,需先检查函数返回值(如返回-1)再查看errno。
题目5 正确答案:ABD
- A正确:第一章1.4节定义绝对路径与相对路径的核心区别是是否以"/"开头,".../doc/memo"从当前目录的父目录开始解析,属于相对路径。
- B正确:第一章1.4节明确文件名的禁止字符为"/"(用于分隔路径组件)和"\0"(用于终止路径名),POSIX推荐的字符集可避免跨平台兼容性问题。
- C错误 :第一章1.4节说明目录是内核保护的特殊文件,直接用
read读取会破坏目录结构,必须通过opendir/readdir/closedir专用函数操作(如图1-3的ls简化实现)。 - D正确:第一章1.4节提到,"./"和".../"是UNIX目录的默认项,根目录无父目录,因此"..."指向自身。
- E错误:"/etc/passwd"以"/"开头,是绝对路径;"./test"不以"/"开头,是相对路径,混淆了两者的定义。