第15章 C语言教程 - 高级主题
第15章 高级主题
系统函数库
系统函数库是操作系统提供的一组函数,用于访问系统资源和服务。在 C 语言中,这些函数通常通过标准头文件提供。
常用系统函数库
1. 标准输入/输出库 (<stdio.h>)
主要功能: 文件操作、标准输入/输出
常用函数详细说明:
1.1 printf() - 格式化输出到标准输出
函数原型:
1 | int printf(const char *format, ...); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
format | const char * | 是 | 无 | 格式化字符串,包含普通字符和格式说明符 |
... | 可变参数 | 否 | 无 | 要输出的数据,数量和类型必须与格式说明符匹配 |
返回值: 成功输出的字符数,失败返回负数
使用场景: 向标准输出(通常是终端)打印格式化信息
注意事项:
- 格式说明符必须与参数类型匹配,否则会导致未定义行为
- 格式化字符串中可以包含转义序列,如
\n(换行)、\t(制表符)等 - 对于字符串参数,确保其以 null 字符结尾
示例:
1 | printf("Hello, %s!\n", "World"); // 输出: Hello, World! |
1.2 scanf() - 从标准输入读取格式化数据
函数原型:
1 | int scanf(const char *format, ...); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
format | const char * | 是 | 无 | 格式化字符串,包含格式说明符 |
... | 可变参数 | 是 | 无 | 指向要存储读取数据的变量的指针,数量和类型必须与格式说明符匹配 |
返回值: 成功读取并赋值的参数个数,遇到文件结束返回 EOF
使用场景: 从标准输入(通常是键盘)读取用户输入的格式化数据
注意事项:
- 参数必须是指针,否则会导致未定义行为
- 格式说明符必须与参数类型匹配
- 会跳过空白字符(空格、制表符、换行符等),直到遇到非空白字符
- 对于字符串,会读取到空白字符为止
示例:
1 | int age; |
1.3 fopen() - 打开文件
函数原型:
1 | FILE *fopen(const char *filename, const char *mode); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
filename | const char * | 是 | 无 | 要打开的文件路径 |
mode | const char * | 是 | 无 | 文件打开模式 |
文件打开模式:
| 模式 | 描述 |
|---|---|
"r" | 只读模式,文件必须存在 |
"w" | 只写模式,文件不存在则创建,存在则截断为空 |
"a" | 追加模式,文件不存在则创建,写入数据追加到文件末尾 |
"r+" | 读写模式,文件必须存在 |
"w+" | 读写模式,文件不存在则创建,存在则截断为空 |
"a+" | 读写模式,文件不存在则创建,写入数据追加到文件末尾 |
"rb", "wb", "ab", etc. | 二进制模式(在 Windows 上有区别) |
返回值: 成功返回文件指针,失败返回 NULL
使用场景: 打开文件以进行读写操作
注意事项:
- 必须检查返回值是否为 NULL,以处理文件打开失败的情况
- 使用完毕后必须调用
fclose()关闭文件 - 二进制模式在 Windows 上会禁用换行符转换
示例:
1 | FILE *file = fopen("example.txt", "r"); |
1.4 fclose() - 关闭文件
函数原型:
1 | int fclose(FILE *stream); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
stream | FILE * | 是 | 无 | 要关闭的文件指针 |
返回值: 成功返回 0,失败返回 EOF
使用场景: 关闭已打开的文件,释放资源
注意事项:
- 关闭文件后,文件指针不再有效
- 必须检查返回值,以处理关闭失败的情况(如磁盘满)
- 程序结束前应关闭所有打开的文件
示例:
1 | FILE *file = fopen("example.txt", "r"); |
1.5 fread() - 从文件读取数据
函数原型:
1 | size_t fread(void *ptr, size_t size, size_t count, FILE *stream); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
ptr | void * | 是 | 无 | 指向存储读取数据的缓冲区的指针 |
size | size_t | 是 | 无 | 每个数据项的大小(以字节为单位) |
count | size_t | 是 | 无 | 要读取的数据项数量 |
stream | FILE * | 是 | 无 | 文件指针 |
返回值: 成功读取的数据项数量,可能小于 count(如果到达文件末尾或发生错误)
使用场景: 从文件(尤其是二进制文件)读取数据块
取值范围限制:
size和count参数的取值范围:0 到 SIZE_MAX- SIZE_MAX 是系统定义的 size_t 类型的最大值
- 当
size或count为 0 时,函数返回 0,不执行任何操作 - 实际读取的字节数为返回值乘以
size
注意事项:
- 通常用于读取二进制文件或结构化数据
- 要检查是否到达文件末尾,应使用
feof()函数 - 要检查是否发生错误,应使用
ferror()函数 - 错误处理:
- 当返回值小于
count时,可能是到达文件末尾或发生错误 - 应先检查
ferror(),再检查feof()
- 当返回值小于
- 性能考虑:
- 对于大文件,使用较大的缓冲区可以提高读取速度
- 避免频繁调用 fread() 读取小数据块
- 缓冲区要求:
ptr指向的缓冲区必须足够大,至少能容纳size * count字节- 缓冲区的对齐方式可能影响读取性能
- 文本文件与二进制文件:
- 在文本模式下,某些系统可能会对换行符进行转换
- 在二进制模式下,数据会被原样读取
- 流状态:
- 读取操作可能会更新流的文件位置指示器
- 发生错误时,流的错误指示器会被设置
示例:
1 | FILE *file = fopen("data.bin", "rb"); |
1.6 fwrite() - 向文件写入数据
函数原型:
1 | size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
ptr | const void * | 是 | 无 | 指向要写入的数据的指针 |
size | size_t | 是 | 无 | 每个数据项的大小(以字节为单位) |
count | size_t | 是 | 无 | 要写入的数据项数量 |
stream | FILE * | 是 | 无 | 文件指针 |
返回值: 成功写入的数据项数量,可能小于 count(如果发生错误)
使用场景: 向文件(尤其是二进制文件)写入数据块
取值范围限制:
size和count参数的取值范围:0 到 SIZE_MAX- SIZE_MAX 是系统定义的 size_t 类型的最大值
- 当
size或count为 0 时,函数返回 0,不执行任何操作 - 实际写入的字节数为返回值乘以
size
注意事项:
- 通常用于写入二进制文件或结构化数据
- 应检查返回值,以确保所有数据都已成功写入
- 对于文本文件,建议使用
fprintf()或fputs() - 错误处理:
- 当返回值小于
count时,表示发生了错误 - 应使用
ferror()函数检查具体的错误原因
- 当返回值小于
- 缓冲区刷新:
- 数据可能会先写入文件流的缓冲区,而不是直接写入磁盘
- 可以使用
fflush()函数强制刷新缓冲区 - 关闭文件时会自动刷新缓冲区
- 性能考虑:
- 对于大文件,使用较大的缓冲区可以提高写入速度
- 避免频繁调用 fwrite() 写入小数据块
- 文本文件与二进制文件:
- 在文本模式下,某些系统可能会对换行符进行转换
- 在二进制模式下,数据会被原样写入
- 流状态:
- 写入操作可能会更新流的文件位置指示器
- 发生错误时,流的错误指示器会被设置
- 磁盘空间:
- 如果磁盘空间不足,fwrite() 会失败
- 应始终检查返回值,即使之前的写入操作都成功了
示例:
1 | FILE *file = fopen("data.bin", "wb"); |
1.7 fgets() - 从文件读取一行
函数原型:
1 | char *fgets(char *s, int size, FILE *stream); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s | char * | 是 | 无 | 指向存储读取字符串的缓冲区的指针 |
size | int | 是 | 无 | 缓冲区大小(包括 null 终止符) |
stream | FILE * | 是 | 无 | 文件指针 |
返回值: 成功返回 s,到达文件末尾或发生错误返回 NULL
使用场景: 从文件或标准输入读取一行文本
注意事项:
- 最多读取
size-1个字符,剩余空间用于存储 null 终止符 - 会读取并包含换行符(如果行长度小于
size-1) - 当读取到换行符或到达文件末尾时停止
示例:
1 | FILE *file = fopen("example.txt", "r"); |
1.8 fputs() - 向文件写入字符串
函数原型:
1 | int fputs(const char *s, FILE *stream); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s | const char * | 是 | 无 | 要写入的以 null 结尾的字符串 |
stream | FILE * | 是 | 无 | 文件指针 |
返回值: 成功返回非负值,失败返回 EOF
使用场景: 向文件写入字符串
注意事项:
- 不会自动添加换行符,需要手动添加
- 字符串必须以 null 字符结尾
- 对于格式化输出,建议使用
fprintf()
示例:
1 | FILE *file = fopen("example.txt", "w"); |
1.9 feof() - 检查文件是否到达末尾
函数原型:
1 | int feof(FILE *stream); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
stream | FILE * | 是 | 无 | 文件指针 |
返回值: 如果文件到达末尾返回非零值,否则返回 0
使用场景: 检查文件读取操作是否因为到达文件末尾而停止
注意事项:
- 只有在读取操作尝试读取超出文件末尾后,才会设置文件结束标志
- 不会检测文件错误,应使用
ferror()检查错误
示例:
1 | FILE *file = fopen("example.txt", "r"); |
1.10 ferror() - 检查文件是否发生错误
函数原型:
1 | int ferror(FILE *stream); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
stream | FILE * | 是 | 无 | 文件指针 |
返回值: 如果文件发生错误返回非零值,否则返回 0
使用场景: 检查文件操作是否发生错误
注意事项:
- 错误标志会一直保持,直到调用
clearerr()清除 - 应在文件操作后检查错误状态
示例:
1 | FILE *file = fopen("example.txt", "r"); |
使用示例:
1 |
|
跨平台兼容性说明
在使用系统函数库时,需要注意不同操作系统之间的差异,以确保代码的可移植性。
主要平台差异
| 平台 | 差异说明 | 解决方案 |
|---|---|---|
| Windows | 使用不同的头文件(如 <windows.h>)和函数名 | 使用条件编译,为不同平台提供不同实现 |
| Linux/Unix | 遵循 POSIX 标准,函数名和行为较为一致 | 使用 POSIX 标准函数,确保兼容性 |
| macOS | 基于 BSD,部分函数与 Linux 有差异 | 参考 macOS 特定文档,注意函数参数和返回值的差异 |
头文件差异
- Windows: 常用头文件包括 <windows.h>, <winsock2.h> 等
- Linux/Unix: 常用头文件包括 <unistd.h>, <sys/socket.h>, <sys/stat.h> 等
- macOS: 兼容大部分 BSD 和 POSIX 头文件
数据类型差异
time_t类型在不同平台上的大小可能不同(32 位或 64 位)pid_t,uid_t等类型的大小也可能不同- 建议使用
<stdint.h>中定义的固定大小类型,如int32_t,int64_t等
函数行为差异
signal()函数在不同平台上的行为可能不同,建议使用sigaction()- 文件路径分隔符不同:Windows 使用
\,Unix/Linux 使用/ - 换行符不同:Windows 使用
\r\n,Unix/Linux 使用\n
系统函数库使用的最佳实践
1. 错误处理
- 始终检查函数返回值,特别是可能失败的系统调用
- 使用
errno变量和perror()或strerror()函数来获取详细的错误信息 - 为错误处理编写清晰的代码,避免忽略错误
2. 资源管理
- 对于分配的资源(如内存、文件描述符、网络连接等),确保在使用完毕后释放
- 使用 RAII(资源获取即初始化)模式或封装资源管理函数
- 避免资源泄漏,特别是在循环和错误处理路径中
3. 安全性
- 避免使用不安全的函数,如
strcpy()、gets()等 - 使用边界检查,防止缓冲区溢出
- 对用户输入进行验证和 sanitization
- 注意权限管理,避免权限提升漏洞
4. 性能优化
- 避免频繁的系统调用,尽量批量处理操作
- 使用适当的缓冲区大小,平衡内存使用和 I/O 性能
- 对于计算密集型任务,考虑使用多线程或异步 I/O
- 缓存常用的计算结果,避免重复计算
5. 代码风格和可读性
- 为系统函数调用添加清晰的注释,说明其用途和可能的错误情况
- 使用有意义的变量名和函数名
- 遵循项目的代码风格指南
- 编写单元测试,确保函数行为符合预期
6. 文档和维护
- 记录系统函数的使用方式和注意事项
- 定期更新依赖库和系统函数的使用方式
- 监控系统函数的性能和错误率
- 为复杂的系统函数调用编写封装函数,提高代码的可维护性
7. 调试技巧
- 使用
strace(Linux)或Process Explorer(Windows)等工具跟踪系统调用 - 为系统函数调用添加日志,记录参数和返回值
- 使用调试器逐步执行代码,观察系统函数的行为
- 模拟错误情况,测试错误处理代码的正确性
总结
系统函数库是 C 语言编程中不可或缺的一部分,它们提供了与操作系统交互的能力。通过正确理解和使用这些函数,开发人员可以编写更加高效、可靠和安全的应用程序。
在使用系统函数时,应注意以下几点:
- 了解函数的参数和返回值:确保正确传递参数,并检查返回值以处理错误
- 注意平台差异:编写可移植的代码,处理不同操作系统之间的差异
- 遵循最佳实践:包括错误处理、资源管理、安全性和性能优化
- 持续学习:系统函数库不断发展,应关注新的函数和最佳实践
通过合理使用系统函数库,开发人员可以充分利用操作系统提供的功能,构建更加功能强大的应用程序。
1 |
|
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s | const char * | 是 | 无 | 指向要计算长度的以 null 结尾的字符串 |
返回值: 字符串中字符的数量(不包括 null 终止符)
使用场景: 计算字符串的长度,用于字符串操作的边界检查
注意事项:
- 字符串必须以 null 字符结尾,否则会导致未定义行为
- 时间复杂度为 O(n),其中 n 是字符串长度
示例:
1 | char str[] = "Hello, World!"; |
2.2 strcpy() - 复制字符串
函数原型:
1 | char *strcpy(char *dest, const char *src); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
dest | char * | 是 | 无 | 指向目标缓冲区的指针 |
src | const char * | 是 | 无 | 指向要复制的以 null 结尾的字符串 |
返回值: 指向目标缓冲区 dest 的指针
使用场景: 将一个字符串复制到另一个缓冲区
取值范围限制:
src必须指向一个以 null 结尾的字符串dest必须指向一个足够大的缓冲区,能够容纳整个源字符串(包括 null 终止符)- 缓冲区大小没有明确的上限,但应根据实际需要合理分配
注意事项:
- 目标缓冲区必须足够大,能够容纳源字符串(包括 null 终止符)
- 会覆盖目标缓冲区中的原有内容
- 源字符串和目标缓冲区不能重叠,否则会导致未定义行为
- 由于可能导致缓冲区溢出,建议使用
strncpy()代替 - 安全性考虑:
strcpy()是一个不安全的函数,因为它不检查目标缓冲区的大小- 可能导致缓冲区溢出漏洞,被攻击者利用
- 在安全敏感的代码中,应使用更安全的替代函数
- 替代函数推荐:
strncpy():指定最大复制长度strlcpy():(某些系统提供)更安全的字符串复制函数snprintf():格式化输出到缓冲区,可指定最大长度
- 性能考虑:
strcpy()的时间复杂度为 O(n),其中 n 是字符串长度- 对于短字符串,性能影响可以忽略
- 对于长字符串,应确保目标缓冲区足够大
- 空字符串处理:
- 如果
src是空字符串(只包含 null 终止符),strcpy()只会复制 null 终止符到dest
- 如果
- 内存重叠:
- 如果
src和dest指向的内存区域重叠,行为未定义 - 应使用
memmove()处理内存重叠的情况
- 如果
示例:
1 | char src[] = "Hello"; |
2.3 strncpy() - 复制指定长度的字符串
函数原型:
1 | char *strncpy(char *dest, const char *src, size_t n); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
dest | char * | 是 | 无 | 指向目标缓冲区的指针 |
src | const char * | 是 | 无 | 指向要复制的以 null 结尾的字符串 |
n | size_t | 是 | 无 | 要复制的最大字符数 |
返回值: 指向目标缓冲区 dest 的指针
使用场景: 安全地复制字符串,避免缓冲区溢出
取值范围限制:
n参数的取值范围:0 到 SIZE_MAX- SIZE_MAX 是系统定义的 size_t 类型的最大值
dest必须指向一个至少为n字节大小的缓冲区src必须指向一个以 null 结尾的字符串(除非n为 0)
注意事项:
- 如果源字符串长度小于
n,会用 null 字符填充目标缓冲区直到n个字符 - 如果源字符串长度大于或等于
n,目标缓冲区不会以 null 字符结尾 - 源字符串和目标缓冲区不能重叠
- null 终止符处理:
- 当源字符串长度小于
n时:会将剩余空间用 null 字符填充 - 当源字符串长度大于或等于
n时:不会添加 null 终止符,目标字符串不是以 null 结尾的 - 建议在使用
strncpy()后,手动添加 null 终止符:dest[n-1] = '\0';
- 当源字符串长度小于
- 安全性考虑:
strncpy()比strcpy()更安全,但仍然存在安全隐患- 如果忘记手动添加 null 终止符,可能导致后续的字符串操作出现问题
- 在安全敏感的代码中,应使用更安全的替代函数
- 替代函数推荐:
strlcpy():(某些系统提供)更安全的字符串复制函数,总是添加 null 终止符snprintf():格式化输出到缓冲区,可指定最大长度,总是添加 null 终止符
- 性能考虑:
- 当源字符串长度远小于
n时,strncpy()会填充大量 null 字符,可能影响性能 - 对于短字符串,性能影响可以忽略
- 对于长字符串,应合理设置
n的值
- 当源字符串长度远小于
- 内存重叠:
- 源字符串和目标缓冲区不能重叠,否则会导致未定义行为
- 应使用
memmove()处理内存重叠的情况
示例:
1 | char src[] = "Hello, World!"; |
2.4 strcmp() - 比较字符串
函数原型:
1 | int strcmp(const char *s1, const char *s2); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s1 | const char * | 是 | 无 | 指向第一个要比较的以 null 结尾的字符串 |
s2 | const char * | 是 | 无 | 指向第二个要比较的以 null 结尾的字符串 |
返回值:
- 小于 0:
s1小于s2 - 等于 0:
s1等于s2 - 大于 0:
s1大于s2
使用场景: 比较两个字符串的大小,用于排序或查找
注意事项:
- 比较是基于字符的 ASCII 值
- 字符串必须以 null 结尾
- 比较会一直进行,直到遇到不同的字符或 null 终止符
示例:
1 | char str1[] = "apple"; |
2.5 strncmp() - 比较指定长度的字符串
函数原型:
1 | int strncmp(const char *s1, const char *s2, size_t n); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s1 | const char * | 是 | 无 | 指向第一个要比较的以 null 结尾的字符串 |
s2 | const char * | 是 | 无 | 指向第二个要比较的以 null 结尾的字符串 |
n | size_t | 是 | 无 | 要比较的最大字符数 |
返回值:
- 小于 0:
s1小于s2 - 等于 0:
s1等于s2(前n个字符) - 大于 0:
s1大于s2
使用场景: 比较两个字符串的前 n 个字符,用于前缀匹配
注意事项:
- 比较是基于字符的 ASCII 值
- 如果其中一个字符串长度小于
n,比较会在遇到 null 终止符时停止
示例:
1 | char str1[] = "Hello, World!"; |
2.6 strcat() - 连接字符串
函数原型:
1 | char *strcat(char *dest, const char *src); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
dest | char * | 是 | 无 | 指向目标字符串的指针(必须以 null 结尾) |
src | const char * | 是 | 无 | 指向要追加的以 null 结尾的字符串 |
返回值: 指向目标字符串 dest 的指针
使用场景: 将一个字符串追加到另一个字符串的末尾
注意事项:
- 目标缓冲区必须足够大,能够容纳原始字符串和追加的字符串(包括 null 终止符)
- 源字符串会追加到目标字符串的 null 终止符位置
- 源字符串和目标缓冲区不能重叠,否则会导致未定义行为
- 由于可能导致缓冲区溢出,建议使用
strncat()代替
示例:
1 | char dest[50] = "Hello, "; // 确保目标缓冲区足够大 |
2.7 strncat() - 连接指定长度的字符串
函数原型:
1 | char *strncat(char *dest, const char *src, size_t n); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
dest | char * | 是 | 无 | 指向目标字符串的指针(必须以 null 结尾) |
src | const char * | 是 | 无 | 指向要追加的以 null 结尾的字符串 |
n | size_t | 是 | 无 | 要追加的最大字符数 |
返回值: 指向目标字符串 dest 的指针
使用场景: 安全地追加字符串,避免缓冲区溢出
注意事项:
- 会在追加的字符串末尾添加 null 终止符
- 目标缓冲区必须足够大,能够容纳原始字符串、追加的字符串和 null 终止符
- 源字符串和目标缓冲区不能重叠
示例:
1 | char dest[20] = "Hello, "; // 确保目标缓冲区足够大 |
2.8 strchr() - 查找字符首次出现的位置
函数原型:
1 | char *strchr(const char *s, int c); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s | const char * | 是 | 无 | 指向要搜索的以 null 结尾的字符串 |
c | int | 是 | 无 | 要查找的字符(会被转换为 unsigned char) |
返回值: 指向字符串中首次出现字符 c 的位置的指针,如果未找到返回 NULL
使用场景: 在字符串中查找特定字符的位置
注意事项:
- 会搜索到 null 终止符为止
- 也会查找 null 终止符本身
示例:
1 | char str[] = "Hello, World!"; |
2.9 strrchr() - 查找字符最后出现的位置
函数原型:
1 | char *strrchr(const char *s, int c); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s | const char * | 是 | 无 | 指向要搜索的以 null 结尾的字符串 |
c | int | 是 | 无 | 要查找的字符(会被转换为 unsigned char) |
返回值: 指向字符串中最后出现字符 c 的位置的指针,如果未找到返回 NULL
使用场景: 在字符串中查找特定字符的最后一个位置
注意事项:
- 会搜索到字符串开头为止
- 也会查找 null 终止符本身
示例:
1 | char str[] = "Hello, World!"; |
2.10 strstr() - 查找子字符串
函数原型:
1 | char *strstr(const char *haystack, const char *needle); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
haystack | const char * | 是 | 无 | 指向要搜索的以 null 结尾的字符串 |
needle | const char * | 是 | 无 | 指向要查找的以 null 结尾的子字符串 |
返回值: 指向子字符串在原字符串中首次出现位置的指针,如果未找到返回 NULL
使用场景: 在字符串中查找子字符串,用于模式匹配
注意事项:
- 如果
needle是空字符串,返回haystack - 时间复杂度为 O(m*n),其中 m 和 n 分别是两个字符串的长度
示例:
1 | char haystack[] = "Hello, World!"; |
2.11 memset() - 填充内存块
函数原型:
1 | void *memset(void *s, int c, size_t n); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s | void * | 是 | 无 | 指向要填充的内存块的指针 |
c | int | 是 | 无 | 要填充的值(会被转换为 unsigned char) |
n | size_t | 是 | 无 | 要填充的字节数 |
返回值: 指向内存块 s 的指针
使用场景: 将内存块设置为特定值,通常用于初始化或清空内存
注意事项:
- 填充的是字节,不是多字节值
- 对于非字符类型的数组,通常用于设置为 0 或 -1
示例:
1 | char buffer[20]; |
2.12 memcpy() - 复制内存块
函数原型:
1 | void *memcpy(void *dest, const void *src, size_t n); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
dest | void * | 是 | 无 | 指向目标内存块的指针 |
src | const void * | 是 | 无 | 指向源内存块的指针 |
n | size_t | 是 | 无 | 要复制的字节数 |
返回值: 指向目标内存块 dest 的指针
使用场景: 复制任意类型的内存块,包括非字符串数据
注意事项:
- 目标内存块必须足够大,能够容纳要复制的数据
- 源内存块和目标内存块不能重叠,否则会导致未定义行为(应使用
memmove()代替)
示例:
1 | int src[] = {1, 2, 3, 4, 5}; |
2.13 memcmp() - 比较内存块
函数原型:
1 | int memcmp(const void *s1, const void *s2, size_t n); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s1 | const void * | 是 | 无 | 指向第一个内存块的指针 |
s2 | const void * | 是 | 无 | 指向第二个内存块的指针 |
n | size_t | 是 | 无 | 要比较的字节数 |
返回值:
- 小于 0:
s1小于s2 - 等于 0:
s1等于s2(前n个字节) - 大于 0:
s1大于s2
使用场景: 比较任意类型的内存块,包括非字符串数据
注意事项:
- 比较是基于字节的,逐字节比较
- 对于多字节类型(如整数、浮点数),比较结果可能与预期不同,因为取决于字节序
示例:
1 | int a[] = {1, 2, 3}; |
使用示例:
1 |
|
3. 数学库 (<math.h>)
主要功能: 数学计算
常用函数详细说明:
3.1 sin() - 正弦函数
函数原型:
1 | double sin(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 角度(弧度制) |
返回值: x 的正弦值,范围在 [-1, 1] 之间
使用场景: 计算角度的正弦值,用于三角函数计算
注意事项:
- 参数
x必须是弧度制,不是角度制 - 对于角度制,需要先转换为弧度:
弧度 = 角度 * π / 180
示例:
1 |
|
3.2 cos() - 余弦函数
函数原型:
1 | double cos(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 角度(弧度制) |
返回值: x 的余弦值,范围在 [-1, 1] 之间
使用场景: 计算角度的余弦值,用于三角函数计算
注意事项:
- 参数
x必须是弧度制,不是角度制 - 对于角度制,需要先转换为弧度:
弧度 = 角度 * π / 180
示例:
1 |
|
3.3 tan() - 正切函数
函数原型:
1 | double tan(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 角度(弧度制) |
返回值: x 的正切值
使用场景: 计算角度的正切值,用于三角函数计算
注意事项:
- 参数
x必须是弧度制,不是角度制 - 当
x接近 π/2 + kπ(k 为整数)时,结果会趋近于无穷大
示例:
1 |
|
3.4 sqrt() - 平方根函数
函数原型:
1 | double sqrt(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 要求平方根的值,必须非负 |
返回值: x 的平方根
使用场景: 计算非负数的平方根,用于几何计算、物理计算等
取值范围限制:
x参数的取值范围:0.0 ≤ x ≤ +∞- 当
x< 0.0 时,会产生域错误 - 当
x= 0.0 时,返回 0.0 - 当
x= +∞ 时,返回 +∞
注意事项:
- 如果
x为负数,会产生域错误(domain error),errno 会被设置为 EDOM - 如果
x为 0 或正无穷大,返回相同的值 - 精度考虑:
- 返回值的精度取决于实现,但通常是双精度浮点数的最大精度
- 对于完全平方数,返回值应该是精确的整数
- 性能考虑:
sqrt()是一个相对昂贵的操作,应避免在性能关键的代码中频繁调用- 某些处理器有硬件平方根指令,可以加速此操作
- 替代方法:
- 对于整数平方根,可以使用
sqrtl()获取更高精度 - 对于需要多次计算平方根的场景,可以考虑使用查表法或其他优化技术
- 对于整数平方根,可以使用
- 特殊值处理:
sqrt(NaN)返回 NaNsqrt(+0.0)返回 +0.0sqrt(-0.0)返回 -0.0sqrt(+∞)返回 +∞
示例:
1 |
|
3.5 pow() - 幂运算函数
函数原型:
1 | double pow(double x, double y); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 底数 |
y | double | 是 | 无 | 指数 |
返回值: x 的 y 次幂,即 x^y
使用场景: 计算任意实数的幂,用于数学计算、科学计算等
取值范围限制:
x和y的取值范围受以下规则限制:- 当
x< 0 时,y必须是整数 - 当
x= 0 时,y必须 > 0 - 当
x= +∞ 时,根据y的值返回不同结果 - 当
y= 0 时,返回 1.0(除非x= 0,此时返回 NaN) - 当
y= +∞ 时,根据x的值返回不同结果 - 当
y= -∞ 时,根据x的值返回不同结果
- 当
注意事项:
- 当
x为负数且y不是整数时,会产生域错误,errno 会被设置为 EDOM - 当
x为 0 且y为负数时,会产生域错误,errno 会被设置为 EDOM - 当结果溢出时,会产生溢出错误,返回 ±HUGE_VAL,errno 会被设置为 ERANGE
- 当结果下溢时,会产生下溢错误,返回 0.0,errno 会被设置为 ERANGE
- 特殊值处理:
pow(1.0, y)返回 1.0 对于任何ypow(x, 0.0)返回 1.0 对于任何非零xpow(NaN, y)返回 NaNpow(x, NaN)返回 NaNpow(+0.0, y)对于y > 0返回 +0.0pow(-0.0, y)对于y > 0且y为奇数返回 -0.0pow(+0.0, y)对于y < 0返回 +∞pow(-0.0, y)对于y < 0且y为奇数返回 -∞
- 精度考虑:
- 对于整数指数,使用循环乘法可能会更精确
- 对于某些特殊情况(如平方、立方),使用专门的函数可能会更高效
- 性能考虑:
pow()是一个相对昂贵的操作,应避免在性能关键的代码中频繁调用- 对于整数指数,可以考虑使用快速幂算法
示例:
1 |
|
3.6 exp() - 指数函数
函数原型:
1 | double exp(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 指数值 |
返回值: 自然常数 e 的 x 次幂,即 e^x
使用场景: 计算指数增长、衰减等,用于科学计算、金融计算等
注意事项:
- 当结果溢出时,会产生溢出错误
- 当
x为负无穷大时,返回 0
示例:
1 |
|
3.7 log() - 自然对数函数
函数原型:
1 | double log(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 要求对数的值,必须为正数 |
返回值: x 的自然对数(以 e 为底)
使用场景: 计算自然对数,用于科学计算、数学变换等
注意事项:
- 如果
x为负数或 0,会产生域错误 - 如果
x为 1,返回 0 - 如果
x为正无穷大,返回正无穷大
示例:
1 |
|
3.8 log10() - 常用对数函数
函数原型:
1 | double log10(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 要求对数的值,必须为正数 |
返回值: x 的常用对数(以 10 为底)
使用场景: 计算以 10 为底的对数,用于科学计数法、工程计算等
注意事项:
- 如果
x为负数或 0,会产生域错误 - 如果
x为 10 的整数次幂,返回整数
示例:
1 |
|
3.9 abs() - 整数绝对值函数
函数原型:
1 | int abs(int x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | int | 是 | 无 | 要求绝对值的整数 |
返回值: x 的绝对值
使用场景: 计算整数的绝对值,用于数学计算、比较操作等
注意事项:
- 对于
int类型的最小值(如 -2147483648),由于溢出,结果可能未定义 - 对于其他整数类型,应使用相应的函数:
labs()(长整型)、llabs()(长 long 整型)
示例:
1 |
|
3.10 fabs() - 浮点数绝对值函数
函数原型:
1 | double fabs(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 要求绝对值的浮点数 |
返回值: x 的绝对值
使用场景: 计算浮点数的绝对值,用于数学计算、比较操作等
注意事项:
- 对于
float类型,应使用fabsf()函数 - 对于
long double类型,应使用fabsl()函数
示例:
1 |
|
3.11 ceil() - 向上取整函数
函数原型:
1 | double ceil(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 要取整的值 |
返回值: 不小于 x 的最小整数
使用场景: 计算上界值,用于资源分配、容量计算等
注意事项:
- 如果
x已经是整数,返回相同的值 - 对于
float类型,应使用ceilf()函数 - 对于
long double类型,应使用ceill()函数
示例:
1 |
|
3.12 floor() - 向下取整函数
函数原型:
1 | double floor(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 要取整的值 |
返回值: 不大于 x 的最大整数
使用场景: 计算下界值,用于整数除法、区间划分等
注意事项:
- 如果
x已经是整数,返回相同的值 - 对于
float类型,应使用floorf()函数 - 对于
long double类型,应使用floorl()函数
示例:
1 |
|
3.13 round() - 四舍五入函数
函数原型:
1 | double round(double x); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
x | double | 是 | 无 | 要四舍五入的值 |
返回值: 最接近 x 的整数,如果 x 正好在两个整数中间,则返回偶数
使用场景: 进行四舍五入取整,用于数值计算、显示格式化等
注意事项:
- 对于
float类型,应使用roundf()函数 - 对于
long double类型,应使用roundl()函数
示例:
1 |
|
3.14 rand() - 随机数生成函数
函数原型:
1 | int rand(void); |
参数说明: 无
返回值: 范围在 [0, RAND_MAX] 之间的伪随机整数
使用场景: 生成随机数,用于游戏、模拟、随机抽样等
注意事项:
rand()生成的是伪随机数,需要先使用srand()初始化种子- 默认种子为 1,每次程序运行会生成相同的序列
- 结果范围依赖于实现,RAND_MAX 至少为 32767
示例:
1 |
|
3.15 srand() - 随机数种子初始化函数
函数原型:
1 | void srand(unsigned int seed); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
seed | unsigned int | 是 | 无 | 随机数种子值 |
返回值: 无
使用场景: 初始化随机数生成器的种子,确保每次运行生成不同的随机数序列
注意事项:
- 通常使用
time(NULL)作为种子,因为时间值每次运行都不同 - 应在程序开始时只调用一次
srand(),多次调用会导致随机数序列质量下降
示例:
1 |
|
使用示例:
1 |
|
4. 时间和日期库 (<time.h>)
主要功能: 时间和日期操作
常用函数详细说明:
4.1 time() - 获取当前时间戳
函数原型:
1 | time_t time(time_t *timer); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
timer | time_t * | 否 | NULL | 指向存储时间戳的变量的指针,如果为 NULL,则只返回时间戳 |
返回值: 当前时间戳(从 1970-01-01 00:00:00 UTC 开始经过的秒数),失败返回 (time_t)-1
使用场景: 获取当前系统时间,用于时间计算、时间戳生成等
取值范围限制:
timer参数:可以是 NULL 或指向有效的time_t变量的指针- 返回值的取值范围:
- 成功:从 1970-01-01 00:00:00 UTC 开始经过的秒数
- 失败:(time_t)-1
time_t类型的范围:- 通常为 32 位或 64 位整数
- 32 位
time_t的最大值对应于 2038-01-19 03:14:07 UTC(2038 年问题) - 64 位
time_t可以表示到约 292 亿年后
注意事项:
time_t类型通常是长整型,但具体实现可能不同- 时间戳是基于 UTC 的,不受时区影响
- 错误处理:
- 当函数失败时,返回 (time_t)-1,errno 会被设置为相应的错误代码
- 常见的失败原因:系统时钟不可用
- 2038 年问题:
- 使用 32 位
time_t的系统在 2038-01-19 03:14:07 UTC 后会出现溢出 - 建议使用 64 位
time_t的系统或库
- 使用 32 位
- 精度:
time()函数的精度通常为秒级- 对于更高精度的时间测量,应使用
gettimeofday()或clock_gettime()函数
示例:
1 |
|
4.2 ctime() - 转换时间戳为字符串
函数原型:
1 | char *ctime(const time_t *timep); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
timep | const time_t * | 是 | 无 | 指向要转换的时间戳的指针 |
返回值: 指向格式化时间字符串的指针,格式为 “Wed Jun 30 21:49:08 1993\n”
使用场景: 将时间戳转换为人类可读的字符串格式,用于日志记录、用户界面等
注意事项:
- 返回的字符串存储在静态内存中,每次调用都会覆盖之前的内容
- 线程不安全,多线程环境应使用
ctime_r() - 字符串包含换行符
示例:
1 |
|
4.3 localtime() - 转换时间戳为本地时间结构
函数原型:
1 | struct tm *localtime(const time_t *timep); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
timep | const time_t * | 是 | 无 | 指向要转换的时间戳的指针 |
返回值: 指向 struct tm 结构的指针,包含本地时间信息
使用场景: 将时间戳转换为本地时区的分解时间结构,用于日期时间的各个字段的访问
注意事项:
- 返回的结构存储在静态内存中,每次调用都会覆盖之前的内容
- 线程不安全,多线程环境应使用
localtime_r() struct tm结构包含年、月、日、时、分、秒等字段
示例:
1 |
|
4.4 gmtime() - 转换时间戳为 UTC 时间结构
函数原型:
1 | struct tm *gmtime(const time_t *timep); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
timep | const time_t * | 是 | 无 | 指向要转换的时间戳的指针 |
返回值: 指向 struct tm 结构的指针,包含 UTC 时间信息
使用场景: 将时间戳转换为 UTC 时区的分解时间结构,用于跨时区的时间处理
注意事项:
- 返回的结构存储在静态内存中,每次调用都会覆盖之前的内容
- 线程不安全,多线程环境应使用
gmtime_r() struct tm结构包含年、月、日、时、分、秒等字段
示例:
1 |
|
4.5 mktime() - 转换时间结构为时间戳
函数原型:
1 | time_t mktime(struct tm *tm); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
tm | struct tm * | 是 | 无 | 指向包含分解时间信息的 struct tm 结构的指针 |
返回值: 对应的时间戳,失败返回 (time_t)-1
使用场景: 将分解的时间结构转换回时间戳,用于时间计算、时间比较等
取值范围限制:
tm参数的字段取值范围:tm_sec:0-61(允许闰秒)tm_min:0-59tm_hour:0-23tm_mday:1-31tm_mon:0-11(0 表示一月)tm_year:自 1900 年以来的年数tm_isdst:-1(自动判断)、0(不使用夏令时)、1(使用夏令时)
- 函数会自动调整超出范围的字段
注意事项:
- 函数会自动调整
struct tm中的字段,例如将 13 月调整为下一年的 1 月,将 60 秒调整为 1 分 0 秒等 - 忽略
tm_wday和tm_yday字段,根据其他字段计算 - 基于本地时区进行转换
- 错误处理:
- 当函数失败时,返回 (time_t)-1,errno 会被设置为相应的错误代码
- 常见的失败原因:时间值超出
time_t类型的范围
- 夏令时处理:
- 当
tm_isdst为 -1 时,函数会尝试自动判断是否为夏令时 - 当
tm_isdst为 0 或 1 时,函数会使用指定的值
- 当
- 字段调整:
- 函数执行后,会更新
tm指向的结构中的所有字段,包括tm_wday和tm_yday
- 函数执行后,会更新
- 范围限制:
- 转换结果必须在
time_t类型的范围内 - 对于 32 位
time_t,有效范围通常是 1901-12-13 20:45:52 UTC 到 2038-01-19 03:14:07 UTC - 对于 64 位
time_t,有效范围要大得多
- 转换结果必须在
示例:
1 |
|
4.6 difftime() - 计算时间差
函数原型:
1 | double difftime(time_t time1, time_t time0); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
time1 | time_t | 是 | 无 | 结束时间的时间戳 |
time0 | time_t | 是 | 无 | 开始时间的时间戳 |
返回值: time1 和 time0 之间的时间差(以秒为单位),即 time1 - time0
使用场景: 计算两个时间点之间的时间差,用于性能测量、计时等
注意事项:
- 返回值是双精度浮点数,可以表示小数秒
- 时间差的符号表示时间顺序:正数表示 time1 在 time0 之后,负数表示 time1 在 time0 之前
示例:
1 |
|
4.7 strftime() - 格式化时间字符串
函数原型:
1 | size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *tm); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
s | char * | 是 | 无 | 指向存储结果字符串的缓冲区的指针 |
maxsize | size_t | 是 | 无 | 缓冲区的最大大小(包括 null 终止符) |
format | const char * | 是 | 无 | 格式化字符串,包含普通字符和格式说明符 |
tm | const struct tm * | 是 | 无 | 指向包含时间信息的 struct tm 结构的指针 |
返回值: 成功写入缓冲区的字符数(不包括 null 终止符),如果缓冲区太小返回 0
使用场景: 自定义格式的时间字符串,用于日志记录、用户界面、数据存储等
注意事项:
- 格式化字符串中的格式说明符以 % 开头,如 %Y(年)、%m(月)、%d(日)等
- 缓冲区必须足够大,能够容纳格式化后的字符串和 null 终止符
- 支持的格式说明符可能因实现而异
常用格式说明符:
| 格式 | 描述 | 示例 |
|---|---|---|
%Y | 四位年份 | 2023 |
%m | 两位月份 (01-12) | 12 |
%d | 两位日期 (01-31) | 25 |
%H | 24小时制小时 (00-23) | 14 |
%M | 分钟 (00-59) | 30 |
%S | 秒 (00-59) | 45 |
%A | 完整星期名 | Monday |
%a | 缩写星期名 | Mon |
%B | 完整月份名 | December |
%b | 缩写月份名 | Dec |
%x | 本地日期格式 | 12/25/23 |
%X | 本地时间格式 | 14:30:45 |
示例:
1 |
|
使用示例:
1 |
|
5. 内存分配库 (<stdlib.h>)
主要功能: 内存管理、程序控制
常用函数详细说明:
5.1 malloc() - 分配内存
函数原型:
1 | void *malloc(size_t size); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
size | size_t | 是 | 无 | 要分配的内存大小(以字节为单位) |
返回值: 指向分配的内存块的指针,失败返回 NULL
使用场景: 动态分配内存,用于不确定大小的数据结构、数组等
取值范围限制:
size参数的取值范围:0 到 SIZE_MAX- SIZE_MAX 是系统定义的 size_t 类型的最大值
- 当
size为 0 时,行为取决于实现:- 可能返回 NULL
- 可能返回一个指向大小为 0 的内存块的指针,该指针后续应通过
free()释放
注意事项:
- 分配的内存未初始化,内容是不确定的
- 应检查返回值是否为 NULL,以处理内存分配失败的情况
- 分配的内存使用完毕后必须使用
free()释放,否则会导致内存泄漏 - 内存对齐:
- 返回的指针保证对于任何内置类型都对齐
- 通常对齐到 8 字节或 16 字节边界
- 内存分配失败的原因:
- 系统内存不足
- 进程的内存使用达到 RLIMIT_AS 限制
- 请求的内存大小超过系统可分配的最大值
- 内存碎片:频繁的 malloc() 和 free() 可能导致内存碎片
- 性能考虑:
- 对于小内存分配,可能会分配比请求更大的内存块
- 对于大内存分配,可能会使用不同的内存分配策略
- 安全考虑:
- 避免缓冲区溢出
- 避免使用已释放的内存
- 避免双重释放
示例:
1 |
|
5.2 calloc() - 分配并初始化内存
函数原型:
1 | void *calloc(size_t nmemb, size_t size); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
nmemb | size_t | 是 | 无 | 要分配的元素数量 |
size | size_t | 是 | 无 | 每个元素的大小(以字节为单位) |
返回值: 指向分配的内存块的指针,失败返回 NULL
使用场景: 动态分配内存并初始化为零,用于数组、结构体等需要初始化为零的数据
注意事项:
- 分配的内存会被初始化为零
- 总分配大小为
nmemb * size字节 - 应检查返回值是否为 NULL,以处理内存分配失败的情况
- 分配的内存使用完毕后必须使用
free()释放
示例:
1 |
|
5.3 realloc() - 重新分配内存
函数原型:
1 | void *realloc(void *ptr, size_t size); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
ptr | void * | 是 | 无 | 指向之前分配的内存块的指针,如果为 NULL,则等同于 malloc(size) |
size | size_t | 是 | 无 | 新的内存大小(以字节为单位) |
返回值: 指向重新分配的内存块的指针,失败返回 NULL(原内存块保持不变)
使用场景: 调整已分配内存的大小,用于动态增长或缩小数据结构
取值范围限制:
ptr参数的要求:- 必须是之前通过
malloc(),calloc(),realloc()分配的内存块的指针 - 或者是 NULL
- 不能是已释放的内存块的指针
- 不能是栈内存或全局内存的指针
- 必须是之前通过
size参数的取值范围:0 到 SIZE_MAX- SIZE_MAX 是系统定义的 size_t 类型的最大值
注意事项:
- 如果
ptr为 NULL,等同于malloc(size) - 如果
size为 0,等同于free(ptr) - 新内存块的内容会被复制到新位置,直到新旧大小的较小值
- 应检查返回值是否为 NULL,以处理内存分配失败的情况
- 分配的内存使用完毕后必须使用
free()释放 - 内存重分配的行为:
- 如果新大小小于等于旧大小,可能在原地调整大小
- 如果新大小大于旧大小,可能需要分配新的内存块并复制数据
- 复制数据的时间复杂度为 O(n),其中 n 是新旧大小的较小值
- 内存分配失败的处理:
- 如果 realloc() 失败,原内存块保持不变
- 因此在调用 realloc() 时,应使用临时指针接收返回值,避免内存泄漏
- 安全考虑:
- 避免使用已释放的指针
- 避免双重释放
- 确保新大小足够容纳所有数据
- 性能考虑:
- 频繁的 realloc() 可能导致内存碎片
- 对于需要频繁增长的数据结构,考虑使用指数增长策略
示例:
1 |
|
5.4 free() - 释放内存
函数原型:
1 | void free(void *ptr); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
ptr | void * | 是 | 无 | 指向要释放的内存块的指针,如果为 NULL,则无操作 |
返回值: 无
使用场景: 释放之前通过 malloc(), calloc(), realloc() 分配的内存,避免内存泄漏
注意事项:
- 只能释放通过动态内存分配函数分配的内存
- 释放后,指针变为悬空指针,不应再使用
- 不要重复释放同一块内存,否则会导致未定义行为
- 如果
ptr为 NULL,函数无操作
示例:
1 |
|
5.5 exit() - 终止程序
函数原型:
1 | void exit(int status); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
status | int | 是 | 无 | 程序退出状态码,0 表示正常退出,非 0 表示异常退出 |
返回值: 无
使用场景: 立即终止程序执行,用于错误处理、正常程序结束等
注意事项:
- 会执行所有已注册的
atexit()函数 - 会刷新所有打开的文件流
- 会关闭所有打开的文件
- 不会执行
return语句后的代码
示例:
1 |
|
5.6 abort() - 异常终止程序
函数原型:
1 | void abort(void); |
参数说明: 无
返回值: 无
使用场景: 异常终止程序,用于严重错误、不可恢复的情况
注意事项:
- 不会执行
atexit()注册的函数 - 会产生 SIGABRT 信号
- 可能会生成核心转储(core dump)
- 通常用于调试目的
示例:
1 |
|
5.7 system() - 执行系统命令
函数原型:
1 | int system(const char *command); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
command | const char * | 是 | 无 | 要执行的系统命令字符串,如果为 NULL,则检查系统是否支持命令处理器 |
返回值: 命令执行的返回状态,具体值取决于系统
使用场景: 执行系统命令,用于调用外部程序、系统操作等
注意事项:
- 存在安全风险,特别是当命令字符串包含用户输入时,可能导致命令注入攻击
- 执行效率较低,应避免频繁调用
- 依赖于系统环境,可能在不同平台上有差异
- 应检查返回值,以处理命令执行失败的情况
示例:
1 |
|
5.8 atoi() - 字符串转换为整数
函数原型:
1 | int atoi(const char *nptr); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
nptr | const char * | 是 | 无 | 指向要转换的字符串的指针 |
返回值: 转换后的整数
使用场景: 将字符串转换为整数,用于命令行参数处理、配置文件解析等
注意事项:
- 跳过字符串开头的空白字符
- 从第一个非空白字符开始转换,直到遇到非数字字符
- 如果字符串不能转换为整数,返回 0
- 不检查溢出,可能导致未定义行为
示例:
1 |
|
5.9 atol() - 字符串转换为长整数
函数原型:
1 | long atol(const char *nptr); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
nptr | const char * | 是 | 无 | 指向要转换的字符串的指针 |
返回值: 转换后的长整数
使用场景: 将字符串转换为长整数,用于处理较大的整数值
注意事项:
- 跳过字符串开头的空白字符
- 从第一个非空白字符开始转换,直到遇到非数字字符
- 如果字符串不能转换为长整数,返回 0
- 不检查溢出,可能导致未定义行为
示例:
1 |
|
5.10 atof() - 字符串转换为双精度浮点数
函数原型:
1 | double atof(const char *nptr); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
nptr | const char * | 是 | 无 | 指向要转换的字符串的指针 |
返回值: 转换后的双精度浮点数
使用场景: 将字符串转换为浮点数,用于处理小数、科学计数法等
注意事项:
- 跳过字符串开头的空白字符
- 从第一个非空白字符开始转换,直到遇到非数字、小数点或指数符号
- 如果字符串不能转换为浮点数,返回 0.0
- 不检查溢出,可能导致未定义行为
示例:
1 |
|
使用示例:
1 |
|
6. 进程控制库 (<unistd.h>)(Unix/Linux)
主要功能: 进程控制、系统调用
常用函数详细说明:
6.1 fork() - 创建子进程
函数原型:
1 | pid_t fork(void); |
参数说明: 无
返回值:
- 成功:在父进程中返回子进程的 PID,在子进程中返回 0
- 失败:返回 -1
使用场景: 创建新的子进程,用于并行处理任务、执行不同的程序等
取值范围限制:
- 子进程的 PID 是一个非负整数,范围通常在 1 到 PID_MAX 之间
- PID_MAX 的值取决于系统实现,通常为 32768 或更高
注意事项:
- 子进程是父进程的副本,包括内存空间、文件描述符等
- 父子进程的执行顺序是不确定的,取决于操作系统的调度
- 子进程中应调用
exec系列函数执行新程序,或在完成任务后退出 - 父进程应调用
wait系列函数等待子进程结束,避免产生僵尸进程 fork()失败的常见原因:- 系统进程数达到上限
- 内存不足
- 非特权进程尝试创建进程数超过 RLIMIT_NPROC 限制
- 子进程继承父进程的:
- 内存空间(但有写时复制机制)
- 文件描述符
- 信号处理设置(除了 SIGCHLD 的处理方式)
- 当前工作目录
- 环境变量
- 打开的文件状态
- 子进程不继承父进程的:
- PID
- 父进程 ID
- 资源使用统计
- 未处理的信号
- 定时器设置
- 文件锁
示例:
1 | pid_t pid = fork(); |
6.2 execvp() - 执行新程序
函数原型:
1 | int execvp(const char *file, char *const argv[]); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
file | const char * | 是 | 无 | 要执行的程序文件名,如果包含路径则直接使用,否则在 PATH 环境变量中查找 |
argv | char *const [] | 是 | 无 | 命令行参数数组,以 NULL 结尾 |
返回值: 成功不返回,失败返回 -1
使用场景: 在当前进程中执行新的程序,替换当前进程的代码和数据
注意事项:
- 如果执行成功,函数不会返回,当前进程的代码会被新程序替换
- 如果执行失败,函数返回 -1,应检查错误原因
argv数组必须以 NULL 结尾- 新程序会继承当前进程的文件描述符、信号处理等
示例:
1 | char *argv[] = {"ls", "-l", NULL}; |
6.3 wait() - 等待子进程结束
函数原型:
1 | pid_t wait(int *status); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
status | int * | 否 | NULL | 指向存储子进程退出状态的整数的指针,如果为 NULL,则不存储退出状态 |
返回值:
- 成功:返回结束的子进程的 PID
- 失败:返回 -1
使用场景: 父进程等待子进程结束,避免产生僵尸进程
取值范围限制:
status指针指向的整数用于存储子进程的退出状态,其值的解释由系统定义- 退出状态的正常范围是 0 到 255
注意事项:
- 函数会阻塞,直到有子进程结束
- 如果
status不为 NULL,可以使用以下宏来解析退出状态:WIFEXITED(status):如果子进程正常退出,返回非零值WEXITSTATUS(status):获取子进程的退出状态码(0-255)WIFSIGNALED(status):如果子进程被信号终止,返回非零值WTERMSIG(status):获取终止子进程的信号编号WIFSTOPPED(status):如果子进程被暂停,返回非零值(仅用于 waitpid())WSTOPSIG(status):获取暂停子进程的信号编号(仅用于 waitpid())
- 如果没有子进程,函数会失败并设置 errno 为 ECHILD
- 如果函数被信号中断,会返回 -1 并设置 errno 为 EINTR
- 调用
wait()会清理僵尸进程,释放其资源 - 对于多个子进程,
wait()会按任意顺序等待它们结束
示例:
1 | int status; |
6.4 waitpid() - 等待指定子进程结束
函数原型:
1 | pid_t waitpid(pid_t pid, int *status, int options); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
pid | pid_t | 是 | 无 | 要等待的子进程 PID,-1 表示等待任意子进程 |
status | int * | 否 | NULL | 指向存储子进程退出状态的整数的指针 |
options | int | 是 | 0 | 控制等待行为的选项,如 WNOHANG(非阻塞)、WUNTRACED(等待停止的进程)等 |
返回值:
- 成功:返回结束的子进程的 PID
- 失败:返回 -1
- 如果使用 WNOHANG 且没有子进程结束,返回 0
使用场景: 等待指定的子进程结束,或在非阻塞模式下检查子进程状态
注意事项:
pid参数可以指定具体的子进程 PID,或使用特殊值:pid > 0:等待 PID 为 pid 的子进程pid == -1:等待任意子进程,等同于wait()pid == 0:等待同组的任意子进程pid < -1:等待组 ID 为 -pid 的任意子进程
options参数可以是多个选项的按位或
示例:
1 | int status; |
6.5 getpid() - 获取进程 ID
函数原型:
1 | pid_t getpid(void); |
参数说明: 无
返回值: 当前进程的 PID
使用场景: 获取当前进程的唯一标识符,用于日志记录、进程间通信等
注意事项: 无特殊注意事项
示例:
1 | printf("当前进程 ID: %d\n", getpid()); |
6.6 getppid() - 获取父进程 ID
函数原型:
1 | pid_t getppid(void); |
参数说明: 无
返回值: 当前进程的父进程 PID
使用场景: 获取父进程的标识符,用于进程间通信、监控等
注意事项:
- 如果父进程已经结束,当前进程会被 init 进程(PID 为 1)收养
示例:
1 | printf("父进程 ID: %d\n", getppid()); |
6.7 sleep() - 进程休眠
函数原型:
1 | unsigned int sleep(unsigned int seconds); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
seconds | unsigned int | 是 | 无 | 要休眠的秒数 |
返回值: 实际休眠的秒数,如果被信号中断,返回剩余的秒数
使用场景: 让进程暂停执行一段时间,用于定时任务、速率限制等
注意事项:
- 函数可能会被信号中断,返回剩余的休眠时间
- 实际休眠时间可能会比指定的时间长,取决于系统的调度
示例:
1 | printf("开始休眠 5 秒...\n"); |
6.8 getcwd() - 获取当前工作目录
函数原型:
1 | char *getcwd(char *buf, size_t size); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
buf | char * | 是 | 无 | 指向存储当前工作目录路径的缓冲区的指针 |
size | size_t | 是 | 无 | 缓冲区的大小(以字节为单位) |
返回值:
- 成功:返回
buf,指向存储当前工作目录路径的缓冲区 - 失败:返回 NULL
使用场景: 获取当前进程的工作目录路径,用于文件操作、日志记录等
注意事项:
- 缓冲区必须足够大,能够容纳当前工作目录的路径和 null 终止符
- 如果缓冲区太小,函数会失败并设置 errno 为 ERANGE
- 可以使用
NULL作为buf,此时函数会自动分配内存,但需要在使用后调用free()释放
示例:
1 | char buf[1024]; |
6.9 chdir() - 更改工作目录
函数原型:
1 | int chdir(const char *path); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
path | const char * | 是 | 无 | 要更改为的新工作目录的路径 |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 更改当前进程的工作目录,用于文件操作、访问不同目录下的资源等
注意事项:
path可以是绝对路径或相对路径- 如果指定的目录不存在或没有权限,函数会失败
- 工作目录的更改只影响当前进程和其子进程,不会影响父进程
示例:
1 | if (chdir("/home/user") == 0) { |
6.10 pipe() - 创建管道
函数原型:
1 | int pipe(int pipefd[2]); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
pipefd | int[2] | 是 | 无 | 存储管道文件描述符的数组,pipefd[0] 是读端,pipefd[1] 是写端 |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 创建管道,用于进程间通信,特别是父子进程之间
取值范围限制:
pipefd数组必须至少有 2 个元素- 管道的容量通常在 4KB 到 64KB 之间,具体取决于系统实现
注意事项:
- 管道是单向的,数据从写端流向读端
- 管道的容量有限,当管道满时,写操作会阻塞
- 当所有写端关闭时,读操作会返回 EOF
- 应在不需要的文件描述符时关闭它们,避免资源泄漏
- 管道的关闭顺序很重要:
- 父进程在写入数据后应关闭写端
- 子进程在读取数据后应关闭读端
- 否则可能导致读操作阻塞
- 管道只能用于具有亲缘关系的进程之间通信(如父子进程)
- 对于无亲缘关系的进程间通信,应使用命名管道(FIFO)或套接字
- 管道的错误处理:
- 当管道的读端被关闭后,向写端写入数据会产生 SIGPIPE 信号
- 如果忽略该信号,写操作会返回 -1 并设置 errno 为 EPIPE
- 管道的性能考虑:
- 管道是基于内核缓冲区的,数据在内核中复制,性能较好
- 但对于大量数据传输,可能会受到缓冲区大小的限制
示例:
1 | int pipefd[2]; |
使用示例:
1 |
|
7. 网络编程库 (<sys/socket.h>)(Unix/Linux)
主要功能: 网络通信
常用函数详细说明:
7.1 socket() - 创建套接字
函数原型:
1 | int socket(int domain, int type, int protocol); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
domain | int | 是 | 无 | 套接字域,指定通信协议族,如 AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地套接字)等 |
type | int | 是 | 无 | 套接字类型,如 SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW(原始套接字)等 |
protocol | int | 是 | 0 | 协议类型,通常为 0,表示使用默认协议 |
返回值:
- 成功:返回套接字文件描述符
- 失败:返回 -1
使用场景: 创建网络通信的端点,用于后续的网络操作
取值范围限制:
domain参数的常见值:- AF_INET:IPv4 协议族
- AF_INET6:IPv6 协议族
- AF_UNIX(或 AF_LOCAL):本地套接字
- AF_PACKET:链路层套接字
type参数的常见值:- SOCK_STREAM:面向连接的可靠流(TCP)
- SOCK_DGRAM:无连接的数据包(UDP)
- SOCK_RAW:原始套接字
- SOCK_SEQPACKET:面向连接的顺序数据包
protocol参数通常为 0,表示使用默认协议:- 对于 AF_INET + SOCK_STREAM,默认协议是 IPPROTO_TCP
- 对于 AF_INET + SOCK_DGRAM,默认协议是 IPPROTO_UDP
注意事项:
domain参数决定了套接字的地址格式,如 AF_INET 使用 IPv4 地址格式type参数决定了套接字的通信方式,如 SOCK_STREAM 提供可靠的、面向连接的通信protocol参数通常为 0,让系统根据domain和type选择默认协议socket()失败的常见原因:- 不支持的
domain或type - 内存不足
- 权限不足(如创建原始套接字需要特权)
- 不支持的
- 套接字文件描述符是一个非负整数,应在使用完毕后调用
close()关闭 - 对于原始套接字,需要 root 权限或 CAP_NET_RAW 能力
示例:
1 | // 创建 TCP 套接字 |
7.2 bind() - 绑定地址
函数原型:
1 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
sockfd | int | 是 | 无 | 套接字文件描述符 |
addr | const struct sockaddr * | 是 | 无 | 指向包含要绑定的地址信息的结构体的指针,需要根据套接字域进行类型转换 |
addrlen | socklen_t | 是 | 无 | 地址结构体的大小(以字节为单位) |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 将套接字绑定到特定的 IP 地址和端口,通常用于服务器端
注意事项:
- 对于服务器端,必须调用
bind()来指定监听的地址和端口 - 对于客户端,通常不需要调用
bind(),系统会自动分配地址和端口 - 绑定的地址必须是主机上有效的地址,对于 IPv4,可以使用 INADDR_ANY 表示绑定到所有可用接口
- 端口号必须是未被占用的,且非特权端口(大于 1023)除非以 root 权限运行
示例:
1 | struct sockaddr_in addr; |
7.3 listen() - 监听连接
函数原型:
1 | int listen(int sockfd, int backlog); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
sockfd | int | 是 | 无 | 套接字文件描述符 |
backlog | int | 是 | 无 | 监听队列的最大长度,指定可以排队的连接请求数 |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 将套接字设置为监听模式,准备接受连接请求,仅用于服务器端的 TCP 套接字
注意事项:
- 仅适用于 SOCK_STREAM 类型的套接字(TCP)
backlog参数指定了连接请求队列的最大长度,超过此长度的连接请求会被拒绝- 实际的队列长度可能会被操作系统限制,不一定等于指定的值
- 调用
listen()后,套接字变为被动套接字,只能用于接受连接,不能用于发送或接收数据
示例:
1 | if (listen(sockfd, 5) == -1) { // 最大允许 5 个连接请求排队 |
7.4 accept() - 接受连接
函数原型:
1 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
sockfd | int | 是 | 无 | 监听套接字的文件描述符 |
addr | struct sockaddr * | 否 | NULL | 指向存储客户端地址信息的结构体的指针,如果为 NULL,则不存储客户端地址 |
addrlen | socklen_t * | 否 | NULL | 指向存储地址结构体大小的变量的指针,传入时为 addr 结构体的大小,返回时为实际的地址大小 |
返回值:
- 成功:返回新的套接字文件描述符,用于与客户端通信
- 失败:返回 -1
使用场景: 接受客户端的连接请求,创建新的套接字用于与客户端通信,仅用于服务器端的 TCP 套接字
注意事项:
- 函数会阻塞,直到有连接请求到达
- 返回的新套接字与客户端一一对应,用于后续的通信
- 原监听套接字仍然存在,继续用于接受其他连接请求
- 如果
addr和addrlen不为 NULL,函数会填充客户端的地址信息
示例:
1 | struct sockaddr_in client_addr; |
7.5 connect() - 连接到服务器
函数原型:
1 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
sockfd | int | 是 | 无 | 套接字文件描述符 |
addr | const struct sockaddr * | 是 | 无 | 指向包含服务器地址信息的结构体的指针,需要根据套接字域进行类型转换 |
addrlen | socklen_t | 是 | 无 | 地址结构体的大小(以字节为单位) |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 客户端连接到服务器,建立 TCP 连接
注意事项:
- 对于 TCP 套接字,函数会阻塞直到连接建立或失败
- 对于 UDP 套接字,函数不会建立真正的连接,只是设置默认的目标地址
- 地址结构体必须包含有效的服务器 IP 地址和端口号
- 端口号和 IP 地址需要转换为网络字节序
示例:
1 | struct sockaddr_in server_addr; |
7.6 send() - 发送数据
函数原型:
1 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
sockfd | int | 是 | 无 | 套接字文件描述符 |
buf | const void * | 是 | 无 | 指向要发送的数据的指针 |
len | size_t | 是 | 无 | 要发送的数据长度(以字节为单位) |
flags | int | 是 | 0 | 控制发送行为的标志,通常为 0 |
返回值:
- 成功:返回实际发送的字节数
- 失败:返回 -1
使用场景: 通过套接字发送数据,适用于 TCP 和 UDP 套接字
注意事项:
- 对于 TCP 套接字,
send()可能只发送部分数据,需要循环发送直到所有数据都被发送 - 对于 UDP 套接字,
send()要么发送整个数据报,要么失败 flags参数通常为 0,表示正常发送- 常见的 flags 值:
- MSG_OOB:发送带外数据(仅 TCP)
- MSG_DONTROUTE:不使用路由表查找
- MSG_NOSIGNAL:发送失败时不产生 SIGPIPE 信号
示例:
1 | const char *message = "Hello, Server!"; |
7.7 recv() - 接收数据
函数原型:
1 | ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
sockfd | int | 是 | 无 | 套接字文件描述符 |
buf | void * | 是 | 无 | 指向存储接收数据的缓冲区的指针 |
len | size_t | 是 | 无 | 缓冲区的大小(以字节为单位) |
flags | int | 是 | 0 | 控制接收行为的标志,通常为 0 |
返回值:
- 成功:返回实际接收的字节数
- 失败:返回 -1
- 连接关闭:返回 0(仅 TCP)
使用场景: 通过套接字接收数据,适用于 TCP 和 UDP 套接字
注意事项:
- 对于 TCP 套接字,
recv()可能只接收部分数据,需要循环接收直到获取完整数据 - 对于 UDP 套接字,
recv()接收整个数据报,如果数据报大于缓冲区,则会被截断 - 函数会阻塞,直到有数据到达
- 常见的 flags 值:
- MSG_OOB:接收带外数据(仅 TCP)
- MSG_PEEK:查看数据但不从缓冲区移除
- MSG_DONTWAIT:非阻塞模式,没有数据时立即返回
示例:
1 | char buffer[1024]; |
7.8 close() - 关闭套接字
函数原型:
1 | int close(int fd); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
fd | int | 是 | 无 | 要关闭的文件描述符,包括套接字文件描述符 |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 关闭套接字,释放相关资源
注意事项:
- 关闭套接字后,不能再使用该文件描述符进行通信
- 对于 TCP 套接字,
close()会发送 FIN 报文,启动四次挥手过程 - 对于 UDP 套接字,
close()只是释放套接字资源 - 应在通信结束后及时关闭套接字,避免资源泄漏
示例:
1 | close(client_sockfd); // 关闭与客户端的连接 |
7.9 setsockopt() - 设置套接字选项
函数原型:
1 | int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
sockfd | int | 是 | 无 | 套接字文件描述符 |
level | int | 是 | 无 | 选项所在的协议层,如 SOL_SOCKET(通用套接字选项)、IPPROTO_TCP(TCP 选项)等 |
optname | int | 是 | 无 | 要设置的选项名称,如 SO_REUSEADDR、SO_REUSEPORT 等 |
optval | const void * | 是 | 无 | 指向包含选项值的缓冲区的指针 |
optlen | socklen_t | 是 | 无 | 选项值的大小(以字节为单位) |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 设置套接字的各种选项,用于配置套接字的行为,如地址重用、端口重用、缓冲区大小等
取值范围限制:
level参数的常见值:- SOL_SOCKET:通用套接字选项
- IPPROTO_TCP:TCP 协议选项
- IPPROTO_IP:IPv4 协议选项
- IPPROTO_IPV6:IPv6 协议选项
optname参数的取值取决于level参数
注意事项:
- 不同的套接字选项需要不同类型的
optval参数,应根据选项类型进行正确的类型转换 - 对于布尔类型的选项,
optval通常是指向整数的指针,非零值表示启用,零表示禁用 - 对于整数类型的选项,
optval是指向相应整数类型的指针 - 对于时间类型的选项,
optval通常是指向struct timeval结构的指针 - 常见的套接字选项:
- SO_REUSEADDR:允许重用本地地址
- SO_REUSEPORT:允许重用本地端口
- SO_RCVBUF:设置接收缓冲区大小
- SO_SNDBUF:设置发送缓冲区大小
- SO_TIMEOUT:设置接收超时时间
- TCP_NODELAY:禁用 Nagle 算法,减少小数据包的延迟
- 套接字选项的设置通常在
bind()之前进行,以确保选项生效 - 某些选项只能在特定类型的套接字上设置,如 TCP_NODELAY 只能在 SOCK_STREAM 类型的套接字上设置
示例:
1 | // 设置套接字选项,允许重用地址和端口 |
使用示例:
1 |
|
8. 信号处理库 (<signal.h>)
主要功能: 信号处理
常用信号说明:
| 信号 | 编号 | 描述 | 默认处理方式 |
|---|---|---|---|
SIGINT | 2 | 中断信号(Ctrl+C) | 终止进程 |
SIGTERM | 15 | 终止信号 | 终止进程 |
SIGKILL | 9 | 杀死信号 | 终止进程(不可捕获) |
SIGSTOP | 19 | 停止信号 | 暂停进程(不可捕获) |
SIGCONT | 18 | 继续信号 | 继续进程 |
SIGSEGV | 11 | 段错误信号 | 终止进程并产生核心转储 |
SIGFPE | 8 | 浮点异常信号 | 终止进程并产生核心转储 |
SIGABRT | 6 | 中止信号 | 终止进程并产生核心转储 |
SIGCHLD | 17 | 子进程状态改变信号 | 忽略 |
SIGALRM | 14 | 闹钟信号 | 终止进程 |
SIGUSR1 | 10 | 用户自定义信号 1 | 终止进程 |
SIGUSR2 | 12 | 用户自定义信号 2 | 终止进程 |
常用函数详细说明:
8.1 signal() - 设置信号处理函数
函数原型:
1 | typedef void (*sighandler_t)(int); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
signum | int | 是 | 无 | 要处理的信号编号,如 SIGINT(中断信号)、SIGTERM(终止信号)等 |
handler | sighandler_t | 是 | 无 | 信号处理函数的指针,或以下特殊值: - SIG_DFL:使用默认处理方式 - SIG_IGN:忽略该信号 |
返回值:
- 成功:返回之前的信号处理函数指针
- 失败:返回 SIG_ERR
使用场景: 设置信号的处理方式,用于捕获和处理各种信号,如用户中断、程序错误等
取值范围限制:
signum参数的取值范围:1 到 NSIG-1- NSIG 是系统定义的最大信号编号,通常为 32 或 64
- 某些信号(如 SIGKILL 和 SIGSTOP)不能被捕获或忽略
注意事项:
signal()函数的行为在不同系统上可能有所不同,建议使用更可移植的sigaction()函数- 信号处理函数应该保持简短,避免调用可能不安全的函数(如 printf())
- 某些信号(如 SIGKILL 和 SIGSTOP)不能被捕获或忽略
- 在信号处理函数中,应尽量只做简单的操作,如设置标志位
- 信号处理函数的安全性:
- 应使用 volatile sig_atomic_t 类型的变量进行通信
- 避免调用不可重入函数(如 malloc()、free()、printf() 等)
- 避免持有锁
- 避免进行长时间的计算
- 信号处理函数的返回值:信号处理函数没有返回值,它的作用是处理信号并返回
- 信号的重启:某些系统调用可能会被信号中断,需要考虑是否使用 SA_RESTART 标志
- 信号的嵌套:默认情况下,信号处理期间会阻塞相同的信号,但可能会接收其他信号
- 推荐使用
sigaction()函数:它提供了更详细的控制选项,行为更加可预测
示例:
1 | // 信号处理函数 |
8.2 raise() - 发送信号
函数原型:
1 | int raise(int signum); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
signum | int | 是 | 无 | 要发送的信号编号 |
返回值:
- 成功:返回 0
- 失败:返回非 0
使用场景: 向当前进程发送信号,用于测试信号处理函数、触发进程自我终止等
注意事项:
raise(signum)等价于kill(getpid(), signum)- 如果信号被忽略,函数仍然返回成功
- 如果信号导致进程终止,函数不会返回
示例:
1 |
|
8.3 kill() - 向进程发送信号
函数原型:
1 | int kill(pid_t pid, int signum); |
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
pid | pid_t | 是 | 无 | 目标进程的 PID,或以下特殊值: - pid > 0:发送信号给指定 PID 的进程- pid == 0:发送信号给与当前进程同组的所有进程- pid == -1:发送信号给所有有权限发送的进程(除了 init 进程)- pid < -1:发送信号给组 ID 为 -pid 的所有进程 |
signum | int | 是 | 无 | 要发送的信号编号 |
返回值:
- 成功:返回 0
- 失败:返回 -1
使用场景: 向指定进程或进程组发送信号,用于进程间通信、控制其他进程等
取值范围限制:
pid参数的取值范围:- 正数:有效的进程 ID
- 0:当前进程组
- -1:所有有权限的进程
- 负数(除 -1 外):进程组 ID
signum参数的取值范围:1 到 NSIG-1- NSIG 是系统定义的最大信号编号,通常为 32 或 64
注意事项:
- 需要有向目标进程发送信号的权限,通常只有进程的所有者或 root 用户可以发送信号
- 权限检查:
- 普通用户可以向自己的进程发送任何信号
- 普通用户可以向其他用户的进程发送 SIGCONT 信号
- 普通用户只能向其他用户的进程发送终止信号,如果目标进程设置了相应的权限
- root 用户可以向任何进程发送任何信号(除了不可捕获的信号)
- 某些信号(如 SIGKILL)不能被捕获或忽略,会强制终止进程
- 发送信号 0 可以用于检查进程是否存在,而不实际发送信号
kill()失败的常见原因:- 目标进程不存在(errno = ESRCH)
- 没有权限发送信号(errno = EPERM)
- 信号编号无效(errno = EINVAL)
- 对于进程组操作:
- 当
pid == 0时,信号会发送给当前进程组的所有进程,包括调用进程本身 - 当
pid < -1时,信号会发送给组 ID 为 -pid 的所有进程
- 当
- 信号的传递是异步的,
kill()成功返回只表示信号已被发送,不表示信号已被处理
示例:
1 |
|
使用示例:
1 |
|
系统函数库的使用最佳实践
- 包含正确的头文件 - 每个系统函数库都有对应的头文件,必须正确包含
- 检查函数返回值 - 大多数系统函数会返回错误码,必须检查
- 了解平台差异 - 某些系统函数在不同平台上可能有差异
- 使用
perror()或strerror()- 用于打印系统错误信息 - 资源管理 - 确保释放所有分配的资源(内存、文件描述符等)
- 错误处理 - 实现健壮的错误处理机制
- 安全编程 - 避免缓冲区溢出、使用安全的函数变体
- 性能考虑 - 对于频繁调用的函数,考虑缓存结果或使用更高效的实现
示例:综合使用多个系统函数库
1 |
|



