
简介这份 C 语言函数库手册 PDF 面向刚入门 C 语言、需要频繁查阅标准库接口的开发者与学生解决函数名、参数、返回值记不牢、查文档效率低的问题。内容以函数分类为主线ctype.h 中的字符分类与大小写转换函数逐一列出判断条件如 isalpha、isdigit、isxdigit、isspace 与 tolower、toupper 的取值区间math.h、stdlib.h、string.h、float.h 相关的数学运算与数值转换函数也一并收录包括 abs、fabs、exp、log、pow、sqrt、三角函数与双曲函数以及 srand、rand、atof、atoi、itoa 等常用工具函数并标注返回值类型与弧度、基数等关键参数含义。整包共 1 个 pdf 文件约 51KB体积轻巧便于随查随用、离线阅读也可打印成速查卡片。目前已有 115 人学习适合作为课堂练习、课后作业和项目开发时的案头参考帮助读者减少翻书与搜索成本把精力集中在代码逻辑本身。1. C 语言函数库手册 PDF 到底该怎么用才不是翻完就忘很多人硬盘里都躺着一份 C 语言函数库手册 PDF可能是标准库中文手册的扫描版也可能是从系统 man page 导出后拼起来的合集。平时想不起来翻真写代码时还是先去搜索引擎里找strcpy到底返回什么、snprintf截断了怎么判断。问题不在手册本身而在于 PDF 这个载体天生不擅长按函数查——它擅长线性阅读不擅长按函数名、按头文件、按 errno 做交叉定位。把一份 C 语言函数库手册用出价值关键在于换个角色把它当原料而不是成品。先用 man 的分节结构把手册里每个字段的含义吃透再动手生成一份属于自己项目、能跳转能搜索的 PDF接着用 pdf 解析的手段把手册变成可查询的索引最后把手册里的一行函数原型变成能编译、能跑、能断言的验证用例。下面按这条链路走一遍每一步都给到可复现的命令和参数。2. 把 C 函数库手册里的字段读懂比背函数名更值钱2.1 man 2 与 man 3 的分工以及 SYNOPSIS 段怎么读C 函数库手册的骨架来自 man 的分节约定man 2是系统调用open、read、write、mmapman 3是库函数printf、strcpy、qsort、malloc。新手最容易混的是同一个名字在两个节里都有比如open在 2 节是文件描述符在 3 节是fopen那一套的缓冲流接口。手册 PDF 如果不标节号检索出来的结果就会互相打架所以自己整理 PDF 时务必在页眉或索引里保留节号。SYNOPSIS 段是手册里信息密度最高的一块它同时告诉四件事依赖哪个头文件、函数原型、参数的可选性、返回值类型。看下面这个典型条目#include string.h char *strcpy(char *dest, const char *src);#include行决定了你必须引入哪个头文件原型里的const决定了src不会被改写而手册正文里那句 the destination buffer must be large enough 是约束而不是检查——编译器不会替你验证dest到底有多大。很多人读手册只扫一眼函数名就跳过去恰恰漏掉了这类约束型描述而它们才是后面写测试用例的依据。2.2 返回值、errno 与头文件宏手册里最容易跳过的三段RETURN VALUE 和 ERRORS 两段是排查问题的入口。以snprintf为例手册明确写返回值是假如缓冲区足够大时应当写入的字符数不包含结尾的\0一旦这个值大于等于你传入的size就说明输出被截断了。这条规则如果只看函数名是绝对猜不到的。errno 的使用有个常见误解不是所有函数失败都会设置 errno只有在 ERRORS 段列出具体错误码的函数才保证设置。正确的写法是在调用前把errno清零调用后立即读取#include errno.h #include stdio.h #include stdlib.h errno 0; /* 手册要求先清零避免读到上次残留值 */ char *end NULL; long v strtol(999999999999, end, 10); if (errno ERANGE) { /* 手册 ERRORS 段列出的溢出错误码 */ fprintf(stderr, out of range, value%ld\n, v); }strtol的 ERRORS 段只列出EINVAL和ERANGE两个值写判断时就只判断这两个多写的分支反而会掩盖真实问题。手册里还有一类容易忽略的内容是头文件里的宏约束比如limits.h的LONG_MAX、stdint.h的INT32_MAX它们决定了你写long long数组做累加时在什么范围内不会溢出。2.3 用 grep 和 apropos 在本地手册里按头文件、按 errno 反查PDF 不适合反查但导出的纯文本手册非常适合。先把 man 手册批量转成去控制符的 txt这一步是后面所有检索的基础# 把 2、3 节的手册页导出成纯文本col -b 去掉退格控制字符 mkdir -p ~/c-manual/txt for p in 2 3; do man -k . 2/dev/null | awk -v p($p) $2 p {print $1} done | sort -u | while read -r f; do man 3 $f 2/dev/null | col -b ~/c-manual/txt/$f.txt done ls ~/c-manual/txt | wc -l有了这批文本就能做两种搜索引擎给不了的查询。第一种是按头文件反查找出某个头文件下到底声明了哪些函数这在排查我到底该 include 谁时很有用# 按头文件反查函数清单 grep -l include stdio.h ~/c-manual/txt/*.txt | xargs -n1 basename # 按错误码反查哪些函数可能返回 ERANGE grep -l ERANGE ~/c-manual/txt/*.txt | xargs -n1 basename # 用 NAME 段做关键字搜索比 man -k 更容易进脚本 grep -il string copy ~/c-manual/txt/*.txt手册 PDF 里的各个段落对应的检索价值可以整理成一张对照表整理手册索引时按这张表决定抽哪些字段man 段名内容检索价值SYNOPSIS头文件与函数原型生成函数签名索引、做原型比对DESCRIPTION行为描述与约束提取must be large enough类约束RETURN VALUE成功/失败返回值语义生成断言条件ERRORSerrno 取值列表生成异常分支测试CONFORMING TO遵循的标准判断可移植性边界SEE ALSO相关函数构建函数关联图注意man -k依赖 mandb 数据库如果刚装完系统没建库apropos会返回空。先执行一次mandb再导出。3. 用 Doxygen 生成一份属于自己的 C 函数库手册 PDF3.1 针对 C 项目的 Doxyfile 关键参数现成的手册 PDF 只能读不能反映你项目里的函数。常见做法是用 Doxygen 从源码注释里直接生成手册再交给 LaTeX 输出 PDF。C 项目需要显式打开几个开关否则 Doxygen 会按 C 的假设去解析遇到函数指针和宏定义就出错。# Doxyfile 片段面向纯 C 库的输出配置 PROJECT_NAME MyCLib Manual OPTIMIZE_OUTPUT_FOR_C YES # 按 C 的语义解析避免 C 假设 EXTRACT_ALL YES # 没有 /** */ 注释的函数也进手册 EXTRACT_STATIC YES # 静态函数一并输出便于内部审阅 INPUT ./src ./include RECURSIVE YES FILE_PATTERNS *.c *.h GENERATE_HTML NO GENERATE_LATEX YES LATEX_CMD_NAME xelatex HAVE_DOT YES CALL_GRAPH YES CALLER_GRAPH YES MACRO_EXPANSION YES EXPAND_ONLY_PREDEF NO这批参数里OPTIMIZE_OUTPUT_FOR_C直接影响交叉引用的准确性它会让 Doxygen 把函数指针 typedef 当作类型引用而不是未知符号。MACRO_EXPANSION决定宏包装过的函数能不能出现在手册里比如你用#define API __attribute__((visibility(default)))修饰过导出函数不开这个开关手册里就只剩一个宏名。CALL_GRAPH依赖 Graphviz 的dot没装的话编译阶段会只给警告图不会生成。几个参数的取值倾向和影响范围参数常见取值影响OPTIMIZE_OUTPUT_FOR_CYES按 C 语义生成索引函数指针解析更准EXTRACT_STATICYES / NO决定内部函数是否出现在手册MACRO_EXPANSIONYES宏包装的声明能否被识别LATEX_CMD_NAMExelatex输出中文 PDF 的前提CALL_GRAPHYES生成调用图依赖 dot3.2 让 LaTeX 输出的中文 PDF 不掉字Doxygen 的 LaTeX 模板默认用 pdflatex 和西文字体中文注释和文档标题会直接丢失或者报字体错误。改成xelatex之后还需要引入 ctex 宏包。稳妥的路径是准备一个自定义样式文件通过LATEX_EXTRA_STYLESHEET注入不去改 Doxygen 的模板文件这样升级 Doxygen 时不会冲突# doxygen-zh.sty 放在项目根目录供生成出的 latex 工程引用 cat doxygen-zh.sty EOF \usepackage{ctex} \usepackage{fontspec} \setmonofont{DejaVu Sans Mono} EOF doxygen Doxyfile cd latex makemake会在 latex 目录下生成refman.pdf。如果中途卡在字体上先确认fc-list :langzh能列出中文字体如果只有警告没有报错通常是对\subsection级别的标题做了截断检查日志里Missing character附近的行号即可。生成出的手册 PDF 目录结构和原版 C 手册是一致的模块页、文件页、数据结构页、函数索引可以直接替换掉硬盘里那份扫描版。3.3 函数指针、指针函数与调用图的解析差异int (*f)(int)是函数指针int *f(int)是指针函数这两者在源码里只差一对括号Doxygen 的解析结果完全不同。前者会被识别为变量类型后者会被识别为返回指针的函数。写接口注释时用typedef先把函数指针类型命名出来手册里的交叉引用才会正确指向类型定义页/** * brief 排序比较函数类型 * param a 指向第一个元素 * param b 指向第二个元素 * return 负数、零、正数分别表示 ab、ab、ab */ typedef int (*cmp_fn)(const void *a, const void *b); /** * brief 对数组做原地排序 * param base 数组首地址 * param nmemb 元素个数 * param size 单个元素字节数 * param cmp 比较函数见 cmp_fn */ void sort_array(void *base, size_t nmemb, size_t size, cmp_fn cmp);这样一来sort_array的文档页会出现指向cmp_fn的链接CALL_GRAPH 也能顺着回调把调用关系画出来。相反如果直接把int (*cmp)(const void*, const void*)写进参数列表Doxygen 会把cmp解析成一个无名类型参数手册里就只剩一行光秃秃的原型读者还得回去翻源码。注意CALL_GRAPH 画的是静态调用关系通过函数指针发起的调用它识别不了除非调用点显式写出具体函数名。做性能分析时别把它当成完整调用链。4. 用 Python 解析 C 函数库手册 PDF建一个可检索索引4.1 用 pdfplumber 按坐标还原 SYNOPSIS 代码块拿到一份现成的手册 PDF 后第一步是把 SYNOPSIS 段抽出来。extract_text()对分栏排版的 PDF 会丢列对齐函数原型经常被压成一行正则根本没法匹配。更稳的做法是按 y 坐标聚行、按 x 坐标排序把视觉上的行还原出来import pdfplumber def page_lines(page, y_tol3): 按 y 坐标聚类成行按 x 坐标排序还原阅读顺序 words page.extract_words(use_text_flowFalse) rows {} for w in words: key round(w[top] / y_tol) rows.setdefault(key, []).append(w) for key in sorted(rows): row sorted(rows[key], keylambda w: w[x0]) yield .join(w[text] for w in row) with pdfplumber.open(c-library-manual.pdf) as pdf: for i, page in enumerate(pdf.pages, 1): head \n.join(page_lines(page)) if SYNOPSIS in head: print(f--- page {i} ---) print(head)extract_words拿到的每个词都带x0、top坐标y_tol3是把垂直距离 3 点以内的词视为同一行。这个值对手册这类单栏排版够用如果原 PDF 是双栏扫描件先按页面宽度从中间切开再处理否则左右两栏会被拼到同一行。4.2 正则解析函数原型把手册变成结构化字段还原出行之后用一条正则把返回值、函数名、参数列表拆开。手册里的原型大多数是ret name(args);这一形态宏包装和函数指针会漏掉属于可接受的取舍import re PROTO re.compile( r^\s*(?Pret(?:const\s)?[A-Za-z_]\w*(?:\s*\*)?)\s r(?Pname[A-Za-z_]\w*)\s*\((?Pargs[^;]*)\)\s*;\s*$ ) def parse_prototype(line): m PROTO.match(line) if not m: return None return { ret: m.group(ret).strip(), name: m.group(name), args: .join(m.group(args).split()), }(?:\s*\*)?用来吃掉落在这个位置的单个星号比如char *strcpy的返回类型[^;]*限定参数列表里不能出现分号避免把多行声明误吞。 .join(...split())是把参数之间的多余空格压缩掉保证同一个函数在不同页面上抽出来的args一致方便去重。手册字段到数据库列的对应关系手册字段正则组数据库列用途返回类型retret查看返回指针还是值函数名namename主键查询参数列表argsargs去重与比对所在页码外部传入page回查原文所在头文件上一行 includeheader按头文件分组4.3 落进 SQLite写一个命令行检索入口结构化的下一步是存起来SQLite 单文件、零配置适合放在仓库里当工具用import sqlite3 conn sqlite3.connect(cmanual.db) conn.execute(CREATE TABLE IF NOT EXISTS funcs ( name TEXT NOT NULL, ret TEXT, args TEXT, header TEXT, page INTEGER, PRIMARY KEY (name, args) )) conn.executemany( INSERT OR REPLACE INTO funcs VALUES (:name, :ret, :args, :header, :page), rows, ) conn.commit()查询时直接用 sqlite3 命令行比写脚本更快# 按函数名模糊查 sqlite3 -header -column cmanual.db \ SELECT name, ret, header FROM funcs WHERE name LIKE %cpy%; # 找出所有返回指针的函数排查内存管理相关接口 sqlite3 -header -column cmanual.db \ SELECT name, ret FROM funcs WHERE ret LIKE %*% ORDER BY name;有了这张表还能做手册版本比对把两份不同来源的 PDF 分别解析入库用EXCEPT找出新增或删除的函数声明接口变更一目了然。这比用 pdf 转 word 再肉眼比对靠谱得多。注意PDF 解析出来的页码是 PDF 物理页码不是手册印刷页码回查时要加上封面和目录的偏移量。5. 把手册条目变成能跑的验证用例5.1 边界值从手册的约束句里反推手册里必须足够大可能被截断这类句子翻译成代码就是边界条件。snprintf的返回值语义是最典型的一个#include assert.h #include stdio.h #include string.h int main(void) { char dst[8]; /* 手册返回假如空间足够应写入的长度不含结尾 \0 */ int n snprintf(dst, sizeof dst, %s, abcdefghij); assert(n 10); /* 说明需要 10 字节被截断了 */ assert(strlen(dst) 7); /* 实际只写入了 7 个字符 \0 */ return 0; }编译时打开-D_FORTIFY_SOURCE2让strcpy、sprintf这类无长度限制的函数在编译期就报警gcc -stdc11 -Wall -Wextra -O2 -D_FORTIFY_SOURCE2 snprintf_check.c -o snprintf_check-O2是_FORTIFY_SOURCE生效的前提优化等级不够时它不会被激活-Wall -Wextra负责把隐式截断、格式串不匹配这类警告暴露出来。手册不会告诉你这些编译开关但它们和手册里的约束描述是配套的。5.2 函数指针用法的验证qsort 比较函数的返回值陷阱手册里qsort的比较函数要求返回负数、零、正数很多示例直接写return *(int*)a - *(int*)b;。换成long long数组做累加或排序时这个减法会溢出typedef int (*cmp_fn)(const void *, const void *); static int cmp_ll(const void *a, const void *b) { long long x *(const long long *)a; long long y *(const long long *)b; return (x y) - (x y); /* 手册只要求符号不要求具体差值 */ }(x y) - (x y)的写法把结果严格约束在 -1、0、1 三个值上避开了减法溢出后又截断成 int 的风险。不同函数在手册里没写全的约束以及对应的验证方式函数手册未写全的约束验证方式strcpydest 的容量下限编译期_Static_assert检查缓冲区长度snprintf截断的判断依据断言返回值 sizeqsort 比较函数返回值不得溢出 int用符号表达式替代减法malloc失败时返回 NULL 而非置 errno显式判空不依赖 errnostrtol溢出时返回边界值并置 ERANGE清零 errno 后检查 ERANGE把这批断言整理成一个tests/目录用make check串起来每次改动手册注释或函数签名时跑一遍手册和实现就不会各自漂移。前面几节生成的手册 PDF 加索引 SQLite配合这套用例才算把一份 C 语言函数库手册从存着变成用着。本文还有配套的精品资源点击获取