【问题标题】:Are string literals const?字符串文字是常量吗?
【发布时间】:2011-05-28 10:43:37
【问题描述】:

如果我将字符串文字分配给 char*,GCC 和 Clang 都不会抱怨,即使使用大量迂腐选项 (-Wall -W -pedantic -std=c99):

char *foo = "bar";

如果我将const char* 分配给char*,他们(当然)会抱怨。

这是否意味着字符串文字被认为是char* 类型?他们不应该是const char*吗?如果它们被修改,这不是定义的行为!

还有(一个不相关的问题)命令行参数呢(即:argv):它是否被认为是字符串文字数组?

【问题讨论】:

  • 如果您希望它发出警告,请使用-Wwrite-strings。等效设置在 g++

标签: c string constants literals


【解决方案1】:

使用-Wwrite-strings 选项,您将获得:

warning: initialization discards qualifiers from pointer target type

不管该选项如何,GCC 都会将文字放入只读内存部分,除非使用 -fwritable-strings 另有说明(但此选项已从最近的 GCC 版本中删除)。

命令行参数不是 const,它们通常存在于堆栈中。

【讨论】:

    【解决方案2】:

    为了完整起见,6.4.5 部分中的C99 draft standard(C89 和 C11 有相似的措辞) 字符串文字第 5 段 说:

    [...]一个字节或 值为零的代码附加到每个由一个或多个字符串文字产生的多字节字符序列。然后使用多字节字符序列初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。对于字符串文字,数组元素具有char类型,并使用多字节字符序列的各个字节进行初始化;[...]

    所以这表示 字符串文字 具有静态存储持续时间(持续程序的生命周期),它的类型是 char[](不是 char *) 及其length 是附加零的字符串文字的大小。 *第 6 段`说:

    如果程序试图修改这样的数组,则行为未定义。

    因此,尝试修改 字符串文字undefined behavior,不管它们不是 const

    关于5.1.2.2.1部分中的argv程序启动第2段说:

    如果它们被声明,主函数的参数应遵循以下 约束:

    [...]

    -参数 argc 和 argv 以及 argv 数组指向的字符串应该可以被程序修改,并在程序之间保留它们最后存储的值 启动和程序终止。

    所以argv不被认为是一个字符串字面量数组,可以修改argv的内容。

    【讨论】:

      【解决方案3】:

      (抱歉,我刚刚注意到这个问题被标记为c,而不是c++。也许我的回答毕竟与这个问题不太相关!)

      字符串字面量不完全是constnot-const,字面量有一个特别奇怪的规则。

      (总结:文字可以通过引用数组作为foo( const char (&)[N]),不能作为非常量数组。它们更喜欢衰减到const char *。到目前为止,这使得它们看起来像是 const但是有一个特殊的旧规则允许文字衰减到 char *。请参阅下面的实验。)

      (在使用 -std=gnu++0x 在 clang3.3 上进行的实验之后。也许这是 C++11 问题?还是特定于 clang?无论哪种方式,都会发生一些奇怪的事情。)

      起初,文字似乎是const

      void foo( const char  * ) { std::cout << "const char *" << std::endl; }
      void foo(       char  * ) { std::cout << "      char *" << std::endl; }
      
      int main() {
              const char arr_cc[3] = "hi";
              char arr_c[3] = "hi";
      
              foo(arr_cc); // const char *
              foo(arr_c);  //       char *
              foo("hi");   // const char *
      }
      

      这两个数组的行为符合预期,证明foo 能够告诉我们指针是否为const。然后"hi" 选择const 版本的foo。所以看起来这就解决了:文字是const ...不是吗?

      但是,如果你删除了void foo( const char * ),它就会变得很奇怪。首先,对foo(arr_c) 的调用在编译时失败并出现错误。这是意料之中的。但是文字调用 (foo("hi")) 通过非常量调用工作。

      因此,字面量比arr_c“更常量”(因为它们更喜欢衰减到const char *,不像arr_c。但字面量比arr_cc“更小”,因为它们愿意衰减到char * 如果需要。

      (当 Clang 衰减到 char * 时会发出警告)。

      但是腐烂的呢?为简单起见,让我们避免它。

      让我们通过引用将数组放入 foo 中。这给了我们更“直观”的结果:

      void foo( const char  (&)[3] ) { std::cout << "const char (&)[3]" << std::endl; }
      void foo(       char  (&)[3] ) { std::cout << "      char (&)[3]" << std::endl; }
      

      和以前一样,字面量和 const 数组 (arr_cc) 使用 const 版本,arr_c 使用非 const 版本。如果我们删除foo( const char (&amp;)[3] ),那么foo(arr_cc);foo("hi"); 都会出现错误。简而言之,如果我们避免指针衰减并改用数组引用,文字的行为就好像它们是const

      模板?

      在模板中,系统将推断出const char * 而不是char *,而您对此“卡住”了。

      template<typename T>
      void bar(T *t) { // will deduce   const char   when a literal is supplied
          foo(t);
      }
      

      所以基本上,文字始终表现为const,除非您直接使用文字初始化char *

      【讨论】:

        【解决方案4】:

        字符串文字有正式类型char []语义类型const char []。纯粹主义者讨厌它,但这通常是有用且无害的,除了用“为什么我的程序崩溃?!?!”带来很多新手。问题。

        【讨论】:

        • 让我很好奇,你有一个有用的例子吗?我的意思是除了这个向后兼容的想法之外,我猜这个想法越来越不重要了?
        • 有时您需要char * 而不是const char * 来迭代字符串,尤其是当您将指向它的指针传递给strtol 之类的函数时。这与其说是向后兼容性问题,不如说是const 有时会很痛苦。当然,在这种情况下,大多数时候我也必须处理非文字字符串,然后丢弃 const...
        • @R.. 我没有看到迭代 const char * 甚至将其传递给 strtol() 的问题。毕竟不是const char const *
        • @morty: strtol 采用 char **,而不是 const char **,作为其 endptr 参数。对于相关功能也是如此。如果您有const char *p,则不能将&amp;p 传递给strtol;相反,您必须将值复制到char *tmp(使用强制转换从指向类型中删除const),传递&amp;tmp,然后将结果复制回来。
        • 特别注意你不能在这里传递(char **)&amp;p;这样做会导致未定义的行为(别名违规)。
        【解决方案5】:

        在 C89 和 C99 中,字符串文字都是 char * 类型(据我了解,出于历史原因)。您是正确的,尝试修改一个会导致未定义的行为。 GCC 有一个特定的警告标志 -Wwrite-strings(它不是 -Wall 的一部分),如果您尝试这样做,它将警告您。

        对于argv,参数被复制到程序的地址空间中,并且可以在main() 函数中安全地修改。

        编辑:糟糕,-Wno-write-strings 被意外复制了。更新为正确(肯定)形式的警告标志。

        【讨论】:

        • 字符串字面量是数组类型(char [N]),而不是指针类型(char *);参考n1256中的6.4.5字符串文字
        【解决方案6】:

        关于类型和内容,Johannes 的回答是正确的。但除此之外,是的,修改字符串文字的内容是未定义的行为。

        关于argv的问题:

        参数 argc 和 argv 以及 argv 数组指向的字符串 应由程序修改, 并保留其最后存储的值 程序启动和程序之间 终止。

        【讨论】:

          【解决方案7】:

          它们是 const char*,但是对于在 const 之前存在的遗留代码将它们分配给 char* 有一个特定的排除。而且命令行参数绝对不是字面的,它们是在运行时创建的。

          【讨论】:

          • 不知道你为什么投了反对票。我做了一些实验来确认它们不是 const 或非常量。我会发布我自己的答案。
          • 这是不正确的。它们在 C 中不是 const,而是在 C++ 中。正如其他人所指出的那样,它们通常不可写,但它们的行为并不像常量。有些奇怪的事情你不能做,比如 C 中的 'case "abc"[2]:',但你可以在 C++ 中,其中字符串文字是常量。这可能会对某些宏产生很大影响。
          【解决方案8】:

          它们的类型为char[N],其中N 是包括终止\0 在内的字符数。所以是的,您可以将它们分配给char*,但您仍然无法写入它们(效果将是未定义的)。

          Wrt argv:它指向一个指向字符串的指针数组。这些字符串是可显式修改的。您可以更改它们,它们需要保存最后存储的值。

          【讨论】:

          • +1 -- 请注意这是他们的类型,但您必须将他们视为const(否则未定义的行为/访问违规精灵会访问您)
          • @Billy:你不觉得这有点不一致吗?否则 OP 根本不需要问这个问题。
          • @Vlad:我没有写标准:P
          • @Vlad c++ 委员会已经禁止将字符串文字转换为char*。在 C++0x 中做这样的事情变得不合时宜。
          • C 自 C89 以来有 const。它没有添加到 C99。
          猜你喜欢
          • 1970-01-01
          • 2019-10-29
          • 2015-09-17
          • 2020-05-01
          • 1970-01-01
          • 2013-11-10
          • 2013-11-12
          • 1970-01-01
          相关资源
          最近更新 更多