《Learning GNU C》读书笔记

作者 Ciarán O’Riordan,2002 年出版,GFDL 自由文档许可证下的免费 C 语言教程。全书 72 页,本 PDF 为 O’Reilly 风格排版版。定位是 GNU/Linux 用户的 C 入门教程,但后半部分(存储类、预处理器、嵌套函数、函数属性)对老手也有参考价值。注意:这是未完成稿——第 8 章(字符串与文件 I/O)、第 11 章(变长参数)、第 12.1 节(函数指针)、13.4 节(环境变量)都是占位符(“Place Holder”、“BLAH”、“better go write one…”),作为教程不完整,但已有章节质量高。

核心定位与写作理念

  • 目标读者:会用 shell 的 GNU/Linux 用户,零编程基础假设;对 FreeBSD/NetBSD/OpenBSD 用户约 98% 适用。
  • 内容双主线:C 核心语言(前几章)+ GNU Libc 标准功能库(后几章),循序渐进、每章建立在前章之上。
  • 作者带强烈 Free Software 立场:书本身即 GFDL 许可证作品,序言花整节论证自由软件的四项自由(使用/修改/分享/分发修改版)及”源代码可审读=同类评审机制=质量优势”的技术论点和”不让贫穷成为使用计算机的门槛”的伦理论点。
  • 序言的”人称指代约定”是个有趣细节:虚构人物一律用女性且设定为残障+少数族裔,用以平衡技术书籍的性别偏见。

C 语言基础要点(第 1-4 章)

  • 语言分层:机器语言(数字指令,CPU 直接理解)→ 汇编(文本化助记符,绑定特定 CPU)→ 高级语言(机器无关,靠编译器翻译)。经典对比示例:同一”算税”程序,80386 汇编约 30 行 vs C 约 8 行。
  • C 方言谱系:K&R C → ISO C (C89/C90) → C99 → GNU C(GCC 扩展)。GCC 原意 GNU C Compiler,现为 GNU Compiler Collection。
  • 最小合法程序必须 int main() { return 0; },gcc -Wall 能抓出缺返回类型和落空控制流两类警告;-o 指定输出名(a.out 是”assembler output”的历史遗留)。
  • 数据类型体系:char/short/int/long/float/double,用 sizeof 查字节数;全局变量文件级可见;static 变量在函数调用间保值;const 变量比 define 常量更灵活(可取地址,不取地址时 GCC 优化效果与宏相当)。
  • printf 格式符:%d 整数、%f 浮点、%c 字符、%s 字符串、%p 指针地址。
  • 流程控制:if…else、while、for、do..while、switch(每个 case 要 break)、三元运算符 ?:(C 唯一的三元运算符)、break(跳出循环)与 continue(回到循环头)。

指针(第 5 章)——本书认为的分水岭

  • 副标题直言 “sorting the programmers from the students”(区分程序员和学生)。
  • 两个新 operators:& 取地址、* 解引用。指针=存内存地址的变量。
  • 核心价值:让函数能修改自身作用域之外的变量(swap_ints 例子:传 &a, &b,函数内通过 *first_number 读写调用者的变量)。
  • 指针算术:加减以指向数据类型的大小为单位;非法访问触发 segmentation fault(访问无权访问的内存段)。
  • void* 泛型指针:不能直接解引用,必须先强制转换类型;适合”同一指针先后指向不同类型数据”的场景。
  • 变量地址不保证跨运行稳定——依赖固定地址是错误做法。

结构化数据与动态内存(第 6-7 章)

  • 数组:同类型元素集合,下标 0 起;越界访问不会报错而是崩溃或读到垃圾数据——边界检查是程序员的职责。GNU 扩展支持按编号选择性初始化:int a[100] = { [90]=4, [0]=5 };。多维数组最多 29 维(实际超 3 维极罕见)。
  • C 字符串 = 以 '\0' 结尾的连续 char 数组,没有独立字符串类型(这是”C 只提供最小类型集,其余靠组合”哲学的例子)。
  • struct:混合类型聚合,struct Person { char name[40]; int age; }; 成员用 . 访问。union:声明语法同 struct 但成员共享同一块内存,字段类型可随时间点变化。
  • 动态内存:程序运行时才知道数据规模时,用 glibc 的 malloc() 申请、free() 释放。作者警告”忘了 close() 释放内存是最糟糕的编程错误之一”。

存储类(第 9 章)

  • auto:默认,随作用域自动创建/销毁,显式写 auto 关键字只为语言对称性,无实际效果。
  • static:出作用域不销毁,函数调用间保值(get_next_square 例子:static 计数器返回连续平方数)。
  • extern:不分配内存,声明”该变量在函数/文件外部已存在”;跨文件共享全局变量的标准做法=一个文件全局定义 + 头文件 extern 声明。
  • register:提示编译器把高频变量放入 CPU 寄存器;取了地址的变量 GCC 会忽略此关键字(寄存器无地址);GCC 自己启发式分配寄存器通常很好但不完美。
  • restrict(C99):告知编译器某指针是访问该内存的唯一途径,便于优化(作者自谦”我猜是这样”——原书坦诚标记不确定处)。
  • typedef:给类型起新名。两大用途:省略 struct 关键字;跨平台定宽类型(用预处理指令按平台 typedef 出保证 32 位的类型)。

预处理器与宏陷阱(第 10 章)

  • 预处理器 = 编译前运行的宏展开器,# 开头行是指令,最常见 define 与 include。
  • 宏的两大价值:一处改动全局生效(防漏改);有意义命名提高可读性(#define PI 3.142857143)。宏展开发生在编译前,因此宏本身的 bug 编译器看不见——调试更难。
  • 经典陷阱:带副作用参数的宏。#define MAX(a,b) (a>b?a:b) 遇到 MAX(cows++, sheep) 时参数被展开多次、副作用执行多次。
  • 替代方案:
    • 简单值宏 → const 变量(可取地址,优化同级)。
    • 函数式宏 → inline 函数。
  • glibc 里部分小函数本身就是宏实现(如 getc())。

GNU 扩展特性(章节错误编号 12-13、16)

  • 嵌套函数(GCC 扩展):函数内定义函数,遵守词法作用域,可直接访问外层函数变量——省去传指针参数(传指针会使变量无法进寄存器,有微小性能损失)。作用域为局部:声明为 extern 报错,static/inline 合法。父函数退出时子函数失效。
  • 命令行参数:main 可零/二/三参数;二参数形式 main(int argc, char *argv[]),argv[argc]==NULL(argv 永远 null 终止)。三参数形式可拿环境变量(书中未展开)。
  • argp(glibc):标准化命令行解析框架,argp_option 表定义选项 + parse_opt 回调 + ARGP_KEY_ARG/ARGP_KEY_END 校验参数个数,自动生成 —help/—version(配合 argp_program_version、argp_program_bug_address)。比手写 argc/argv 遍历更接近 GNU 标准用户体验。
  • 函数属性(GCC 扩展):int f(int, int) __attribute__((pure));
    • pure:可读全局/指针指向的数据但不可写;不读 volatile/外部资源。
    • const:比 pure 更严,只依赖入参值,连指针解引用都不允许读。
    • 效果:GCC 可做公共子表达式消除——循环里重复调用直接替换为已算出的值(纯/常函数返回 void 则毫无意义)。

工程质量观点(第 15-16 章)

  • 可读性是良好代码的第一品质;最大敌人是”聪明代码”——把多个操作挤进一行炫技,别人读时要在脑内重新展开。
  • 二八定律在优化上的表述:90% 运行时间消耗在 10% 的代码里,别优化很少被调用的函数。
  • 库:无 main() 的函数集合,-lm 形式链接;静态链接编入应用 vs 动态链接运行时加载(库可独立更新不需重编应用)。编译三阶段:预处理(CPP)→ 编译(源码→目标码,-c 只编不链)→ 链接。

可行动点 / 工程启示

  • 写 C 时把 -Wall 设为默认(本书最小示例即用 -Wall 演示警告文化)。
  • 评审 C 代码先搜多参数宏:检查实参是否带副作用(++、函数调用),建议 const/inline 替代。
  • 嵌入式/驱动代码里 register 关键字基本是历史文物,restrict 在 C99 编译的驱动中仍有优化价值。
  • 需要 C 里实现多态回调用函数指针(本书占位未写,可参照 C语言参考手册 或 K&R)。

关联

  • 云盘同目录还有大量 C/C++ 分章 PDF(/田浩然上传的资料/电子书/C_C++/),本书是其中唯一整本教程。
  • The C Programming Language (K&R) 体系互补:K&R 覆盖本书占位的字符串/文件 I/O/变长参数章节。
  • GCC 函数属性与嵌入式仪器软件(如化学发光仪、流式细胞仪上位机)的 C 编码规范相关。