【问题标题】:Are const variables placed in read-only memory? [duplicate]const 变量是否放置在只读内存中? [复制]
【发布时间】:2013-08-05 12:21:26
【问题描述】:

或者是否有其他防止修改它们的保护措施?

如果它们在只读内存中是有意义的 - 这就是使它们成为 const 的原因,对吧?

【问题讨论】:

  • 参见this answer 以获取const volatile 的示例,其中只读内存没有意义。
  • 我很确定,在 C# 中,const 的所有用法在编译期间都被替换为它们的值,所以没有什么需要改变的了。不过,我不知道这是 .NET 主义,还是继承自 C。
  • @Bobson 这在 C++ 中是不可能实现的,因为 const 变量可能会在运行时设置它们的值。
  • @juanchopanza - 很高兴知道。各种相关语言之间存在的差异令人着迷。
  • const int r = rand();

标签: c++ c constants


【解决方案1】:

const 是一个编译时构造,在运行时不为人所知。它只是为了帮助程序员对他的程序进行推理,并防止通过更改不应该更改的内容而引入错误。 const 告诉编译器您不想允许更改此变量,因此编译器将强制执行它。

【讨论】:

  • 通过“编译器将强制执行”,我认为您的意思是您希望编译器在源代码尝试通过 const 限定类型修改对象时发出诊断,而不是编译器将防止您将 const 限定类型(或指向此类的指针)转换为非 const 限定类型(或指向此类的指针)并通过非 const 类型修改对象,前提是 C++ 标准允许此类修改(就像在某些情况下一样)?
【解决方案2】:

不,他们不是必须的。 const 是编译时,允许编译器执行某种优化。但是,将变量放入只读内存位置并不是强制性的。

看这个例子,它是未定义的行为(感谢 Dyp 指出):

#include <iostream>

int     main()
{
  const int bla = 42;
  int       *ptr = const_cast<int *>(&bla);

  std::cout << *ptr << std::endl;
  *ptr = 21;
  std::cout << *ptr << std::endl;
}

它会输出4221,但它也可能会崩溃。

现在看看这个:

#include <iostream>

int     main()
{
  const int bla = 42;
  int       *ptr = const_cast<int *>(&bla);

  std::cout << bla << std::endl;
  *ptr = 21;
  std::cout << bla << std::endl;
}

在我的编译器上,这个输出4242,因为编译器做了一些优化。注意它仍然可能因为*ptr = 21而崩溃;

【讨论】:

  • *ptr = 21; 未定义的行为。
  • @DyP 已编辑。你能给我解释或链接解释*ptr = 21是UB吗? const_cast 不是允许修改变量的有效构造吗?
  • 它允许你修改一个变量如果该变量没有被声明const。否则,UB: [dcl.type.cv]/4 "除了可以修改声明为mutable 的任何类成员外,任何在其生命周期内修改const 对象的尝试都会导致未定义的行为。"
  • @DyP 是的,我考虑过字符串文字,但认为它们是一个例外。让我编辑,非常感谢您指出这一点。今天学到了一些东西:)
  • 程序没有显示内存位置差异/*ptr = 21; 没有奇怪行为的原因可能是因为bla 要么放在堆栈上,要么放在 x86 类型的寄存器中建筑学;两者都不是只读的。
【解决方案3】:

在很多情况下,编译器不可能将const 变成只读内存(假设系统中首先存在只读内存)。事实上,我相信几乎所有的编译器,作为一项规则,使const 对象存在于常规数据(或堆栈)内存中,就像普通变量一样。

const 的主要目的是向编译器声明您不希望也不应该更改某些值的意图。我看不出为什么编译器不能在有限的情况下将const 变量放在只读内存中。但我也不会依赖这一点——标准当然使这成为可能,因为它提到使用const_cast 从最初标记为const 的对象中删除const,然后写入它是未定义的行为(所以,它不需要编译器允许在使用 const_cast 删除原始 const 后修改值 - 因此确实允许“崩溃,因为我们试图写入只读内存”)。

但是考虑一下:

class X
{
   int x;
  public:
   X(int v) : x(v) {}
}

int c = rand();

const X a(c+1);
const X b(c+2); 

在这种情况下,编译器无法知道从rand得到的c中的值,因此它无法在编译时初始化ab

【讨论】:

  • 抱歉,我的意思是写信给已删除 constconst 对象。我已经澄清了我的意思。
  • 好吧 :) 但我仍然可能是迂腐的:char c; char const&amp; rc = c; -> rc 也是一个变量,它被声明为 const
  • 不确定我是否理解你的迂腐。 rcconstc 的引用,因此与 c 相同的变量 - 在这种情况下,以 const_cast 方式有效并写入引用,从而改变 @987654346 的原始值@.
  • 确实如此,但它可以被解释为从最初标记为const的变量[rc]中删除const。该标准使用其自己的术语并谈到const objects 而不是变量(或变量声明)。另一个例子:new const int{5};(一个对象,但我不认为它是一个变量)
【解决方案4】:

const 关键字有两种用途:

  1. 众所周知的“我保证不会修改此对象的状态”
  2. 作为一个特殊规则,编译器可以将定义为 const 的对象放在只读内存中,只要该对象没有 mutable 成员(即整个对象都是该对象的州)。

这就是为什么 const_cast 允许去掉 const 限定符,但要求对象定义本身不是 constWikipedia on const correctness 声明“但是,任何尝试修改本身通过 const_cast 声明为 const 的对象的任何尝试都会导致根据 ISO C++ 标准的未定义行为。”。

【讨论】:

  • “这就是为什么 const_cast 被允许去掉 const 限定符”我认为你的意思是正确的,但看起来 (2.) 是这个原因(它不是)。奖励:这是直接来自标准的 UB 引用:[dcl.type.cv]/4 “除了可以修改任何声明为 mutable 的类成员之外,任何在其生命周期内修改 const 对象的尝试都会导致未定义的行为。 "
【解决方案5】:

C++ 标准不仅不保证const 对象在只读内存中,而且实现也很难完全实现,如下所述。

C++ 实现很难放置具有自动存储持续时间的const 对象,这些对象的地址取自只读内存。这是因为每个不同的对象必须有不同的地址(因为它们的指针必须比较不相等)。因此,每次执行它所在的块时,都必须创建每个这样的对象。

如果const 对象的地址未被获取,编译器可以仅使用内存中的单个实例来实现它的多个实例(在 C++ 计算模型中)(因为每个实例都是相同的并且不会改变)。因此,这样的对象可以在程序启动时创建一次(可能通过从程序文件的常量数据部分加载),标记为只读,并在程序执行期间保持不变。然而,如果一个对象的地址被获取(并以可观察的方式使用),那么编译器必须创建该对象的每个单独实例(或以某种方式“假装”一个实例具有多个地址)。通常,编译器无法预测一个块可能同时存在多少次执行(例如,当有递归函数调用时)。因此,它无法在程序启动时创建所需的对象的所有实例。它们必须即时创建。

创建对象需要修改内存,写入对象的初始值。因此,将具有自动存储持续时间的const 对象放在只读内存中将需要频繁地将内存从只读更改为可写并返回。要创建新对象,程序必须将只读内存更改为可写内存,写入新对象的初始值,然后将内存改回只读。

此外,这将为没有信号处理程序的单线程程序提供只读内存的外观。具有多个执行线程或单个处理程序的程序可能能够在内存处于可写状态时观察内存。

【讨论】:

    【解决方案6】:

    就标准而言,没有要求将const 变量放入写保护RAM。 const 基本上只是编译器强制执行的文档。

    实际上,如果一个值可以在编译时完全计算,编译器通常会为const 变量使用只读存储。否则,它们将被放入与其他所有内容相同的读写堆或堆栈中。

    【讨论】:

      【解决方案7】:

      没有。考虑一个带有mutable 成员的const 对象。如前所述,const 是一种编译时构造,可帮助程序员传达意图。

      【讨论】:

        【解决方案8】:

        const 是编译器的标记。在编译期间,编译器将常量添加到单独的组中并检查是否尝试更改它。如果某个函数试图这样做,你会得到编译器错误。

        但是可以欺骗编译器并使用指针和const_cast 更改const 的值,但结果是你欺骗的不是编译器而是你自己(或同事)。

        正如我所说,我的朋友一直在寻找如何更改 const 值。 const 不仅仅用于编译器,它对于开发者而言比编译器更重要,因为它显示了哪些数据不会被更改。

        【讨论】:

          【解决方案9】:

          如果它们在只读内存中,这将阻止 const_cast 有用和有意义,所以这样的代码是不可能的,将 const 变量传递给具有非 const 参数的函数: -

          void print (char * str)
          {
              cout << str << endl;
          }
          
          int main () 
          {
              const char * c = "test text";
              print ( const_cast<char *> (c) );
              return 0;
          }
          

          请注意,打印函数实际上可以更改最初定义的const 变量c 中的值。

          【讨论】:

          • "注意 print 函数实际上可以改变最初定义的 const 变量 c 中的值": 但这可能会使程序崩溃。
          • 当然可以,但这取决于更改的内容。如果它只是使字符串为空或保持在字符串长度的边界内,那么就可以了。所以它“可以”改变它,但可能不应该。
          • const_cast 实际上对于丢弃已添加的常量非常有用,例如 char c = 'x'; char const&amp; crc = c; char&amp; rc = const_cast&lt;char&amp;&gt;(crc);(通常涉及函数参数)。
          • @Merlin069 我不确定你所说的“保持在字符串长度的边界内”是什么意思,所以在这里,以防万一:ideone.com/ePAzRA。如果我误解了,请随意忽略它:)
          • @Xaqq,不错的工具,我没看到ideaone。你是对的,在那种情况下它会失败,但你可以做这样的事情来改变一个 const 转换变量:ideone.com/BuKjOf 并且你必须小心不要改变超出原始字符串的内存。
          猜你喜欢
          • 2011-09-08
          • 2018-10-02
          • 2019-04-02
          • 2020-03-13
          • 2017-11-03
          • 2014-02-08
          • 1970-01-01
          • 2017-09-14
          • 1970-01-01
          相关资源
          最近更新 更多