在C语言中,const关键字的内部工作原理是怎样的?

13
我想了解C和C++中的const内部机制。编译器是如何实施“常量性”的?

5
你是对C还是C++特别感兴趣,因为它们之间可能有所不同。例如,在C++中标记一个方法为const在C中没有相应的结构。 - Skurmedel
有人能帮忙解释一下编译器如何创建常量吗?我只想了解其内部原理,而不是在C或C++中如何使用const。 - mousey
1
(你甚至可以得到积分) - Adam
6个回答

26

一般来说,const 完全由编译器控制。 当您声明某个内容为常量时,编译器 会对您的写作进行限制。它不会允许您对常量标量进行赋值,通过常量引用或指针进行赋值,或调用常量对象的非常量函数。

并不能保证编译器会提供任何形式的运行时保护。


3
很好的回答。对于任何想进一步阅读的人,我建议查看C++ FQA关于const正确性的部分:http://yosefk.com/c++fqa/const.html - Eli Courtwright
4
@Eli 怎么会有人投票支持FQA链接... 最好阅读一下有关常量正确性的FAQ:http://www.parashift.com/c++-faq-lite/const-correctness.html。少些抱怨,多些实质内容。 - Johannes Schaub - litb
2
@Johannes:实际上,我不同意FQA关于C++的基本结论,并且我很高兴在我的日常工作中使用C++,但我认为FQA可以提供深刻的见解和引人思考的内容,阅读它有助于我更好地理解编写C++时遇到的一些问题,比FAQ更好。此外,每个问题中的FQA直接链接到FAQ,因此很容易看到原始答案。我可能应该添加一个声明,即FQA可能不是新手程序员的好资源,他们应该坚持使用FAQ。 - Eli Courtwright

11

const关键字在C和C++中有两种不同的语义含义。

(1) 它可以声明一个对象的常量性。

const SomeType t;

在上述情况中,对象 t 是一个不可修改的对象。编译器会遵循“const-correctness”规则(在C和C++中不同),尽力防止您修改它。这些规则仅在语言层面上概念性地强制执行,这意味着有方法可以规避这些规则,并且这也意味着对象的const属性不一定实现在物理层面上。即没有保证该对象最终将被放置在只读存储器中。
需要注意的是,此类const属性在某种意义上是“不可移除的”。任何试图通过删除const属性来修改该对象的尝试都会导致未定义的行为(在C++中除了可能存在的mutable成员)。
它还可以声明对对象的访问路径的const属性。
const SomeType *p;

上述的p被声明为指向常量的指针。这并不一定意味着p所指向的对象是一个常量对象(由第一种const定义)。它很可能是一个非常量对象,在这种情况下,从上述访问路径中去除constness并修改对象是完全合法的,尽管通常不是一个好的编程实践。换句话说,访问路径的constness是可以被移除的。
考虑到上述内容,以下声明:
const int* const* const* const p = 0;

包含两种不同的const:最后一个const声明了对象p的常量性(第一种),而其余的const声明了由p表示的访问路径的各个级别的常量性(第二种)。

P.S. 作为[可能无关的]附带说明,值得注意的是,在C和C++中,术语constant具有截然不同的含义。在C++中,constants是声明为const的对象。在C中,constants是字面值。在C的术语中,声明为const的对象不是constants


我模糊地记得很久以前读过一些关于C和C++内置整数类型之间的区别的信息,就像在C++中一样(不同于C),const可以定义一个编译时常量而不一定是一个变量。我记不清足够的细节来提供一个完美正确的答案,但如果我没记错的话,也许这些信息可以被添加到这里 :) - Maciej Hehl
@Maciej H:是的,我在这个问题上回答了https://dev59.com/THE95IYBdhLWcg3wb9hb#2308364,但我不想在这里深入讨论这个问题。 - AnT stands with Russia
1
在尝试理解像 const int* const* const* const p = 0; 这样的声明时,这个“规则”很有用 - 基本上,“const”应用于其左侧的任何内容(除非左侧没有任何内容,在这种情况下它将应用于其右侧的任何内容) - Lazer
@Lazer 所以,本质上可以写成 int const *const *const *const p = 0; - Mateen Ulhaq

5
除了编译时强制不可变性所提供的 const 关键字,其他回答中已经提到,有时使用它可以让编译器将这些数据放置在二进制和内存的只读区域中。根据 Ulrich Drepper 的文章《如何编写共享库》中的第2.4.2节“永远 const”,这样做可能允许程序(1)使用更少的资源和(2)更快地启动。
请注意,在这种只读内存区域中去除数据的const属性会导致未定义的行为,与通常情况下一样。

2

编译器在编译代码时会计算每个表达式的类型,以便对其进行类型检查并正确地生成代码(例如,在尝试将int存储在指针中时发出警告,或正确地将整数转换为双精度浮点数)。const可以被视为类型的一部分。由于它具有表达式的类型信息,因此它可以检查lvalue(赋值操作符左侧)的类型,并在其类型中包含“const”时抛出错误。


1

在优化编译器中,这实际上是一件非常复杂的事情。不过,它起初很简单。

当您使用const关键字声明变量时(让我们忽略指针,因为它们可以是const或指向const或两者都是),编译器会记住不应更改该变量的任何代码(几乎)。如果编译器看到更改const变量的代码,则认为这是一个错误(或仅值得警告,但为了简单起见,我将忽略它)。编译器还假定它看不到的任何代码(现在或以后{其他.c文件中的代码或可能是库或.s或.asm文件)都不会更改const变量(除非它是const volatile,在这种情况下,它将假定它随时可能发生更改,但仍将强制不允许您的代码更改它--这对于用于读取设备状态但无法写入的内存映射SFR [特殊功能寄存器]非常有用。请记住,c和c ++用于操作系统和嵌入式编程)。

假设某些或所有情况下变量不会改变,允许编译器的优化例程执行它本来无法执行的操作。这意味着可以将变量的文字值放入指令流中,而不是加载变量的地址和值。它还可以假设如果加载了该值,则:
extern const int foo; // note that the value isn't visible, so a load is necessary
...
extern int baz(int, int);
...
int bar(int x, int y) {
   int m, n;
   int r = x / foo; // this would require loading the value of foo from RAM
   m = baz(r, y); // the compiler normally has to assume that a function could change a global
   n = m + r + foo; // but since the global foo is const it shouldn't be able to be changed
                    // by the call to baz and does not need to be reloaded
   return n;
}

一个可执行文件(或其他目标文件)可能有一个只包含常量的节(.rodata)。在许多情况下,操作系统可能会强制不允许程序写入此数据(在某些情况下甚至可能在ROM中)。该节还可以包含非const变量的版本,用于初始化,因为它可以包含任何常量,而不仅仅是声明为const的常量。

因此,在C语言中,const主要告诉编译器告诉你,你已经犯了错误,并且正在尝试更改不应更改的东西。虽然它允许基于它做出一些假设。

在C++中,它变得更加复杂。我不记得所有细节,但我确实记得你可以根据传递给它的值是否为const来重载函数名,这可能很有用。


1

在C语言中,const关键字会使变量成为不可变的,也就是说它不能被修改。

通常情况下,这是一个编译时的区别,在运行时对变量进行修改没有任何影响。例如,可以使用指向相同内存地址的指针间接地修改const变量。

在C++中,const关键字有多重含义。

例如,类成员函数可以被声明为“const”,这意味着它不允许修改类实例的状态。

与C语言类似,使用指针、"mutable"关键字或const_cast<>操作符也可以间接地修改声明为const的变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接