这一章在干嘛?

开门见山:用一个完整的小程序把「写 C 代码 → 翻译成机器能执行的文件 → 运行看结果」整条链路走一遍,对 C 的样子建立第一印象。细节不懂没关系,后面每一章都会把对应的部分展开讲透。

1.1 解剖一个完整的小程序

1.2 从源码到运行:翻译的完整流水线

1.3 给初学者的三条上手建议

1.1 解剖一个完整的小程序

下面这个程序做一件很朴素的事:从标准输入读文本,把每一行中指定列范围的内容提取出来打印。列号从 0 开始,成对给出,以一个负数结束。比如输入 3 7 -1,就提取每行的第 3~7 列:

#include <stdio.h>
#include <string.h>

#define MAX_COLS 20     /* 最多处理的列对数 */
#define MAX_INPUT 1000  /* 每行输入的最大长度 */

/* 读列对:成对存入 cols,遇到负数或读满停止,返回对数 */
static int read_columns(int cols[], int max_cols)
{
    int n = 0, v;
    while (n < max_cols && scanf("%d", &v) == 1 && v >= 0) {
        cols[n++] = v;               /* 起点 */
        if (n < max_cols && scanf("%d", &v) == 1 && v >= 0)
            cols[n++] = v;           /* 终点 */
        else
            break;                   /* 凑不满一对:结束 */
    }
    return n / 2;
}

int main(void)
{
    int cols[MAX_COLS];
    char line[MAX_INPUT];
    int pairs = read_columns(cols, MAX_COLS);
    int start, end, i;

    while (fgets(line, MAX_INPUT, stdin) != NULL) {
        line[strcspn(line, "\n")] = '\0';   /* 去掉行尾换行 */
        for (i = 0; i < pairs; i++) {
            start = cols[2 * i];
            end   = cols[2 * i + 1];
            printf("%.*s\n", end - start + 1, line + start);
        }
        putchar('\n');
    }
    return 0;
}

逐块看它都用了什么:

代码片段涉及的知识点
include <stdio.h>预处理指令:把标准库的函数声明抄进来(第 14 章)
define MAX_COLS 20符号常量:给「魔法数字」起名字(第 14 章)
int cols[], char line[]数组:一串同类型数据住在连续内存里(第 8 章)
line + start指针运算:数组名退化成指针再偏移(第 6、8 章)
printf(”%.*s\n”, …)格式化 I/O:域宽、精度那些 % 开头的暗号(第 15 章)
static / return函数与存储类型(第 3、7 章)

为什么第一课不是语法细节?

因为 C 的知识点是一张网:指针连着数组、数组连着函数参数、函数连着声明。先见过「整体长什么样」,再逐个击破,比从孤立语法片段学起效率高得多。

1.2 从源码到运行:翻译的完整流水线

计算机只认机器码,C 源文件必须经过一条流水线变成可执行文件:

  • 预处理:纯文本替换——#include 抄头文件、#define 做替换、条件编译裁代码;
  • 编译:把预处理后的文本翻译成汇编/目标码,同时做语法检查;
  • 链接:把你写的目标文件和库的目标文件拼在一起,补齐「外部符号」的地址(比如 printf 到底在哪儿)。

日常一条命令 gcc main.c -o prog 其实把这几步全做了。分步执行可以用 -E(只预处理)、-c(只编译到目标文件)观察中间产物。

链接期错误的典型长相:

undefined reference to 'foo' ——编译器见过 foo 的声明所以不报错,但链接器找不到它的定义。九成是忘了写实现或忘了链上对应的目标文件。

1.3 给初学者的三条上手建议

① 警告要当错误看。 C 编译器非常宽容,很多「能跑」的程序其实埋着雷。把 -Wall -Wextra 开成习惯,警告清零再谈运行。

② 每个知识点配一个最小实验。 学到指针运算就写五行代码验证 *(p+1) 到底等于什么;sizeof 一个怀疑的类型就 printf("%zu\n", sizeof(int))。机器永远比记忆可靠。

③ 出错先想内存。 段错误、乱码、诡异崩溃,大多源于越界、未初始化、悬空指针。养成习惯:声明变量就初始化,用数组先问长度。

本章通关标准:

能独立编译运行一个小程序;能说出 includedefine 在哪个阶段起作用;看到 undefined reference 知道该查链接而不是语法。