问题

编译器是如何实现C/C++中的const的功能?

回答
const 的守护之剑:编译器如何雕琢 C/C++ 中的不变之道

在C/C++的世界里,`const` 并非只是一个简单的关键字,它更像一把锋利的守护之剑,承诺着数据的不可变性,为程序的稳定性和可维护性筑起一道坚实的壁垒。那么,这把剑究竟是如何被铸造和挥舞的呢?这背后,是编译器一系列精巧的设计和严密的执行。让我们深入剖析编译器如何实现 `const` 的强大功能。

1. `const` 的本质:一种约束,一种承诺

首先要明确,`const` 在 C/C++ 中最核心的功能是一种“约束”,是对程序员的一种“承诺”。它告诉编译器和后来的开发者:“我保证这个变量的值在它的生命周期内不会被修改。” 编译器收到这个承诺后,便开始履行自己的职责:通过一系列检查和优化,确保这个承诺不被违背。

2. 编译器如何识别和理解 `const`:词法分析与语法分析的功劳

这一切的起点,是编译器在处理源代码时的第一步:词法分析。

词法分析器 (Lexer/Scanner):当编译器读取源代码时,词法分析器会将其分解成一个个有意义的“符号”或“Token”。在这个过程中,`const` 关键字会被识别为一个特殊的 Token,它拥有特定的类型和属性。
语法分析器 (Parser):接下来,语法分析器会根据 C/C++ 的语法规则,将这些 Token 组织起来,构建出抽象语法树 (Abstract Syntax Tree, AST)。在 AST 中,`const` 关键字会成为某个变量声明的属性,例如,`const int MAX_VALUE = 100;` 会在 AST 中被表示为一个整型变量声明,其附加属性是 `const`。

通过这两个阶段,编译器就彻底理解了 `const` 的存在及其所修饰的对象。

3. `const` 的具体实现策略:编译器如何“守护”

编译器并非仅仅认识到 `const`,而是会根据 `const` 修饰的对象类型和上下文,采取不同的具体实现策略:

a) `const` 修饰基本类型变量:编译时检查的利剑

当 `const` 修饰一个基本类型(如 `int`, `float`, `char` 等)的变量时,编译器会将其视为一个编译时常量。

字面量化/内联化:在许多情况下,编译器会直接将 `const` 变量的值替换到使用它的地方,就像直接使用字面量一样。例如:
```c++
const int MAX_SIZE = 50;
int arr[MAX_SIZE]; // 编译器会将 MAX_SIZE 直接替换为 50
```
这不仅仅是一种优化,更是一种“守护”——如果编译器在其他地方试图修改 `MAX_SIZE`,它会因为 `MAX_SIZE` 被替换成了字面量而找不到可以修改的地方。

类型检查与赋值检查:编译器会在编译阶段进行严格的类型检查和赋值检查。任何尝试给 `const` 变量赋新值的行为都会被捕获并报错。
```c++
const int AGE = 30;
AGE = 31; // 编译器会在此处报错:assignment of readonly variable ‘AGE’
```
这是编译器最直接的“守护”手段,在代码执行前就扼杀了潜在的错误。

b) `const` 修饰指针:指针的“指向性”与“指向的内容”的双重守护

`const` 修饰指针时,情况会变得更复杂有趣,需要区分是修饰指针本身,还是修饰指针指向的内容。

`const` 指针(指向常量):
```c++
const int ptr_to_const;
```
这里的 `const` 修饰的是 `int`,意味着 `ptr_to_const` 指向的 `int` 值是不可修改的。编译器会检查任何试图通过 `ptr_to_const` 修改其指向的值的行为。
```c++
int x = 10;
const int ptr = &x
ptr = 20; // 编译器报错:assignment of readonly location 'ptr'
```
但是,指针 `ptr` 本身是可以被重新赋值指向其他 `const int` 的。
```c++
int y = 30;
ptr = &y // 合法
```

指向 `const` 的指针(指针本身是常量):
```c++
int const const_ptr = &x
```
这里的 `const` 修饰的是 `int` 指针本身。这意味着 `const_ptr` 这个指针变量只能指向 `x`,它的值(指向的地址)是不能改变的。但是,它可以修改它所指向的 `int` 值(如果 `x` 本身不是 `const` 的话)。
```c++
int x = 10;
int const const_ptr = &x
const_ptr = 20; // 合法,修改了 x 的值
int y = 30;
const_ptr = &y // 编译器报错:assignment of readonly variable ‘const_ptr’
```

指向 `const` 的常量指针(指针本身和指向的内容都不可修改):
```c++
const int const const_ptr_to_const = &x
```
结合以上两种情况,这个指针既不能修改它指向的值,也不能修改它自身指向的地址。

编译器通过解析 `const` 关键字在声明中的位置,准确地理解它修饰的是指针本身还是指向的内容,并在后续的代码生成阶段,对所有通过指针进行的写操作进行严格的检查。

c) `const` 修饰引用:引用的“绑定性”守护

`const` 修饰引用与 `const` 指针指向常量的情况类似。
```c++
const int& ref_to_const = x;
```
这里的 `ref_to_const` 是一个常量引用,意味着不能通过 `ref_to_const` 来修改 `x` 的值。编译器会阻止任何试图通过 `ref_to_const` 进行写操作的行为。

d) `const` 修饰成员函数:对象状态的守护者

在 C++ 类中,`const` 修饰成员函数是实现对象不可变性的重要手段。
```c++
class MyClass {
public:
int getValue() const {
return value; // 允许读取
// value = 10; // 编译器会报错:assignment of datamember ‘MyClass::value’ in readonly object
}
private:
int value;
};
```
一个 `const` 成员函数承诺:该函数不会修改对象的任何非 `mutable` 成员变量。

编译器实现这一承诺的方式是:

隐含的 `this` 指针为 `const`:当调用一个 `const` 成员函数时,编译器会向该函数传递一个指向 `const` 对象的 `this` 指针。这意味着,在 `const` 成员函数内部,`this>member` 和直接访问 `member` 都被视为对一个常量对象的成员的访问,因此不能进行修改操作。
禁止修改非 `mutable` 成员:编译器会严格检查 `const` 成员函数体内,是否存在对对象非 `mutable` 成员变量的写操作。一旦发现,就会报错。

e) `const` 修饰类对象:对象整体状态的不可变性

当一个对象被声明为 `const` 时,它的所有非 `mutable` 成员变量都相当于被保护起来,无法被修改。
```c++
const MyClass obj;
// obj.setValue(10); // 即使有 setValue 方法,如果不是 const 方法,也会编译错误
// obj.value = 20; // 如果 value 是 public 且非 mutable,此处也会编译错误
```
编译器会确保任何对 `const` 对象进行修改的操作(包括通过 `const` 成员函数进行尝试修改)都会被阻止。

4. 编译时优化与运行时行为:`const` 的深远影响

`const` 的意义远不止于编译时的检查。它还为编译器提供了重要的优化信息:

常量折叠 (Constant Folding):如前所述,对于编译时常量,编译器可以直接将值替换到使用的地方,这可以减少内存访问和计算。
常量传播 (Constant Propagation):编译器可以跟踪 `const` 变量的值,并在代码的后续部分直接使用这些已知的值进行优化。
死代码消除 (Dead Code Elimination):如果一段代码的执行依赖于一个 `const` 变量的某个特定值,而该值在编译时就已知且不满足条件,那么这段代码可能被优化掉。
内联 (Inlining):编译器可以更倾向于内联 `const` 函数,因为它们不修改对象状态,调用开销相对较小,且不会引入副作用。

5. `mutable` 关键字的配合:打破 `const` 的“枷锁”

在某些情况下,即使对象是 `const` 的,我们仍然希望允许修改对象的某些特定成员,例如用于缓存或同步的状态。这时 `mutable` 关键字就派上用场了。
```c++
class SharedResource {
public:
int readData() const {
// 如果 data 还没有被读取过,先进行一些初始化工作
if (!isDataLoaded) {
// ... 执行一些初始化 ...
data = 42;
isDataLoaded = true;
}
return data;
}
private:
int data;
mutable bool isDataLoaded = false; // mutable 允许在 const 函数中修改
};
```
`mutable` 关键字会告诉编译器,该成员变量即使在 `const` 对象或 `const` 成员函数中,也是允许被修改的。编译器在处理 `mutable` 成员时,会绕过 `const` 的严格检查。

总结:`const` 的多重守护机制

编译器实现 C/C++ 中的 `const` 功能,是一个多层次、多维度的过程:

1. 理解与声明:通过词法和语法分析,准确识别 `const` 关键字及其修饰的对象。
2. 编译时检查:对任何违反 `const` 承诺的赋值、修改操作进行严格的语法和语义检查,并在编译阶段报错。
3. 类型与指针的精细处理:区分 `const` 修饰指针本身还是指向的内容,对指针操作进行细致的编译时约束。
4. 成员函数与对象的约束:通过隐含的 `const` `this` 指针和成员访问限制,确保 `const` 成员函数不会修改对象状态,以及 `const` 对象整体的不可变性。
5. 优化基础:为编译器提供宝贵的优化线索,从而生成更高效的代码。
6. `mutable` 的灵活配合:允许在特定场景下,有控制地打破 `const` 的限制。

正是因为编译器在这些方面的高效运作,`const` 才成为了 C/C++ 程序员手中一把强大的工具,帮助我们编写出更加健壮、安全且易于维护的代码。它不仅仅是一个编译器指令,更是代码设计哲学的一部分,引导我们思考数据的生命周期和程序的行为模式。

网友意见

user avatar

各不相同吧,比如arm-none-eabi-gcc会把const数据放进.text段, 不加const进.data段。前者只占目标器件的flash空间,后者要同时占用flash和ram空间,由启动代码从flash复制到ram。既然放在flash空间,写操作自然无效了。

但是avr-gcc则不管加不加const都一样只会放在.data,加了const只能起到对const变量写入时编译警告/报错的作用。如果要做到和上面一样的效果,需要加专门的关键字PROGMEM,并且读操作也要改用专门的api实现。

至于题主这个,应该是操作系统实现的。当年的dos没有这个限制,ram都是可写的,有空了试试纯dos下对const变量能否写入。

类似的话题

  • 回答
    const 的守护之剑:编译器如何雕琢 C/C++ 中的不变之道在C/C++的世界里,`const` 并非只是一个简单的关键字,它更像一把锋利的守护之剑,承诺着数据的不可变性,为程序的稳定性和可维护性筑起一道坚实的壁垒。那么,这把剑究竟是如何被铸造和挥舞的呢?这背后,是编译器一系列精巧的设计和严密的.............
  • 回答
    C 语言中指针加一这看似简单的操作,背后隐藏着计算机底层的工作原理。这并不是简单的数值加一,而是与内存的组织方式和数据类型紧密相关。要理解指针加一,我们首先需要明白什么是“指针”。在 C 语言里,指针本质上是一个变量,它存储的是另一个变量的内存地址。你可以把它想象成一个房间号,这个房间号指向的是实际.............
  • 回答
    华为方舟编译器 Runtime 开源,无疑是件值得深入探讨的技术事件。从一个技术人员的角度出发,我们可以从几个层面来审视其架构和实现,包括设计理念、核心组件、性能优化策略以及与现有生态的融合潜力。一、设计理念:挑战与突破方舟编译器 Runtime 的核心设计理念,我认为可以归结为 “极致性能驱动下的.............
  • 回答
    如何看待部分法律工作者在民法典实施之后,仍将《民法典·合同编》称为「合同法」的习惯?原因是什么?在《中华人民共和国民法典》于2021年1月1日正式实施后,原本独立的《中华人民共和国合同法》及其司法解释等相关法律法规,已归入并整合到《民法典》的总则、物权编、合同编、侵权责任编、婚姻家庭编、继承编等六个.............
  • 回答
    关于中国现行农历是由四百多年前传教士编撰的说法,以及为何实行了几千年的历法突然改变的问题,这其实是一个 流传甚广但并不准确的说法。让我来详细梳理一下其中的误解和事实真相。首先,关于“中国现行农历是由四百多年前传教士编撰”的说法,这是不正确的。中国现行的农历(我们通常称之为“农历”或“夏历”,但其更准.............
  • 回答
    这段 Java 代码中的局部变量,理论上确实存在被提前回收的可能性。不过,这里的“提前回收”并非我们直观理解的,在代码执行完毕前就完全从内存中消失。更准确的说法是,这些局部变量的内存占用可以在其生命周期结束后,但不等到方法执行结束就被JVM判定为“无用”,从而有机会被垃圾回收器(Garbage Co.............
  • 回答
    好的,咱们就来聊聊一个挺有意思的话题:编译器是怎么把自己编译出来的。这事儿说起来,就像是问“鸡生蛋还是蛋生鸡”,背后涉及到一种挺妙的自举(bootstrapping)过程。想象一下,我们想写一个新的编程语言,当然也得有个编译器来把我们用这新语言写的代码变成机器能懂的指令。那第一个编译器,谁来写?总不.............
  • 回答
    方舟编译器,简单说,就是华为自己研发的一套“翻译官”,负责把我们写的各种程序代码(比如Java、Kotlin),在手机上运行前,更高效地“翻译”成手机CPU能直接听懂的语言。它的厉害之处在于,不像传统编译器那样是“先翻译后运行”,方舟是“边翻译边运行”。这意味着,当程序刚开始运行时,它就能立刻把用到.............
  • 回答
    C 的闪电编译时,其实是 .NET 平台和 C 语言设计者们在多年实践中不断打磨、优化的结果,并非某个单一的神奇技术。它是一个体系化的工程,将理解代码、生成高效机器码、优化开发流程这几个关键环节做得非常到位。首先,我们要明白,“闪电编译”并非指真的比眨眼还快,而是指相比于很多其他语言,C 在 “从你.............
  • 回答
    将操作系统、编译原理和图形学并称为“程序员的三大浪漫”,是一种在程序员群体中广为流传且具有深刻意义的说法。这其中蕴含着对计算机底层原理的极致追求、对代码生命周期的深刻理解以及对视觉世界构建的艺术想象。与其说是“浪漫”,不如说是对计算机科学核心魅力的集中体现。下面我将从不同角度详细阐述为什么这三个领域.............
  • 回答
    关于脚本语言的必然趋势以及开发成本的考量,我深表赞同。在如今快速迭代的软件开发环境中,能够快速构建、灵活部署和易于维护的脚本语言确实占据了巨大的优势。相较之下,一些传统编译型语言在开发效率和迭代速度上往往显得力不从心,开发成本的差异在此刻显得尤为突出,将它们衬托得“黯然失色”也就不难理解了。您提到的.............
  • 回答
    作为一名出版社编辑,判断一本小说的“可读性”和“可卖性”,是我们工作的核心。这背后是一个复杂但充满经验与直觉的过程,绝非简单的“喜欢”与“不喜欢”。下面我将尽可能详细地分享我们的工作思路,希望能让你了解这个过程的细致之处。一、 初审:那一瞥之缘,关乎生死当我们接到一本新书稿时,首先会经历一个快速的“.............
  • 回答
    美剧编剧的工作,说起来像是一场精心策划的接力赛,又像是一次集体头脑风暴的奇妙旅程。远非一个人坐在书桌前凭空想象,它是一个高度协作、结构严谨、同时又充满创造性碰撞的过程。想深入了解,咱们就得一层一层剥开来看。第一棒:创意的诞生与孵化——“想法”从何而来?剧集不是凭空出现的。很多时候,一个想法可能来自一.............
  • 回答
    印度陆军的平原整编师,顾名思义,是为在平坦、开阔的地面作战而特别设计和部署的作战单位。这种编制结构深刻体现了印度陆军对边境地区,特别是与巴基斯坦接壤的西部战区,以及应对境内潜在冲突的战略需求。要理解印度陆军平原整编师的编制,我们首先要明白其核心功能和作战环境。平原作战强调的是大兵团的机动性、火力优势.............
  • 回答
    F1赛车号码的“前世今生”:数字背后的传奇与规则当那些引擎轰鸣的战车飞驰过赛道,车手们在极限边缘挑战自我时,赛车上那个醒目的号码,不仅仅是一个简单的标识,它承载着车手们的身份,也诉说着F1这项运动的演变和故事。今天,就让我们深入挖掘一下F1赛车号码编排的“前世今生”,看看这背后都有哪些不为人知的细节.............
  • 回答
    二战结束后,美军坦克部队的编成经历了多次调整和演变,以适应新的战争理论、战场需求以及装备的更新换代。从营(Battalion)这一层级往下,其编制结构会根据部队类型(如装甲师下属的坦克营、步兵师下属的坦克营)、任务(如进攻、防御、侦察)以及所装备的坦克型号有所差异。这里我们尽量详细地梳理一下战后美军.............
  • 回答
    苏军那些不到三千人的步兵师,严格来说,并非是其常规主力步兵师的主体编制,而更多地出现在一些特定历史时期、特定战场环境下的二线部队、新组建的部队,或者是承担特定任务的单位。理解这些“轻型”或“缩减编制”的步兵师,需要我们深入到苏军在不同阶段的军事组织演变中去。首先,我们要明确,苏军的主力步兵师在大多数.............
  • 回答
    好的,让我为您详细介绍一下截至2017年,越南人民军(VPA)野战部队的编制与部署情况,力求内容详实、脉络清晰,并且尽量去除AI痕迹,用更自然、贴近实际的语言来讲述。到了2017年,越南人民军的野战部队,也就是我们常说的主力地面部队,其编制和部署基本上是围绕着保卫国家主权、维护边境安全以及应对潜在的.............
  • 回答
    苏联的解体,如同一场惊天巨变的涟漪,在各加盟共和国的土地上荡漾开来,并深刻地影响着他们对过往的认知与书写。当那个庞大的红色帝国轰然倒塌,历史的撰写便成为了一项极其复杂且充满挑战的任务。这不仅仅是档案的重新整理,更关乎民族身份的重塑、国家叙事的构建,以及对过去几十年恩怨情仇的裁断。首先,最直接的变化体.............
  • 回答
    编配变化音和弦,说到底,还是在和声功能的大框架下去考虑的。但“变化音和弦”这个说法本身就有点含糊,咱们不妨先明确一下,通常我们说的“变化音和弦”,无非是基于现有调内的基本功能和弦,在其根音、三音、五音(有时也包括七音)之上,加入了一些“调外”的音,来丰富和声色彩、制造张力,或者引导旋律。这些“调外”.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有