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 ;
}
此代码工作正常...如何更改只读内存?它是如何真正存储的?请详细解释一下。
答案 0 :(得分:9)
关键字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类型转换为指针 不合格的类型更多 危险,因此被禁止 (虽然你可以绕过这个 使用演员)。例如......
答案 1 :(得分:4)
修改代码以打印值:
#include <stdio.h>
main()
{
const int j=20;
int *p;
p=&j;
(*p)++;
printf("%d\n", j);
return 0 ;
}
以上代码在使用gcc 4.3.2在-O1
优化或更高优化时编译时,会产生输出20
而不是21
。这表明它并没有真正起作用 - 只是出现才能工作。
const
限定符不是请求将变量放在特定类型的内存中 - 这是您对编译器的承诺,您不会修改它无论如何都是变数。编译器可以依靠您的承诺来优化生成的代码 - 如果您违背承诺,它不一定会以明显的方式破坏,它可能只会产生奇怪的结果。
答案 2 :(得分:4)
根据C标准(n1256草案):
6.7.3类型限定符
...
3与限定类型关联的属性仅对表达式有意义 是左值。 114)
...
5如果尝试通过使用修改使用const限定类型定义的对象 如果是非const限定类型的左值,则行为未定义。如果是尝试 通过使用左值来引用用volatile限定类型定义的对象 对于非易失性限定类型,行为未定义。 115)
...
114)实现可以将const
对象放在volatile
的只读区域中 存储。此外,如果其地址是,则实现不需要为这样的对象分配存储 从未使用过。
115)这适用于那些行为就好像用合格类型定义的对象,即使它们是 从未实际定义为程序中的对象(例如内存映射输入/输出中的对象) 地址)。
简而言之,const
- 限定对象可以存储在与非const
限定对象不同的区域中,但不一定如此。
const
限定符是指令编译器拒绝尝试直接修改该对象的代码;尝试间接修改对象(正如您在第二个代码段中所做的那样)会导致未定义的行为,这意味着任何结果都是可能的。
答案 3 :(得分:2)
我不知道它存储在何处,因为它是实现定义的,但是您的代码会导致未定义的行为。
答案 4 :(得分:1)
这真的不行。
常量通常不存储在任何地方。它们是内联扩展的。
你的编译器可能对你很好并给你一个内存位置来修改,但通常这是不可能的。
你得到了什么警告?我想你必须得到一些......
答案 5 :(得分:1)
它完全取决于编译器编写器const会发生什么,它会根据您请求的优化而变化。
在你的第一个例子中,永远不会使用常量,所以编译器可能会完全忽略它们。
在你的第二个例子中,当你使用“地址关闭”时,它必须实际存储在某个地方 - 可能是在堆栈的开头。
由于C旨在取代汇编语言指令,并且用于编写OS内核和设备驱动程序类型代码,因此它可以让您执行各种操作,并假设您在开始弄乱指针时知道自己在做什么。
答案 6 :(得分:1)
编译器确定是否需要常量的地址。如果不是,它(通常)内联输入代码段,因为它(通常)比引用内存更快。
如果需要地址,则将常量存储为当前作用域中的非const变量(相对于编译器)。也就是说,全局consts通常存储在你的数据段中,函数(参数或声明的)consts通常存储在堆栈中。
将其视为寄存器变量。如果您熟悉它,它就在您的CPU注册表中。在您需要其地址之前,它位于CPU的寄存器中。然后它被放入可寻址的空间。
真正的问题是初始化 - 如果你需要它的地址并因此实际分配,那么它在哪里被初始化?你有什么值得思考的。
答案 7 :(得分:1)
第二个代码根本不应该编译。我在这里同意编译器:gcc给出了-pedantic-errors的错误(其目的是将一些强制性诊断变成错误,历史上gcc没有被认为是错误),xlc也给出了错误。
如果您想要参考:C90标准中的6.3.16.1描述何时可以进行分配:
两个操作数都是指向合格或非限定版本的指针 兼容的类型,左边指向的类型都有 右侧指向的类型的限定符
和c99作为类似的约束。
答案 8 :(得分:1)
当您声明(非外部,非参数)并将变量初始化为const
时,这意味着变量根本不可写。所以编译器可以自由地将它放到只读部分。虽然它可以在物理上进行修改(如果硬件允许的话)。或者不可修改,如果它受MMU保护或放在独立应用程序的ROM中。
标准没有陈述,如果你试图编写const会发生什么(它被称为“未定义的行为”),所以任何事情都可能发生:它可能是写的,不是写的,导致异常,挂起或其他你不能想像。 C不是那么偏执,比如Ada,并且所有不受关注的行为都取决于程序员,而不是编译器或RTL。
正如许多人所说,在大多数情况下它是内联的(如果编译器知道要内联),但仍然保留变量的属性,例如地址(并且你可以得到指针),大小。如果删除了所有const读取和指针,那么const的存储也将被编译器(如果它是静态的或本地的)或链接器(如果它是全局的)消除。
注意,如果可以在编译时计算它们的位置,也可以消除局部指针。如果在本地变量之后没有读取它们,也可以删除对局部变量的写入,因此您的代码段可能根本没有代码。
如果编译器证明只需要一个实例,则可以在静态存储中编译自动局部变量。由于const不可修改,它也在静态存储中编译,但可以如上所述被删除。
在你的所有例子中,所有的consts可能首先被置于静态存储(const部分),然后很容易消除。