C foundations / Lesson 01

让第一个 C 程序
真正运行起来。

这节课只解决一件事:把自己写的 hello.c 变成能运行的程序,并说清每一行代码和每一条命令在做什么。暂时不用记大量语法,先建立“写、编译、运行、修改、再验证”的循环。

01 / C 语言基础零基础可开始以动手结果为准
01 / 目标

学完后,你能做到什么?

先给自己一个明确的终点。本课不要求记住每个编译器参数,但要求你能独立重做完整过程。

  • 确认电脑上有可用的 C 编译器,并在终端看见它的版本。
  • 亲手保存一个真正以 .c 结尾的源文件,编译后运行出 Hello, world!。
  • 解释 #include、main、printf、\n 和 return 0 的职责。
  • 发生错误时,能区分“编译没通过”“程序没找到”和“运行结果不对”。
本课产出

一个 hello.c 文件、一个编译出的可执行文件,以及一份自己的小记录:运行了什么命令、看到了什么输出、遇到过什么错误。

02 / 环境

先让编译器能被找到

编写 C 代码可以用普通文本编辑器或 VS Code;编辑器负责写文件,编译器负责把文件变成程序。选择与你的电脑相符的一条路径即可,不需要同时安装两套环境。

Windows x64

使用 MSYS2 UCRT64

  1. 从 MSYS2 官网 ↗安装,并按官网提示完成首次更新。
  2. 在开始菜单打开 MSYS2 UCRT64 终端;不要把它和名字只有“MSYS2 MSYS”的终端混淆。
  3. 在 UCRT64 终端安装 GCC,然后查版本:
pacman -S mingw-w64-ucrt-x86_64-gcc
gcc --version

如果输出中出现 GCC 版本信息,就具备本课所需的编译器。本页采用 MSYS2 的 x64 UCRT64 环境;其他架构请以官网对应安装说明为准。普通 Windows PowerShell 不会因为安装了 VS Code 就自动拥有 GCC;本课后续 Windows 命令都在 UCRT64 终端执行。

Ubuntu

使用系统包中的 GCC

  1. 打开 Ubuntu 终端。
  2. 安装 C 编译器 GCC,再查版本:
sudo apt update
sudo apt install gcc
gcc --version

已经安装过 GCC 的电脑可先运行 gcc --version;能看到版本便可直接进入下一步。命令里的 sudo 可能要求你输入系统密码。

遇到 gcc: command not found?先确认安装步骤确实完成,并确认自己正在正确的终端中;不要急着修改 C 代码。环境问题和语法问题要分开排查。
03 / 源码

亲手建立 hello.c

在一个容易找到的文件夹中,新建纯文本文件 hello.c。把下面的代码完整输入并保存。为了把注意力放在 C 语言本身,第一版只输出英文字母,暂时避开不同终端的中文编码差异。

hello.cC11 · 可直接编译
#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}
保存前检查:文件名应是 hello.c,不是 hello.c.txt;代码中的引号是英文半角双引号 ",分号是英文半角 ;。文本编辑器的“另存为”窗口有时会自动加上 .txt,请特别看一眼实际文件名。
04 / 执行

编译一次,再运行一次

让终端切换到保存 hello.c 的文件夹。先用 ls 确认当前目录能列出 hello.c,再使用对应平台的命令。这里每个平台都有两条不同的命令:第一条生成程序,第二条运行程序。

MSYS2 UCRT64

Windows 命令

gcc -std=c11 -Wall -Wextra hello.c -o hello.exe
./hello.exe

请在 UCRT64 终端输入;这里的 ./hello.exe 表示运行当前文件夹内刚生成的程序。

Ubuntu

Ubuntu 命令

gcc -std=c11 -Wall -Wextra hello.c -o hello
./hello

Linux 下这次把可执行文件命名为 hello;前面的 ./ 同样表示“当前文件夹”。

预期输出一行文本
Hello, world!

如果编译命令没有报错,它通常会安静地结束,并生成可执行文件。-std=c11 选择 C11 语言模式;-Wall 和 -Wextra 打开两组常用警告;-o 后面是你指定的输出文件名。警告不是“已修好”,应读懂并处理后再往下走。

第一次成功后,马上再试一次

把源码中的 world 改成自己的英文名字,保存、重新运行编译命令,然后再运行程序。只改源文件却直接启动旧的可执行文件,屏幕仍会显示旧结果。

05 / 阅读

逐行读懂这七行

别急着背“固定模板”。把每一行放回它要解决的问题中,就能理解为什么程序这样写。

#include <stdio.h>
我要调用谁?stdio.h 是标准输入输出头文件,里面提供了 printf 等函数的声明,让编译器知道我们准备怎样调用它。#include 是预处理指令;它并不是把整个标准库的实现代码复制进这个文件。函数实现的使用还涉及后面的链接步骤。
int main(void)
程序从哪里开始?在这类由操作系统启动的普通 C 程序中,main 是入口函数。int 表示它最终返回一个整数状态,括号里的 void 表示这个版本不接收参数。后面学 STM32 时会认识启动代码如何在进入应用 main 前准备硬件。
{ 和 }
哪些语句属于函数?花括号把函数体包在一起。从左花括号开始执行里面的语句,到右花括号结束。它们要成对出现;代码缩进方便人看,不代替花括号的结构作用。
printf("Hello, world!\n");
程序做了什么?printf 把双引号中的字符串输出到标准输出。这里的 \n 是换行转义序列,不是屏幕上要逐字打印的两个字符。右括号后面的英文分号 ; 结束这条语句;漏掉它会产生编译诊断。
return 0;
程序怎样结束?return 使 main 返回。约定上,状态 0 表示本次运行成功。先明确写出来,便于建立“程序可以向外报告结果”的概念。
空行与缩进:源码第二行是空行,四个空格是缩进,它们让结构更清楚。编译器关心的是语言结构;良好的排版主要帮助你和以后一起读代码的人。
06 / 原理

从文本到可执行文件

hello.c 只是人写的文本。它不能靠双击源码直接变成程序。使用 gcc 命令时,GCC 驱动会协调预处理、编译、汇编和链接;本课的一条编译命令把这些步骤串起来了。

  1. 01
    源码

    你保存的 hello.c,包含预处理指令和 C 语句。

  2. 02
    预处理

    处理 #include 等指令,准备送去编译的文本。

  3. 03
    编译与汇编

    把 C 代码转换为目标文件;目标文件还不是这次要运行的完整程序。

  4. 04
    链接

    把需要的目标代码与库代码联系起来,生成 hello.exe 或 hello。

  5. 05
    运行

    操作系统启动可执行文件;此时 printf 才真正执行并显示文字。

先分清两个动作:gcc ... -o ... 是构建程序;./hello 或 ./hello.exe 是运行已经构建好的程序。前者通过不代表后者输出一定符合你的预期,所以每次修改都要重新编译并运行。
想看一眼“只编译、不链接”是什么样?(选做)

在 hello.c 所在目录运行以下命令。-c 让 GCC 先停在目标文件阶段;此时会生成 hello.o,还没有生成最终程序。第二条命令再把目标文件链接成可执行文件。

gcc -std=c11 -Wall -Wextra -c hello.c -o hello.o
gcc hello.o -o hello

这组示例以 Ubuntu 的输出名 hello 演示;在 Windows UCRT64 可把最后的输出名改成 hello.exe。

07 / 排错

出了错,先判断卡在哪一步

报错文字会随编译器版本和系统略有变化。先看错误发生在环境、文件、编译、链接,还是运行,再回到对应位置修改。

看到的现象先检查什么下一步
gcc 找不到是否安装 GCC;Windows 是否打开的是 UCRT64 终端。回到本课环境步骤,先让 gcc --version 成功。
hello.c: No such file or directory终端所在文件夹、实际文件名是否为 hello.c。用 pwd 和 ls 核对目录;检查是否被保存为 hello.c.txt。
提示缺少 ;、引号或花括号从报错行及其上一行看是否漏写配对符号。对照完整示例修正后,重新运行编译命令。
找不到 main是否写成别的函数名,或编译的不是当前这份源码。确认 int main(void) 与命令中的文件名一致。
运行时提示找不到 hello编译是否真的成功;当前目录是否有输出文件。先用 ls 看文件,再输入带 ./ 的运行命令。
程序仍输出旧文字修改源码后是否保存、是否重新编译。保存 → 编译 → 运行,按这个顺序重做。
面对 warning 怎么办?

Warning 表示编译器发现值得注意的问题;它有时仍会生成程序。不要把“生成了文件”当成“代码已经正确”。先读完整诊断、定位源文件行号,再处理或解释警告。

08 / 练习

做三次小修改,而不是只读一遍

每题都先预测结果,再改代码、保存、编译、运行。展开答案前把自己的判断写下来;即使答对,也看一眼编译器有没有提示。

LEVEL 01 / 改动

换一句问候

把输出改为 Hello, C!。除了字符串里的文字,还有什么必须保留?运行后应看见几行?

查看参考答案
printf("Hello, C!\n");

保留双引号、右括号和分号;\n 让下一次输出从新的一行开始。这个程序仍只输出一行问候。

LEVEL 02 / 预测

连续输出两次

在 main 中依次放两条 printf:第一条输出 C 和换行,第二条输出 STM32 和换行。先画出你预期的屏幕内容,再运行验证。

查看参考答案
printf("C\n");
printf("STM32\n");

应看到两行:第一行 C,第二行 STM32。\n 是两行分开的关键;去掉第一条中的 \n,结果会变成同一行的 CSTM32。

LEVEL 03 / 排错

故意制造一次编译错误

暂时删掉 printf("Hello, world!\n") 末尾的分号并编译。记录编译器指出的文件名、行号和问题,再补回分号,重新编译、运行。错误信息不必与你看到的示例一字不差。

查看排错思路

缺少分号时,编译器会在这一句附近给出语法诊断,有时箭头指向下一行。修复后的语句应为:

printf("Hello, world!\n");

排错的目的不是背报错原文,而是练习把诊断定位到自己的代码,再用新一轮编译验证修复。

09 / 验收

什么时候可以进入下一课?

不用赶进度。下面四项可以独立完成时,本课就算过关:

  1. 关闭旧终端后重新打开,在正确的环境中运行 gcc --version。
  2. 从空文件重新敲出 hello.c;编译无错误和未解释的警告,输出与预期一致。
  3. 不用看答案,口头解释 main、printf、\n、return 0 以及 #include 的作用。
  4. 修改输出后,自己完成“保存 → 编译 → 运行”,并能用错误信息修复一个小拼写错误。
和嵌入式路线有什么关系?

日后写 STM32 程序,同样要把源码构建成目标文件和最终映像,也会读编译警告、定位错误。现在先在电脑上把这一套最短流程练熟,再逐步加入变量、控制流和硬件细节。

资料

从官方文档继续查

本课以“能读懂并运行”为目标。以后遇到具体编译器版本或开发板差异,优先核对所用工具链与板卡的官方文档。