C标准库¶
C Reference | cppreference.com
C Standard Library headers | cppreference
Null-terminated byte strings | cppreference
C 标准把语言本身与标准库分开规定: 语言管语法与语义, 标准库提供可移植的常用能力 (字符串, I/O, 堆分配, 数值转换等). 在 Linux 上这些函数大多由 libc (如 glibc) 实现, 少数 (如数学函数) 在 libm 中; 链接时常自动带上 libc, 使用 sin / log 等则往往还要 -lm.
查细节应阅读 Man Page (man 3 strcpy) 或 cppreference. 读接口原型与契约的方法见函数接口.
Abstract
-
标准库 = 头文件 (类型 / 宏 / 函数声明) + 库文件 (函数实现).
#include只引入声明, 真正代码在链接时从共享库或静态库并入. -
命名习惯:
str*处理以'\0'结尾的 C 字符串;mem*按字节块操作, 不关心'\0'. 二者都在<string.h>. -
出错时许多函数返回「哨兵值」(
NULL/EOF/-1) 并设置全局errno. 应在调用失败后立刻读errno或调用perror/strerror, 中间不要夹其它可能改写errno的库调用. -
Man Page 的 CONFORMING TO 区分 C 标准与 POSIX.
strcpy属 C;strdup/strcasecmp/strtok_r等属 POSIX, 在严格 ISO C 环境未必可用. -
堆分配 (
malloc/free等) 与字符串拷贝的接口契约已在函数接口展开; 堆布局与常见 bug 见 C Memory Management.
标准库的组成¶
头文件放哪里取决于Linux发行版与编译器. 常见情况是: stdio.h / stdlib.h / string.h / errno.h 等在 /usr/include; 与编译器强绑定的如 stddef.h / stdarg.h 可能在GCC自带的 include 目录下. C99 起标准头文件有二十余个, 最常用的几个头文件及其典型内容大致如下:
| 用途 | 头文件 | 典型内容 |
|---|---|---|
| 文件与流式输入输出 | <stdio.h> | FILE *, printf / scanf, fopen / fread, stdin / stdout / stderr |
| 通用工具(堆内存, 串数值转换, 随机数, 进程退出等) | <stdlib.h> | malloc / free, atoi / strtol, rand / srand, exit |
| 空终止字节串与原始内存块 | <string.h> | strlen / strcpy / strcmp, memcpy / memset, strerror |
| 跨头文件共用的基础类型与宏 | <stddef.h> | size_t, NULL, ptrdiff_t, offsetof |
| 库 / 系统调用失败时的错误码 | <errno.h> | errno 与各类错误码宏 (ENOENT 等) |
访问 ... 可变参数列表 | <stdarg.h> | va_list / va_start / va_arg / va_end (可变参数) |
调试期条件断言 (可被 NDEBUG 关掉) | <assert.h> | assert 宏 |
| 常用实数数学运算 | <math.h> | sin / log 等 (常需链接 libm) |
| 日历时间, 时钟与格式化 | <time.h> | time, localtime, strftime 等 |
字符串与内存块¶
符号处理程序最终都落在「长度 / 拷贝 / 连接 / 比较 / 搜索 / 分割」几类操作上. 下面按用途归类; 原型与缓冲区责任见函数接口 · strcpy / strncpy.
初始化与长度¶
#include <string.h>
void *memset(void *s, int c, size_t n); /* 把 n 字节都写成 (unsigned char)c, 常用于清零 */
size_t strlen(const char *s); /* 到 '\0' 为止的字符数, 不含 '\0' */
局部数组与 malloc 得到的块初值不确定, 需要确定性内容时再 memset. strlen 要求 s 确实以 '\0' 结尾; 若缓冲区没有终止符就会越界读, 触发未定义行为.
拷贝: str* 与 mem*¶
| 函数 | 停在哪里 | 重叠区 |
|---|---|---|
strcpy / strncpy | 遇 '\0' (或 strncpy 写满 n) | 不允许重叠 (restrict) |
memcpy | 固定拷 n 字节 | 不允许重叠 |
memmove | 固定拷 n 字节 | 允许重叠 |
memcpy 与 memmove 都不把参数当 C 字符串看待, 参数类型是 void *. C99 在 memcpy 上加了 restrict, 等于约定调用者保证区间不重叠, 实现与编译器才可按「无别名」做优化; 可能重叠时用 memmove.
POSIX 另有 strdup: 内部 malloc 再拷贝整串, 返回新指针, 用完必须 free. 归属与寿命规则同堆分配并返回.
连接, 比较, 搜索¶
#include <string.h>
char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n); /* 最多再接 n 个字符, 且总会写 '\0' */
int strcmp(const char *s1, const char *s2); /* <0 / 0 / >0 */
int strncmp(const char *s1, const char *s2, size_t n);
char *strchr(const char *s, int c); /* 第一次出现; 找不到返回 NULL */
char *strrchr(const char *s, int c); /* 从右向左 */
char *strstr(const char *haystack, const char *needle);
strncat 的 n 是「最多从 src 取几个字符」, 不是 dest 的总容量; dest 至少要有 strlen(dest) + n + 1 字节. 这一点与 strncpy 的 n 含义不同, 读文档时不要混.
POSIX 的 strcasecmp / strncasecmp 在比较时忽略大小写, 不属于 ISO C.
分割: strtok 与可重入¶
#include <string.h>
char *strtok(char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr); /* POSIX */
strtok 会就地把分隔符改成 '\0', 并用函数内部的静态指针记住下次从哪继续. 因此:
-
不能用于字符串字面量或其它只读区.
-
分割后原串被破坏, 分隔符不可恢复.
-
静态状态使它不可重入, 也不适合多线程共用; 需要可重入时用
strtok_r, 由调用者提供saveptr(典型的 Value-result 指针, 见函数接口).
第一次调用传入待分割串, 后续传入 NULL 表示「接着上次」:
#include <string.h>
char str[] = "root:x:0:0:root:/root:/bin/bash";
char *tok = strtok(str, ":");
while (tok != NULL) {
/* 使用 tok ... */
tok = strtok(NULL, ":");
}
应用示例¶
标准 I/O¶
FILE * 与打开 / 关闭¶
对文件或设备做 I/O 前用 fopen 打开, 得到不透明指针 FILE * (句柄): 调用者把它在库函数之间传来传去, 不应依赖 FILE 内部成员. 用完后 fclose; 长跑进程若只开不关会耗尽系统资源.
FILE *fopen(const char *path, const char *mode);
int fclose(FILE *fp); /* 成功返回 0; 失败返回 EOF */
常用 mode (UNIX 上文本 / 二进制通常无区别, b 可省略):
| mode | 含义 |
|---|---|
"r" | 只读, 文件必须已存在 |
"w" | 只写; 不存在则创建, 已存在则截断为 0 |
"a" | 追加写; 不存在则创建 |
"r+" / "w+" / "a+" | 在对应基础上允许读写 |
失败时 fopen 返回 NULL 并设置 errno. 错误信息应打到 stderr:
FILE *fp = fopen(path, "r");
if (fp == NULL) {
perror(path); /* 或 fprintf(stderr, ...) */
exit(EXIT_FAILURE);
}
stdin / stdout / stderr¶
进程启动时库会打开三个流: 标准输入 stdin, 标准输出 stdout, 标准错误 stderr. printf 写 stdout, scanf 读 stdin. 正常结果与诊断信息分开, 是为了重定向时互不干扰 (例如只把 stdout 接到文件, 错误仍留在终端).
errno 与 perror¶
#include <errno.h>
#include <stdio.h>
#include <string.h>
void perror(const char *s); /* 打印 s, 再打印 ": " 与 errno 对应说明, 输出到 stderr */
char *strerror(int errnum); /* 返回指向静态说明串的指针 */
errno 是线程相关的错误码存储 (历史上是全局 int). 许多失败路径会改写它; 成功路径是否清零因函数而异, 不能靠「调用前 errno 非 0」判断失败, 必须先看函数返回值. strerror 返回的指针指向静态缓冲, 多次调用会互相覆盖, 规则同静态缓冲区.
读写粒度¶
在已打开的流上, 可按粒度选择接口 (均需传入 FILE *, 对标准流则用 stdin / stdout):
| 粒度 | 读 | 写 |
|---|---|---|
| 字节 | fgetc / getc | fputc / putc |
| 字符串 (遇换行或满缓冲) | fgets | fputs |
| 记录 (二进制块) | fread | fwrite |
| 格式化 | fscanf / scanf / sscanf | fprintf / printf / sprintf / snprintf |
定位用 fseek / ftell / rewind. 读到流末尾时, 面向字符的接口常返回 EOF (值为 -1); 应用 feof / ferror 区分「真结束」与「出错」.
拼串优先 snprintf(buf, sizeof buf, ...), 避免 sprintf 不知缓冲区长度而溢出. 可变参数与格式串类型必须一致.
打印已有字符串时写 printf("%s", s), 不要写 printf(s): 若 s 含 %, 会被当成转换说明并从栈上误取参数.
I/O 缓冲区¶
标准库在用户空间为每个流维护缓冲区, 多数读写先落在缓冲里, 满了或需要时再经系统调用进内核, 从而减少陷入次数.
写方向上常见三种策略:
| 类型 | 何时把数据交给内核 | 典型对象 |
|---|---|---|
| 全缓冲 | 缓冲写满, 或 fflush / fclose | 普通文件 |
| 行缓冲 | 遇到换行, 或缓冲写满, 或 fflush | 连到终端时的 stdout |
| 无缓冲 | 每次写库调用都尽快交出 | 通常是 stderr |
因此 printf("hello"); 后若进程被信号杀掉且没有换行、也未 fflush/exit, 终端上可能什么都看不到; printf("hello\n"); 或正常 return 经 exit 刷新后才会出现. 需要立刻看到输出时显式 fflush(stdout).
数值字符串转换¶
#include <stdlib.h>
int atoi(const char *nptr); /* 简便, 几乎无法区分「真是 0」与「解析失败」 */
long strtol(const char *nptr, char **endptr, int base); /* 可检查范围与未解析后缀 */
double strtod(const char *nptr, char **endptr);
atoi("123abc") 得 123; atoi("asdf") 也得 0, 与合法的 "0" 无法区分. 严格场景用 strtol / strtod:
-
调用前
errno = 0. -
传入
endptr, 返回后若endptr == nptr表示没有识别到任何数字. -
若
errno == ERANGE表示溢出 / 下溢. -
若
*endptr != '\0'表示后面还有未吃掉的字符 (是否算错由应用决定).
base 为 0 时按 C 字面量规则认前缀 (0 → 八进制, 0x → 十六进制); 显式传 10 / 16 等则按该进制解析.
分配内存¶
#include <stdlib.h>
void *malloc(size_t size); /* 未初始化 */
void *calloc(size_t nmemb, size_t size); /* nmemb * size 字节, 并清零 */
void *realloc(void *ptr, size_t size); /* 调整大小, 可能搬迁 */
void free(void *ptr); /* free(NULL) 合法 */
-
malloc/calloc/realloc失败返回NULL; 成功返回的指针必须最终free恰好一次. -
realloc可能返回新地址: 务必p = realloc(p, n)的写法要先用临时变量接返回值, 失败时旧块仍然有效, 直接赋给p会丢掉唯一指针. -
realloc(NULL, n)等价malloc(n); 部分实现对realloc(p, 0)的语义有历史差异, 新代码避免依赖「用realloc释放」. -
POSIX 的
alloca在调用者栈帧上分配, 函数返回即失效, 不能free, 过大则栈溢出风险高; 与 C99 VLA 类似, 不属于 ISO C 堆接口.
所有权, 泄漏与简易分配器模型见函数接口 · malloc / free 与 stdlib Heap Functions.