C语言中限定词的深层分析

const变量在哪里准确存储,它的行为如何变化? 比如说:

 const int i=10; // stores where ? main() { const int j=20; //stores where? return 0; } 

如果答案是代码段,那么以下代码如何工作?

 main() { const int j=20; int *p; p=&j; (*p)++; return 0 ; } 

这段代码工作正常……如何更改只读内存? 它是如何存储的? 请详细解释一下。

关键字const表示只读变量(即,在运行时无法更改)。 它不表示编译时常量。 因此,变量的所有常见属性都适用; 具体来说,它被分配了可寻址的存储空间。

#define不同,您的常量不一定由编译器内联。 相反,编译器将在目标文件中创建一个与const声明相对应的符号,以便可以从其他代码文件中访问它 – 请记住const对象默认情况下在C中具有外部链接(尽管某些编译器仍然会在其中内联常量值它定义的文件)。

您发布的“工作”代码片段的原因是因为一元运算符&可以应用于包含const对象的任何左值。 虽然这里的行为是未定义的,但我怀疑你的编译器正在检测这种用法,并确保你的const声明被赋予地址空间,因此不会内联它,即使在声明它的文件中也是如此。

编辑:另见: http : //publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html

让我们来看看使用const时的含义。 它非常简单:const意味着某些东西不可修改,因此在程序运行期间不得以任何方式将以const作为其类型规范的一部分声明的数据对象分配。 对象的定义很可能包含一个初始值设定项(否则,因为你无法分配它,它将如何得到一个值?),但情况并非总是如此。 例如,如果您访问固定内存地址的硬件端口并承诺只读取它,那么它将被声明为const但未初始化。

获取非const类型的数据对象的地址并将其放入指向相同类型的const限定版本的指针中是安全且明确允许的; 您将能够使用指针检查对象,但不能修改它。 将const类型的地址放入指向非限定类型的指针更加危险并因此被禁止(尽管您可以通过使用强制转换来解决这个问题)。 例如…

修改代码以打印值:

 #include  main() { const int j=20; int *p; p=&j; (*p)++; printf("%d\n", j); return 0 ; } 

上面的代码,当使用gcc 4.3.2在-O1优化或以上编译时,导致输出20而不是21 。 这表明它并没有真正“有效” – 它似乎只是起作用。

const限定符不是将变量放在特定类型的内存中的请求 – 它是您对编译器的承诺,您不会以任何方式修改该变量。 编译器可以依靠您的承诺来优化生成的代码 – 如果您违背承诺,它不一定会以明显的方式破坏,它可能只会产生奇怪的结果。

按照C标准( n1256草案):

6.7.3类型限定符

3与限定类型关联的属性仅对作为左值的表达式有意义。 114)

5如果尝试通过使用具有非const限定类型的左值来修改使用const限定类型定义的对象,则行为未定义。 如果尝试通过使用具有非volatile限定类型的左值来引用使用volatile限定类型定义的对象,则行为未定义。 115)

114)实现可以在只读存储区域中放置volatileconst对象。 此外,如果从不使用其地址,则实现不需要为这样的对象分配存储。

115)这适用于那些行为就好像用限定类型定义的对象,即使它们实际上从未被定义为程序中的对象(例如内存映射输入/输出地址的对象)。

简而言之, const -qualified对象可以存储在与非const qualifiedified对象不同的区域中,但不一定。

const限定符是指令编译器拒绝尝试直接修改该对象的代码; 尝试间接修改对象(就像在第二个代码片段中那样)会导致未定义的行为,这意味着任何结果都是可能的。

我不知道它存储在何处,因为它是实现定义的,但是您的代码会导致未定义的行为。

它真的不应该工作。

常量通常不存储在任何地方。 它们是内联扩展的。

你的编译器可能对你很好并且给你一个内存位置来修改,但通常这是不可能的。

你得到了什么警告? 我想你必须得到一些……

它完全取决于编译器编写器const发生了什么,它会根据您请求的优化而变化。

在你的第一个例子中,永远不会使用常量,所以编译器可能会完全忽略它们。

在你的第二个例子中,当你使用“地址关闭”时,它必须实际存储在某个地方 – 可能是在堆栈的开头。

由于C旨在取代汇编语言指令,并且用于编写OS内核和设备驱动程序类型代码,因此它可以让您执行各种操作,并假设您在开始弄乱指针时知道自己在做什么。

编译器确定是否需要常量的地址。 如果不是,它(通常)内联输入代码段,因为它(通常)比引用内存更快。

如果需要该地址,则将常量存储为当前作用域中的非const变量(相对于编译器)。 也就是说,全局consts通常存储在您的数据段中,函数(参数或声明的)consts通常存储在堆栈中。

把它想象成一个寄存器变量。 如果您熟悉它,它就在您的CPU寄存器中。 在您需要其地址之前,它位于您的CPU寄存器中。 然后它被放入可寻址的空间。

真正的问题是初始化 – 如果你需要它的地址并因此实际分配,那么它在哪里被初始化? 你有什么需要思考的。

第二个代码根本不应该编译。 我在这里同意编译器:gcc给出了-pedantic-errors的错误(其目的是将一些强制性诊断变成错误,历史上gcc没有被认为是错误),xlc也给出了错误。

如果您需要参考:C90标准中的6.3.16.1描述何时可以进行分配:

两个操作数都是指向兼容类型的限定或非限定版本的指针,左边指向的类型具有右边指向的所有类型的限定符。

和c99作为类似的约束。

当您声明(非外部,非参数)并将变量初始化为const ,这意味着变量根本不可写。 所以编译器可以自由地将它放到只读部分。 虽然它可以在物理上进行修改(如果硬件允许的话)。 或者不可修改,如果它受MMU保护或放在独立应用程序的ROM中。

标准没有陈述,如果你试图编写const会发生什么(它被称为“未定义的行为”),所以任何事情都可能发生:它可能被写入,而不是写入,导致exception,挂起或其他你无法想象的东西。 C不是那么偏执,比如说,Ada,所有不受关注的行为都取决于程序员,而不是编译器或RTL。

正如许多人所说,它在大多数情况下被内联(如果编译器知道要内联),但仍然保留变量的属性,例如地址(并且你可以得到一个指针),大小。 如果消除了所有const读取和指向它的指针,那么const的存储也将被编译器(如果它是静态的或本地的)或链接器(如果它是全局的)消除。

注意,如果可以在编译时计算它们的位置,也可以消除局部指针。 如果在本地变量之后没有读取它们,也可以删除对局部变量的写入,因此您的代码段可能根本没有代码。

如果编译器certificate只需要一个实例,则可以在静态存储中编译自动局部变量。 由于const不可修改,它也在静态存储中编译,但可以如上所述被消除。

在所有的例子中,所有的consts首先可以放在静态存储(const部分),然后很容易消除。