const关键字在C中是如何工作的?

nwlqm0z1  于 2023-10-16  发布在  其他
关注(0)|答案(6)|浏览(127)

我想知道const在C和C++中的内部结构。编译器如何强制 “恒定性”

jvidinwx

jvidinwx1#

一般来说,const是100%编译器。当你声明一个常量时,编译器会对你写的东西设置限制。它不允许你赋值给常量标量,通过常量引用或指针赋值,或者调用常量对象的非常量函数。
不能保证编译器会安排任何类型的运行时保护。

dy2hfwbg

dy2hfwbg2#

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

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

const SomeType t;

在上面的例子中,对象t是一个不可修改的对象。编译器将通过遵守 const-correctness 规则(这在C和C中是不一样的)来尽最大努力防止您修改它。const-correctness规则只是在概念上,在语言级别上强制执行,这意味着有办法规避这些规则,这也意味着对象的constness不一定在物理级别上实现。也就是说,不能保证对象最终会被放置在只读存储器中。
重要的是要注意,这种constance是 * 不可移除的 *,在某种意义上,任何试图通过抛弃constance来修改上述对象的尝试都会导致未定义的行为(排除C
中可能的mutable成员)。

**(2)**它可以声明到对象的 * 访问路径 * 的constance

const SomeType *p;

上面的p被声明为指向常量的指针。这并不一定意味着p所指向的对象是常量对象(如上面第一种const所定义的)。它可能很容易是一个非常量,在这种情况下,从上述访问路径中丢弃常量并修改对象是 * 完全法律的 *,尽管这通常不是一个好的编程实践。换句话说,访问路径的恒定性可能是 * 可移除的 *。
考虑到上述情况,

const int* const* const* const p = 0;

包括两种不同类型的const:最后一个const声明了对象p(第一类)的恒定性,而const的其余部分声明了由p(第二类)表示的各级访问路径的恒定性。
P.S.作为一个[可能不相关的]旁注,可能值得注意的是,术语 constant 在C和C中有着截然不同的含义。在C中,* 常量 * 是声明为const的对象。在C * 中,常量 * 是字面量。声明为const的对象在C术语中不是 * 常量 *。

t5zmwmid

t5zmwmid3#

除了通过使用const关键字提供的编译时强制不变性之外,使用它有时允许编译器将这些数据放在二进制文件和内存的 read-only 部分。根据Ulrich Drepper的文章How To Write Shared Libraries中的第2.4.2节“Forever const“,这样做可能会使程序(1)使用更少的资源和(2)启动更快。
请注意,像往常一样,在内存的只读区域中丢弃数据的const属性会导致未定义的行为。

nr7wwzry

nr7wwzry4#

当编译器编译代码时,它会计算每个表达式的类型,这样它就可以对它们进行类型检查并正确地发出代码(例如,当你试图在指针中存储一个int时发出警告,或者正确地将一个整数转换为一个double)。const-ness可以被认为是类型的一部分。因为它有表达式的类型信息,所以它可以检查 lvalue 的类型(赋值的左侧),如果类型中有'const',则抛出错误。

euoag5mw

euoag5mw5#

在C中,const关键字将使变量不可变,这意味着它不能被修改。
通常,这是编译时的区别,对变量的运行时修改没有影响。例如,可以使用指向同一内存地址的指针间接修改const变量。
在C++中,const关键字有多种含义。
例如,类成员函数可以是“const”,这意味着不允许修改类示例的状态。
与C语言一样,声明为const的变量也可以使用指针间接修改,但也可以使用“mutable”关键字或const_cast<>运算符。

ioekq8ef

ioekq8ef6#

这在优化编译器中实际上是一件非常复杂的事情。不过,一开始很简单。
当你用const关键字声明一个变量时(让我们忽略指针,因为它们可以是const或指向const或两者兼而有之),编译器会记住任何代码都不应该改变这个变量(几乎)。如果编译器看到代码改变了一个常量变量,那么它会认为这是一个错误(或者偶尔只值得警告,但为了简单起见,我会忽略它)。编译器还假设没有它看不到的代码(无论如何(其他.c文件或可能的库或.s或.asm文件中的代码)将更改const变量(除非它是const volatile,在这种情况下,它会假设它可以随时更改,但仍然会强制不让您的代码更改它-这对于用于读取设备状态的内存MapSFR [特殊功能寄存器]很有用,但不能写入。请记住,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来重载函数名,这可能很有用。

相关问题