C/C++ 入门:从编译环境到 HelloWorld

最近想系统补一下计算机基础,从 c/c++ 开始吧。

📚 基本概念速读

名称 定义 省流
源码 程序员写的文本代码,比如 .c.cpp 文件 人能读
机器码 CPU 能直接执行的二进制指令 机器能跑
目标文件 源码经过编译和汇编后得到的中间文件,比如 .o.obj 还不能独立运行
可执行程序 链接完成后的最终程序,比如 Linux 下的 a.out、Windows 下的 .exe 可以运行
编译器 把高级语言翻译成更低层表示的工具 负责翻译
链接器 把多个目标文件和库组合成可执行程序的工具 负责拼装
标准库 语言自带的一组常用功能,比如输入输出、字符串、容器 省掉重复造轮子
头文件 通常保存声明的文件,比如 iostream.h.hpp 告诉编译器有什么

🧩 核心概念

源码、机器码、可执行程序

源码是给人看的,机器码是给 CPU 执行的。C/C++ 程序不能直接把 .cpp 文件丢给操作系统运行,中间必须经过编译工具链处理。



flowchart TD
    A[源码 hello.cpp] --> B[目标文件 hello.o]
    B --> C[可执行程序 hello]

操作系统真正启动的是可执行程序,不是源码文件。

编程语言分类

常见分类不是绝对边界,但有助于理解 C/C++ 的位置。

分类 代表语言 特点
编译型语言 C、C++、Rust、Go 通常先生成可执行文件,再运行
解释型语言 Python、JavaScript 通常由解释器边解释边执行
字节码语言 Java、C# 先编译成字节码,再由虚拟机运行
低级语言 汇编语言 接近 CPU 指令,可移植性较弱
高级语言 C++、Python、Java 更接近人的思考方式

C 和 C++ 通常被归到编译型语言。它们离硬件更近,性能和控制力强,但也要求程序员理解更多底层细节。

🛠️ 编译环境

常见编译器

编译器 常用命令 常见平台 说明
GCC gccg++ Linux、macOS、Windows MinGW GNU 编译器集合
Clang clangclang++ Linux、macOS、Windows LLVM 项目的前端编译器
MSVC cl Windows Visual Studio 自带编译器

C++ 代码建议使用 g++clang++cl 编译。虽然 gcc 也能参与 C++ 编译,但它默认更偏 C 语言入口,链接 C++ 标准库时容易让初学者困惑。

简单理解三者:

工具链 特点 适合场景
GCC 历史久、生态成熟、Linux 上非常常见 Linux 开发、开源项目、竞赛和课程环境
Clang 错误提示清晰、模块化设计好、和 LLVM 生态绑定紧密 macOS、现代 C++ 项目、静态分析和编译器相关工具
MSVC 和 Visual Studio、Windows SDK 集成紧密 Windows 原生开发、Visual Studio 工程

GNU 是一个自由软件项目,GCC 是 GNU 生态里最重要的编译器工具链之一。Linux 发行版里常见的 gccg++ldmake 等工具,很多都来自 GNU 生态。

LLVM 更像一套可复用的编译器基础设施。Clang 负责解析 C/C++/Objective-C 源码,LLVM 负责中间表示、优化和后端代码生成。很多现代工具都会复用 LLVM,例如静态分析器、格式化工具、语言服务器和新的语言编译器。

怎么选:

场景 建议
Linux 初学 g++,资料最多,环境最常见
macOS 初学 clang++,系统工具链默认就是 Clang
Windows + Visual Studio cl,和 IDE、调试器、SDK 配合最好
Windows + 命令行练习 MinGW 的 g++ 或 LLVM 的 clang++ 都可以
课程、OJ、竞赛 优先使用平台指定的编译器和标准版本

最小编译命令

假设文件名是 hello.cpp

1
2
3
4
5
# 使用 g++ 编译 C++ 源码,并生成名为 hello 的可执行程序
g++ hello.cpp -o hello

# 运行程序
./hello

在 Windows 的 PowerShell 中,使用 MinGW 或 MSVC 时,运行方式通常是:

1
2
# 运行当前目录下的 hello.exe
.\hello.exe

常见编译参数

参数 示例 作用
-o g++ hello.cpp -o hello 指定输出文件名
-Wall g++ hello.cpp -Wall -o hello 打开常见警告
-Wextra g++ hello.cpp -Wall -Wextra -o hello 打开更多警告
-std=c++17 g++ hello.cpp -std=c++17 -o hello 指定 C++ 标准版本
-g g++ hello.cpp -g -o hello 生成调试信息
-O2 g++ hello.cpp -O2 -o hello 开启较常用的优化级别
-E g++ -E hello.cpp -o hello.i 只做预处理
-S g++ -S hello.cpp -o hello.s 生成汇编代码
-c g++ -c hello.cpp -o hello.o 只生成目标文件,不链接

-std=c++17 的意思是:让编译器按照 C++17 标准理解这份代码。C++ 语言会不断演进,常见标准包括 C++98、C++11、C++14、C++17、C++20、C++23。

标准 简单印象 选择建议
C++11 现代 C++ 的起点,引入 auto、lambda、智能指针等 维护旧项目时常见
C++14 对 C++11 做了小幅增强 老项目可能使用
C++17 稳定、普及度高,很多课程和工程仍在使用 初学和普通项目的稳妥选择
C++20 引入 concepts、ranges、coroutines 等较大特性 新项目可考虑,但要看编译器支持
C++23 更新,生态仍在跟进 学习新特性或特定项目需要时使用

初学阶段建议从 -std=c++17 开始:资料多、编译器支持成熟,也不会一开始就被 C++20/23 的新特性分散注意力。如果课程或项目明确要求某个版本,以课程或项目为准。

-O2 是优化选项,告诉编译器在不明显拉长编译时间的前提下,尽量生成运行更快的机器码。常见优化级别有:

参数 含义 常见用途
-O0 不优化,默认通常接近这个级别 调试最友好
-O1 开启基础优化 较少手动使用
-O2 开启多数稳妥优化 发布版本常用
-O3 更激进优化,可能增大代码体积 计算密集型程序可测试
-Os 优化体积 嵌入式或体积敏感场景
-Og 兼顾调试体验和部分优化 调试优化相关问题

优化可能做的事情包括:删除无用代码、减少重复计算、内联小函数、调整循环、改善寄存器使用。它不改变语言语义,但如果程序本身有未定义行为,比如数组越界、使用未初始化变量,优化后问题可能更明显。

调试时优先用:

1
g++ hello.cpp -std=c++17 -Wall -Wextra -g -O0 -o hello

发布或性能测试时再考虑:

1
g++ hello.cpp -std=c++17 -Wall -Wextra -O2 -o hello

初学阶段推荐命令:

1
g++ hello.cpp -std=c++17 -Wall -Wextra -g -o hello

⚙️ 程序完整运行流程

从源码到运行,C/C++ 一般经历四个构建阶段:



flowchart LR
    A[源码 .cpp] --> B[预处理 .i]
    B --> C[编译 .s]
    C --> D[汇编 .o]
    D --> E[链接 可执行程序]

如果把“编译”这一步继续拆开,编译器内部大致会经历下面的流程:



flowchart LR
    A[源码 .cpp] --> B[预处理]
    B --> C[词法/语法分析]
    C --> D[语义分析]
    D --> E[生成 IR]
    E --> F[IR 优化]
    F --> G[指令选择]
    G --> H[寄存器分配]
    H --> I[生成汇编/目标文件 .s/.o]

预处理

预处理器处理 #include#define、条件编译等内容。

1
g++ -E hello.cpp -o hello.i

#include <iostream> 会在这个阶段展开相关声明。可以粗略理解为:把编译所需的接口信息带进来,让后续阶段知道 coutendl 等名字是什么。

常见头文件:

头文件 作用 常见内容
<iostream> 标准输入输出 cincoutcerrendl
<string> 字符串 std::string
<vector> 动态数组 std::vector
<array> 固定长度数组封装 std::array
<algorithm> 常用算法 sortfindmaxmin
<cmath> 数学函数 sqrtpowsincos
<cstdlib> 通用工具 exitabs、随机数相关旧接口
<cstdio> C 风格输入输出 printfscanf
<fstream> 文件输入输出 ifstreamofstream
<unordered_map> 哈希表 std::unordered_map

C++ 标准库头文件通常写成 <iostream><vector> 这种形式;自己写的头文件通常写成 "my_header.h"

1
2
#include <iostream>      // 标准库头文件
#include "my_header.h" // 当前项目里的头文件

两种写法的主要区别是搜索路径不同:

写法 常见用途
#include <...> 标准库或系统/第三方库头文件
#include "..." 当前项目自己的头文件

头文件不等于库本体。头文件通常提供声明,让编译器知道“有什么函数、类、变量”;真正的实现可能在源码文件里,也可能在已经编译好的库文件里,最后由链接阶段接上。

预处理阶段还会处理宏:

1
#define PI 3.1415926

以及条件编译:

1
2
3
#ifdef DEBUG
cout << "debug mode" << endl;
#endif

这些内容会在正式编译前先被展开、替换或裁剪。

编译

编译器把预处理后的代码翻译成汇编代码,并做语法检查、类型检查和优化。

1
g++ -S hello.cpp -o hello.s

如果少写分号、类型不匹配、函数调用不合法,通常会在这个阶段报错。

汇编

汇编器把汇编代码转换为目标文件。

1
g++ -c hello.cpp -o hello.o

目标文件里已经有机器指令,但它通常还缺少一些外部符号,比如标准库函数的最终地址。

链接

链接器把目标文件、启动代码、标准库和其他库拼起来,生成可执行程序。

1
g++ hello.o -o hello

常见的链接错误包括:

错误类型 常见原因
undefined reference 声明了函数,但没有提供实现,或链接时漏了库
multiple definition 同一个函数或全局变量被重复定义
找不到库 -l-L 参数不正确,或库没有安装

🚀 HelloWorld:C++ 版本

完整示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 引入标准输入输出头文件
#include <iostream>

// 标准命名空间,简化 std::cin/std::cout 书写
using namespace std;

// 程序入口函数:所有 C/C++ 程序从 main 开始执行,唯一入口
int main()
{
// 控制台输出文字
cout << "Hello World!" << endl;

// 返回 0,表示程序正常结束
return 0;
}

编译运行:

1
2
g++ hello.cpp -std=c++17 -Wall -Wextra -o hello
./hello

输出:

1
Hello World!

🔍 HelloWorld 逐行拆解

#include <iostream>

include 是预处理指令,用来引入头文件。

iostream 是 C++ 标准库里的输入输出头文件,里面提供了 cincoutcerrendl 等常用对象和工具。

注意:#include 不是在运行时加载模块,而是在编译前的预处理阶段让编译器看到相关声明。

using namespace std;

std 是 C++ 标准库使用的命名空间。cout 的完整名字其实是 std::coutendl 的完整名字是 std::endl

写了:

1
using namespace std;

就可以简写:

1
cout << "Hello World!" << endl;

如果不写 using namespace std;,推荐写成:

1
std::cout << "Hello World!" << std::endl;

初学时使用 using namespace std; 可以减少干扰;项目代码里更推荐显式写 std::,避免名字冲突。

int main()

main 是程序入口函数。操作系统启动程序后,C/C++ 运行时会调用 main,程序从这里开始执行。

int 表示这个函数会返回一个整数。这个整数会作为进程退出码交给操作系统。

{ }

花括号表示函数体。main 里面的语句会按顺序执行。

1
2
3
4
int main()
{
// 这里是函数体
}

cout << "Hello World!" << endl;

cout 是标准输出对象,通常对应控制台。

<< 是输出运算符,可以把右侧内容送到左侧输出流。

"Hello World!" 是字符串字面量。

endl 表示换行,并刷新输出缓冲区。日常输出换行也可以使用 '\n'

1
cout << "Hello World!\n";

return 0;

return 0; 表示 main 正常结束,并把退出码 0 返回给操作系统。

常见约定:

返回码 含义
0 程序正常结束
0 程序异常结束或表示某种错误状态

在 Linux/macOS Shell 中,可以用下面的命令查看上一个程序的返回码:

1
echo $?

在 Windows PowerShell 中,可以查看:

1
$LASTEXITCODE

🧪 把四个阶段跑一遍

用同一个 hello.cpp,可以手动观察中间产物:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 预处理:生成 hello.i
g++ -E hello.cpp -o hello.i

# 编译:生成 hello.s
g++ -S hello.cpp -o hello.s

# 汇编:生成 hello.o
g++ -c hello.cpp -o hello.o

# 链接:生成 hello
g++ hello.o -o hello

# 运行
./hello

这些文件的关系是:



flowchart TD
    A[hello.cpp 源码] --> B[hello.i 预处理结果]
    B --> C[hello.s 汇编代码]
    C --> D[hello.o 目标文件]
    D --> E[hello 可执行程序]

⚠️ 常见误区

误区 正解
C 和 C++ 是同一种语言 C++ 兼容大量 C 语法,但它们是两门语言
.cpp 文件可以直接运行 源码必须先编译链接成可执行程序
#include 会把库代码全部复制进来 更准确地说,它让编译器看到声明;真正库代码通常在链接阶段接入
using namespace std; 必须写 不是必须,也可以写 std::cout
return 0; 是给用户看的 它主要是返回给操作系统或调用者看的退出码
编译成功就一定没有问题 编译通过只代表语法和链接基本通过,不代表逻辑正确

✅ 总结

C/C++ 入门的主线是:人写源码,编译器生成目标文件,链接器生成可执行程序,操作系统加载后从 main 开始运行。

后续学习可以沿着三条线继续:语法基础、内存模型、工程构建工具。先把 g++ hello.cpp -o hello 背后的流程弄清楚,后面学指针、类、CMake 和调试器都会轻松很多。

Happy Hacking! 🎉