【问题标题】:Why do compilers allow string literals not to be const?为什么编译器允许字符串文字不是 const?
【发布时间】:2011-03-05 17:45:58
【问题描述】:

内存中的文字到底在哪里? (见下面的例子)

我不能修改文字,所以它应该是 const char*,虽然编译器允许我使用 char*,但即使使用大多数编译器标志,我也没有警告。

而将 const char* 类型隐式转换为 char* 类型会给我一个警告,见下文(在 GCC 上测试,但在 VC++2010 上表现类似)。

另外,如果我修改 const char 的值(在下面使用 GCC 最好给我警告的技巧),它不会出错,我什至可以修改并在 GCC 上显示它(即使我猜到了仍然是一个未定义的行为,我想知道为什么它没有对文字做同样的事情)。这就是为什么我要问这些文字存储在哪里,以及更常见的 const 应该存储在哪里?

const char* a = "test";
char* b = a; /* warning: initialization discards qualifiers 
  from pointer target type (on gcc), error on VC++2k10 */

char *c = "test"; // no compile errors
c[0] = 'p'; /* bus error when execution (we are not supposed to 
  modify const anyway, so why can I and with no errors? And where is the 
  literal stored for I have a "bus error"? 
  I have 'access violation writing' on VC++2010 */

const char d = 'a';
*(char*)&d = 'b'; // no warnings (why not?)
printf("%c", d);  /* displays 'b' (why doesn't it do the same
  behavior as modifying a literal? It displays 'a' on VC++2010 */

【问题讨论】:

  • 有人可以消除标题的歧义以引用“字符串文字”
  • 至于第二个示例,至少在 Windows 上,文字位于写保护页面中,很可能与实际说明一起。您可以通过VirtualProtect解除保护验证。

标签: c++ c memory standards


【解决方案1】:

我不确定 C/C++ 标准对字符串的含义。但我可以告诉确切地,MSVC 中的字符串文字会发生什么实际。而且,我相信其他编译器的行为也类似。

字符串字面量驻留在 const 数据段中。它们的内存被映射到进程地址空间。但是它们存储的内存页面是只读的(除非在运行期间明确修改)。

但还有一些你应该知道的。并非所有包含引号的 C/C++ 表达式都具有相同的含义。让我们澄清一切。

const char* a = "test";

上面的语句使编译器创建一个字符串字面量“test”。链接器确保它将位于可执行文件中。 在函数体中,编译器生成一个代码,在堆栈上声明一个变量a,该变量由字符串文字“test”的地址初始化。

char* b = a;

这里你在堆栈上声明另一个变量b,它的值是a。由于a 指向一个只读地址——b 也是如此。即使b 没有const 语义这一事实并不意味着您可以修改它所指向的内容。

char *c = "test"; // no compile errors
c[0] = 'p';

以上会产生访问冲突。同样,缺少const 并不意味着机器级别的任何事情

const char d = 'a';
*(char*)&d = 'b';

首先 - 以上与字符串文字无关。 'a' 不是字符串。这是一个角色。这只是一个数字。就像写下面这样:

const int d = 55;
*(int*)&d = 56;

上面的代码让编译器很傻。你说变量是const,但是你设法修改它。但这与处理器异常无关,因为d 仍然驻留在读/写内存中。

我想再添加一个案例:

char b[] = "test";
b[2] = 'o';

上面在堆栈上声明了一个数组,并用字符串“test”对其进行初始化。它驻留在读/写内存中,并且可以修改。这里没有问题。

【讨论】:

    【解决方案2】:

    内存中的文字到底在哪里? (见下面的例子)

    初始化数据段。在 Linux 上,它是 .data.rodata

    我无法修改文字,所以它应该是 const char*,尽管编译器允许我使用 char*,但即使使用大多数编译器标志,我也没有警告。

    历史性的,因为它已经被其他人解释过了。大多数编译器允许您通过命令行选项判断字符串文字是只读的还是可修改的。

    通常希望字符串文字只读的原因是内存中具有只读数据的段可以(并且通常是)在从可执行文件启动的所有进程之间共享。这显然可以避免一些 RAM 被浪费以保存相同信息的冗余副本。

    【讨论】:

      【解决方案3】:

      主要是历史原因。但请记住,它们在某种程度上是合理的:字符串文字没有类型 char *,但 char [N] 其中N 表示缓冲区的大小(否则,sizeof 在字符串文字上不会按预期工作) 并可用于初始化非const 数组。您只能将它们分配给const 指针,因为数组隐式转换为指针和非constconst

      如果字符串文字表现出与复合文字相同的行为会更加一致,但由于这些是 C99 构造并且必须保持向后兼容性,这不是一个选项,因此字符串文字仍然是一个例外情况。

      【讨论】:

        【解决方案4】:

        C 标准不禁止修改字符串文字。它只是说如果进行了尝试,则行为是未定义的。根据 C99 的基本原理,委员会中有些人希望字符串文字可以修改,因此标准没有明确禁止。

        请注意,C++ 中的情况有所不同。在 C++ 中,字符串文字是 const char 的数组。但是,C++ 允许从 const char * 转换为 char *。不过,该功能已被弃用。

        【讨论】:

        • +1,从const char*char* 的转换符合标准,以保持与C 和C 库的兼容性。许多库采用仅读取为char* 的参数(在const 添加到C 之前和之后)。为了允许用户在文字(或指向文字的指针)上调用这些函数,添加了转换。
        • 实际上,C 标准禁止修改字符串文字,因为行为是 undefined 而不是 implementation-defined;依赖于修改的代码不是 C,而是用类似 C 但不兼容的语言编写的
        【解决方案5】:

        您可以写信给c,因为您没有将其设为 const。将 c 定义为 const 是正确的做法,因为右侧的类型为 const char*

        它在运行时会产生错误,因为“test”值可能分配给只读的代码段。见herehere

        【讨论】:

          【解决方案6】:

          即使使用大多数编译器标志,我也没有任何警告

          真的吗?当我编译以下代码sn-p时:

          int main()
          {
              char* p = "some literal";
          }
          

          在 g++ 4.5.0 即使没有任何标志,我也会收到以下警告:

          警告:不推荐将字符串常量转换为 'char*'

          【讨论】:

          • 何?太好了,他们在最新版本中添加了警告,我使用的是旧版本 GCC 3.2,但我没有。我在 VC++2010 上也没有警告
          • @Dpp:您使用这么旧版本的 GCC 有什么特别的原因吗?
          • @Dpp 您使用的是 gcc 还是 g++ - 警告在 C++ (g++) 中有效,但在 C (gcc) 中无效?
          猜你喜欢
          • 2018-09-24
          • 2013-01-12
          • 1970-01-01
          • 1970-01-01
          • 2011-01-31
          • 2017-01-19
          • 2021-07-18
          相关资源
          最近更新 更多