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

时间:2010-11-25 09:31:05

标签: c const

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 ;  
} 

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

9 个答案:

答案 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部分),然后很容易消除。