跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 内存布局、编译流程与关键字链接性

介绍 C++ 程序的内存布局结构,包括栈、堆、代码段等区域及其增长方向。阐述了链接脚本在定义内存映射、地址分配及符号生成中的作用,对比了 Linux 与 ARMv7-M 架构的差异。详细解析了 C++ 编译流程的四个阶段:预处理、编译、汇编和链接,并列出各阶段输入输出及可读性。重点讲解了全局变量、类内成员及函数的链接性规则,涵盖 static、inline、constexpr、extern 等关键字的使用场景与区别,以及 decltype 和 volatile 的作用机制。

HadoopMan发布于 2026/3/29更新于 2026/9/355 浏览
C++ 内存布局、编译流程与关键字链接性

1. 内存布局

1.1 Linux C++ 内存布局示意

高地址 +---------------------------+
| 栈 (Stack) | ← 局部变量、函数参数、返回地址 |
↓ 增长 |
(向下增长)
+---------------------------+
|
|
空闲区域 |
|
+---------------------------+
| 堆 (Heap) | ← 动态分配 (new/malloc) |
↑ 增长 |
(向上增长)
+---------------------------+
| 内存映射段 (mmap) | ← 共享库、动态映射、大分配
+---------------------------+
| BSS 段 | ← 未初始化的全局/静态变量
+---------------------------+
| 数据段 (.data) | ← 已初始化的全局/静态变量 (可读写)
+---------------------------+
| 只读数据段 (.rodata) | ← 常量、字符串字面量 (只读)
+---------------------------+
| 代码段 (.text) | ← 程序指令 (只读 + 可执行)
+---------------------------+
低地址

1.2 内存布局由谁规定

链接脚本的作用:

  • 内存映射:将程序逻辑段(代码/数据)映射到物理内存(FLASH/RAM)
  • 地址分配:精确控制各段在内存中的位置和布局
  • 符号定义:生成关键地址符号供启动文件和 C 代码使用
  • 优化控制:决定哪些段保留/丢弃,影响最终固件大小

一个 ARMv7-M 架构的硬件的链接脚本如下,其内存布局与 Linux C++ 不同:

/* ========================================================================
 * 1. 程序入口点
 * ======================================================================== */
ENTRY(Reset_Handler)

/* ========================================================================
 * 2. 定义变量栈顶 _estack - 从 RAM 顶部开始向下生长
 * 计算:0x20000000 + 32KB = 0x20008000
 * ======================================================================== */
_estack = ORIGIN(RAM) + LENGTH(RAM);

/* ========================================================================
 * 3. 物理内存定义 - STM32G431RBT6 固定配置
 * ======================================================================== */
MEMORY {
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* 程序存储区 */
  RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K /* 数据运行区 */
}

/* ========================================================================
 * 4. 内存段布局,定义段映射规则
 * 每个 { ... } 块是一个输出段
 * 书写顺序决定了不同段在内存中的物理排列顺序
 * VMA(Virtual Memory Address):程序运行时该段所在的地址
 * LMA(Load Memory Address):该段在存储介质中的地址,AT 用于显示指定 LMA
 * . 是当前位置计数器(location counter)
 * 它的值是 当前输出段的 VMA,即程序运行时该位置的地址
 * ======================================================================== */
SECTIONS {
  /* --------------------------------------------------------------
   * 4.1 中断向量表 - 位于 FLASH 起始地址:0x08000000
   * Cortex-M4 硬件要求:前 8 字节 = [栈顶,复位地址]
   * -------------------------------------------------------------- */
  .isr_vector : {
    . = ALIGN();
    KEEP(*(.isr_vector)) 
    KEEP(*(.vectors)) 
    . = ALIGN();
  } > FLASH 

  
  .text : {
    . = ALIGN();
    *(.text*) 
    *(.glue_7) 
    *(.glue_7t)
    *(.eh_frame) 
    . = ALIGN();
    _etext = .; 
  } > FLASH 

  
  .rodata : {
    . = ALIGN();
    *(.rodata*)
    *(.rodata.*)
    . = ALIGN();
  } > FLASH 

  
  _sidata = LOADADDR(.data);

  
  .data : {
    . = ALIGN();
    _sdata = .; 
    *(.data*)
    *(.data.*)
    . = ALIGN();
    _edata = .; 
  } > RAM AT > FLASH 
  . = ALIGN();

  
  .bss : {
    _sbss = .;
    *(.bss*)
    *(.bss.*)
    *(COMMON) 
    . = ALIGN();
    _ebss = .;
  } > RAM 

  
  .ARM.attributes  : { *(.ARM.attributes) }

  
  /DISCARD/ : {
    *(.ARM.exidx*) 
    *(.ARM.extab*) 
    *(.init) 
    *(.fini)
    *(.preinit_array*) 
    *(.init_array*) 
    *(.fini_array*) 
    *(.note.gnu.build-id) 
  }
}
4
/* 保留向量表不被优化,KEEP(...): 强制保留括号内的段,即使在链接优化时也不会被丢弃 */
/* 兼容不同命名 */
4
/* VMA 和 LMA 相同,都在 FLASH 起始地址 */
/* -------------------------------------------------------------- * 4.2 代码段 - 所有函数机器码(main/port/tasks 等代码) * -------------------------------------------------------------- */
4
/* 所有代码段 */
/* Thumb/ARM 胶合代码(保留兼容性)*/
/* 异常帧 */
4
/* 定义变量 _etext 为代码段 VMA 结束地址(供启动文件使用)*/
/* VMA 和 LMA 相同,都在 FLASH .isr_vector 段后面 */
/* -------------------------------------------------------------- * 4.3 只读数据 - 常量、字符串字面量等 * -------------------------------------------------------------- */
4
4
/* VMA 和 LMA 相同,都在 FLASH .text 段后面 */
/* 定义 _sidata 为.data 段初始值在 FLASH 中的起始地址(LMA)*/
/* -------------------------------------------------------------- * 4.4 已初始化数据 - 如:int x = 5; * 运行时在 RAM,初始值从 Flash 复制 * -------------------------------------------------------------- */
4
/* 定义 .data 段在 RAM 中起始地址 */
4
/* 定义 .data 段在 RAM 中结束地址 */
/* LMA=Flash, VMA=RAM */
4
/* -------------------------------------------------------------- * 4.5 未初始化数据 - 如:int y; * 启动时由启动文件清零 .bss 段 * -------------------------------------------------------------- */
/* 传统未初始化全局变量 */
4
/* LMA=VMA=RAM */
/* -------------------------------------------------------------- * ABI 属性段 - 必须保留,否则链接器警告 * 包含 Thumb 指令集等 ABI 信息 * -------------------------------------------------------------- */
0
/* -------------------------------------------------------------- * 丢弃无用段 * -------------------------------------------------------------- */
/* C++ 异常索引 */
/* C++ 异常表 */
/* C 库初始化(直接跳 main)*/
/* 全局构造函数(C++)*/
/* 全局构造函数 */
/* 全局析构函数 */
/* 构建 ID(调试用,生产可移除)*/

由该链接脚本的内容可知,链接脚本的作用就是规定程序的内存布局,并提供符号给启动文件以及其他目标文件,以便符合特定硬件的行为。

2. 程序编译流程

2.1 预处理

作用:

  • 处理以 # 开头的预处理指令
  • 展开宏定义
  • 处理条件编译
  • 插入头文件内容
  • 删除注释
  • 添加行号和文件名标识

生成文件:

  • .i 文件:预处理后的源代码(仍是文本文件)

2.2 编译(生成 .s)

作用:

  • 词法分析:将代码拆分成 token
  • 语法分析:构建抽象语法树 (AST)
  • 语义分析:类型检查、符号解析
  • 代码优化:优化中间代码
  • 生成汇编代码:输出目标平台的汇编代码

生成文件:

  • .s 文件:汇编代码(文本文件,可读)

2.3 汇编(生成 .o)

作用:

  • 将汇编代码转换为机器指令
  • 生成目标文件(二进制文件)
  • 创建符号表(记录函数和变量)
  • 生成重定位表(为链接做准备)

生成文件:

  • .o 文件

2.4 链接

作用:

  • 符号解析:解析未定义的符号引用
  • 重定位:修正地址引用
  • 合并段:合并相同类型的段
  • 库处理:链接静态库或动态库

生成文件:

  • 可执行文件:Linux 无后缀或 .out,Windows .exe

2.5 总结

阶段输入输出文件类型可读性
预处理.c/.cpp.i文本✅ 可读
编译.i.s文本✅ 可读
汇编.s.o二进制❌ 不可读
链接.o + 库可执行文件二进制❌ 不可读

3. 关键字及其链接性

3.1 全局变量

全局变量定义链接性注释
int x = 0外其他 cpp 若要访问需先声明 extern int x。⚠️ 不推荐:若放在头文件中会导致链接重定义错误
inline int x = 0外✅ 允许重定义(C++17 起),适合放在头文件中
static int x = 0内其他 cpp 无法访问。⚠️ 注意:放在 h 中则每个包含它的编译单元都会生成一个独立副本
const int x = 0内其他 cpp 无法访问。C++ 中全局 const 默认内部链接,C 语言是外部
constexpr int x = 0内其他 cpp 无法访问。隐含 inline 但默认内部链接,除非显式 extern
extern const int x = 0外其他 cpp 若要访问需先声明 extern const int x。通常定义在 .cpp 文件中
inline const int x = 0外✅ 允许重定义,适合放在头文件中
inline constexpr int x = 0外✅ 允许重定义,编译期常量。推荐用于头文件中的全局常量(适用于 int、char 等字面类型)

3.2 类内成员

有 inline / constexpr 就必须有 static inline(static) : 确保外部链接性,全局唯一实例

  • 允许 static 直接在类内定义
  • 加 const / constexpr 后确保为编译期常量
类内成员声明链接性注释
int x = 0无非静态成员。允许类内初始化 (C++11)。⚠️ 只是个初始化建议,实际内存分配在对象实例化时,每个对象一份副本。
static int x外静态成员声明。不是定义。需在某个 .cpp 中定义 int Class::x;,防止头文件多次包含导致重定义。
inline static int x = 0外C++17 起。是完整的定义。可直接放在头文件中,链接器会合并多个编译单元的副本。
const int x无非静态常量成员。每个对象一份副本。⚠️ 必须在构造函数初始化列表中赋值 (规定在创建时初始化)。
static const int x = 0内整型/枚举特例。是编译时常量,视为完整定义。⚠️ C++17 前若取地址 (ODR-use),仍需外部定义。
static const int x外声明。需在 .cpp 中定义 const int Class::x = val;。同 static int x。
static const double x外非整型静态常量。C++17 前类内不能初始化。需在 .cpp 中定义 const double Class::x = val;。
inline static const int x = 0外C++17 起。是完整的定义,编译时常量。✅ 适用于 double, char*, string_view 等所有类型,推荐放头文件。
static constexpr int x = 0外隐含 inline static。是完整的定义,编译期常量。✅ 推荐用于头文件中的静态常量 (适用于字面类型)。
extern const int x = 0外⚠️ 注意:extern 通常用于全局变量,不用于类成员。类外定义静态成员时不需要写 extern (直接写 const int Class::x = 0;)。
  1. C++17 inline static:
    • 在 C++17 之前,静态成员变量必须在 .cpp 文件中定义一次,否则链接报错。
    • C++17 引入 inline static 后,允许在头文件的类定义中直接初始化静态成员,极大简化了代码。
  2. constexpr 隐含 inline:
    • 类内的 static constexpr 成员默认就是 inline 的,不需要显式写 inline。
    • 它是编译期常量,适合用于数组大小、模板参数等场景。
  3. 非静态 const 成员:
    • 即使是 const int x,如果是非静态的,每个对象实例都有自己的一份内存。
    • 必须在构造函数的初始化列表中初始化,不能在类内直接 = 0 (除非是静态整型常量)。

3.3 函数

函数修饰符链接性注释
noexcept不变异常规范。表示该函数承诺不会抛出异常。✅ 若抛出则调用 std::terminate。✅ 有助于编译器优化和重载决议。
inline外允许重定义(ODR 例外)。适合放在头文件中。✅ 类内定义的成员函数自动隐式 inline。⚠️ 只是建议编译器内联,实际由编译器决定。
static内内部链接性(仅限自由函数)。⚠️ 仅当前翻译单元可见,其他 cpp 无法访问。💡 现代 C++ 推荐用 匿名命名空间 代替。
constexpr外编译期求值。若输入参数是常量表达式,则结果在编译期计算。✅ 隐含 inline。✅ C++14/17 后允许更复杂的函数体。
类内函数修饰符链接性/特性注释
= delete无显式删除函数。禁止使用某些构造函数、操作符或成员函数。✅ 常用于禁止拷贝 (MyClass(const MyClass&) = delete;)。✅ 比私有化更明确,报错更早。
= default无显式默认。要求编译器生成默认实现。✅ 常用于恢复被用户定义构造函数抑制的默认构造函数。✅ 比手写默认实现更高效且语义清晰。
explicit无显式转换。防止隐式类型转换。✅ 主要用于单参数构造函数。✅ C++11 起也可用于转换运算符 (explicit operator bool())。
static类作用域静态成员函数。属于类而非对象实例。✅ 无 this 指针,只能访问静态成员。⚠️ 链接性取决于定义位置(类内定义隐含 inline,类外定义通常外部链接)。
const无常量成员函数。承诺不修改对象状态。✅ 只能访问 const 成员变量。✅ mutable 成员变量可被修改。✅ 常对象只能调用 const 成员函数。
constexpr外 (隐含 inline)编译期求值。C++11/14/17 非静态成员函数隐含 const。✅ C++20 起不再隐含 const。✅ 参数和返回类型必须是字面量类型。✅ 构造函数只能用初始化列表 (C++11 函数体需为空)。
override无显式重写。明确该函数是对父类虚函数的重写。✅ 编译器检查:若父类无对应虚函数则报错。✅ 增强代码可维护性,防止签名不匹配。

3.4 static

  • 静态局部变量:在函数中为静态局部变量,只会在第一次进入函数时初始化一次,生命周期延长至整个程序运行结束。
  • 静态全局变量和静态函数:内部链接性。
  • 类中的静态成员变量和静态成员函数:外部链接性。静态成员变量属于整个类,而不是类的对象,必须在类外定义和初始化 (static const int x = 0 和 static constexpr int x = 0 需在类内初始化,类外定义,而 inline static constexpr int x = 0 可以直接类内初始化并定义)。静态成员函数只能访问静态成员变量和其他静态成员函数。

3.5 decltype

用于在编译时推导表达式的类型。

如果 () 中是一个任意其他表达式(如括号表达式、运算表达式等)

  • 如果表达式是左值,decltype 返回左值引用 T&
  • 如果表达式是纯右值(prvalue),返回非引用类型 T
  • 如果表达式是将亡值(xvalue),返回右值引用 T&
int x = 42;
deduce(x) y = x; // 推导出 y 的类型为 int
int x = 42;
deduce(x) a; // a 的类型为 int
deduce((x)) b = x; // b 的类型为 int&,因为 (x) 是一个左值 

3.6 volatile

告诉编译器:

  • 不要优化对该变量的读写操作
  • 每次访问都必须从内存中重新读取,不能使用缓存的值
  • 保持操作顺序,不重排与其他 volatile 变量的操作

目录

  1. 1. 内存布局
  2. 1.1 Linux C++ 内存布局示意
  3. 1.2 内存布局由谁规定
  4. 2. 程序编译流程
  5. 2.1 预处理
  6. 2.2 编译(生成 .s)
  7. 2.3 汇编(生成 .o)
  8. 2.4 链接
  9. 2.5 总结
  10. 3. 关键字及其链接性
  11. 3.1 全局变量
  12. 3.2 类内成员
  13. 3.3 函数
  14. 3.4 static
  15. 3.5 decltype
  16. 3.6 volatile

更多推荐文章

查看全部
  • GitHub 日榜精选:AI 代理与开发工具趋势 (2026-02-24)
  • 解决 VS Code 远程连接服务器后 GitHub Copilot 无法使用问题
  • C语言Web开发:CGI、FastCGI与Nginx模块实战
  • Spring AI 实战:基于 Ollama 构建离线私有化 AI 服务方案
  • 黑客技术零基础入门学习指南
  • OpenBCI + OpenViBE + Python: 实时运动想象小球控制系统
  • Python 网页数据爬取实战教程
  • 前端开发:善用组件库,拒绝重复造轮子
  • 注意力机制与 Transformer 模型实战指南
  • Mac mini M4 部署 OpenClaw + Ollama 本地大模型接入飞书机器人
  • ESP32-S31 发布:高性能多协议双核 RISC-V 芯片,支持 AI 交互
  • OpenClaw Web Search 工具配置与渠道选择指南
  • LLaMA 大模型本地化稳定部署指南:基于 Ollama 与 NextChat
  • PyTorch 训练技巧与模型微调指南
  • FaceFusion 支持 VR 全景视频人脸替换
  • C++中%取余运算符与模运算的区别
  • Seedream 4.0 企业级图像生成模型能力与应用场景分析
  • Stable Diffusion 视觉提示词注入攻击原理与实现
  • Arthas 实战:结合 IDEA 的 Java 线上排查流程
  • Meta-Llama-3-8B-Instruct 本地部署与 vLLM 加速实战

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online