【问题标题】:If char*s are read only, why can I overwrite them?如果 char*s 是只读的,为什么我可以覆盖它们?
【发布时间】:2017-11-01 20:11:58
【问题描述】:

我的课程告诉我 char*s 是静态的/只读的,所以我认为这意味着您在定义它们之后就无法编辑它们。但是当我运行时:

char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit = "apple";
printf("fruit is %s\n", fruit);

然后它编译得很好并给了我:

fruit is banana
fruit is apple

为什么?我是否误解了只读的含义?对不起,如果这很明显,但我是编码新手,我无法在网上找到答案。

【问题讨论】:

  • 你没有覆盖任何东西。您刚刚将 fruit 指向了不同的字符串。
  • 试试const char * const fruit = "banana";
  • A char* 不是只读的。像 "banana""apple" 这样的字符串文字是只读的。 C 允许 char* 指向一个字符串字面量。
  • 没有发生重新分配。程序启动时,"banana""apple" 都已分配在静态内存中。您所做的只是将fruit 的值更改为存储"apple" 的地址。
  • @sally2000,这样想,当你声明一个指针并初始化它指向"banna"时,你初始化指针指向第一个字母'b'的地址。 (例如char* fruit = "banana";)。 fruit(一个指针)只是保存'b'地址作为它的。当您分配fruit = "apple"; 时,您只是将fruit 持有的值更改为'a'地址。所以现在fruit 指向 "apple" 中的'a'(它允许以字符串的形式访问"apple"

标签: arrays c c-strings string-literals


【解决方案1】:

呈现的代码 sn-p 不会改变字符串本身。它只会更改存储在指针fruit 中的值。

你可以想象这些线条

char* fruit = "banana";
fruit = "apple";

以下方式

char unnamed_static_array_banana[] = { 'b', 'a', 'n', 'a', 'n', 'a', '\0' };
char *fruit = &unnamed_static_array_banana[0];
char unnamed_static_array_apple[]  = { 'a', 'p', 'p', 'l', 'e', '\0' };
fruit = &unnamed_static_array_apple[0];

这些语句不会更改与字符串文字对应的数组。

另一方面,如果你会尝试写作

char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit[0] = 'h';
^^^^^^^^^^^^^^
printf("fruit is %s\n", fruit);

也就是说,如果您尝试使用指向它的指针(指向字符串文字的第一个字符)来更改字符串文字,那么程序将具有未定义的行为。

来自 C 标准(6.4.5 字符串文字)

7 不确定这些数组是否不同,前提是它们的 元素具有适当的值。 如果程序试图 修改这样的数组,行为未定义。

【讨论】:

  • mm 好的,但如果我使用 char[] 代替例如fruit [10] = {'a','p','p','l','e','\0'} 这里是fruit 不只是指向'a' 的内存地址的指针吗?为什么我可以更改 char[] 而不是 char*?
  • @sally2000 如果您使用字符数组而不是字符串文字,并且有一个指向字符数组第一个字符的指针,那么您可以更改它,前提是该数组未使用限定符 const 声明。
  • @sally2000 在内部,字符串文字存储为数组,其静态存储持续时间可能不会更改。
  • 字符串文字在某些实现中可能位于只读内存中(类似哈佛的机器,或者直接从 ROM 中运行的东西,或者现代保护模式系统上的 mmap-ed 共享库.. .) ...编写它只会失败甚至触发硬件异常...在其他环境中您会尝试并成功...
  • @sally2000: NO 如果你有一个数组,fruit 不是一个指针——它是一个数组。现在确实,如果使用数组的名称,它会衰减为指向 char 的指针,但它一个数组。例如,如果你写char fruit [10] = {'a','p','p','l','e','\0'};,你就不能写fruit = "banana";。您还会发现 sizeof(fruit); 是 10,而不是 sizeof(char*)(可能是 4 或 8)。
【解决方案2】:

在您的程序中,表达式"banana" 表示程序映像中的字符串文字对象,即字符数组。表达式的值是char * 类型,或“指向字符的指针”。指针指向该数组的第一个字节,字符'b'

您的char *fruit 变量也具有“指向字符的指针”类型,并从该表达式中获取其初始值:它被初始化为指向数据的指针的副本,而不是数据本身;它只是指向b

当您将 "apple" 分配给 fruit 时,您只是将其指针值替换为另一个值,因此它现在指向不同的文字数组。

要修改数据本身,需要如下表达式:

char *fruit = "banana";
fruit[0] = 'z';  /* try to turn "banana" into "zanana" */

根据 ISO C 标准,此行为未定义。 可能"banana" 数组是只读的,但这不是必需的。

C 实现可以使字符串文字可写,或使其成为一个选项。

(如果您能够修改字符串文字,这并不意味着一切都很好。首先,您的程序仍然没有根据 ISO C 进行良好定义:它不可移植。其次,允许使用 C 编译器将具有共同内容的文字合并到同一个存储中。这意味着程序中出现的两次"banana"实际上可能是完全相同的数组。此外,出现在程序某处的字符串文字"nana"可能是后缀数组"banana" 出现在别处;换句话说,共享相同的存储。修改文字可能会产生令人惊讶的效果;修改可以出现在其他文字中。)

“静态”和“只读”也不是同义词。 C 中的大多数静态存储实际上是可修改的。我们可以创建一个可修改的静态字符数组,其中包含这样的字符串:

/* at file scope, i.e. outside of any function */
char fruit[] = "banana";

或者:

{
  /* in a function */
  static fruit[] = "banana";

如果我们省略数组大小,它会根据初始化字符串文字自动调整大小,并包含空终止字节的空间。在函数中,我们需要static将数组放入静态存储,否则我们得到一个局部变量。

这些数组可以修改; fruit[0] = 'z' 是定义明确的行为。

此外,在这些情况下,"banana" 不表示字符数组。数组为变量fruit"banana" 表达式只是表示数组初始值的一段语法:

char *fruit = "banana";  // "banana" is an object in program image
                         // initial value is a pointer to that object

char fruit_array[] = "apple"; // "apple" is syntax giving initial value

【讨论】:

    【解决方案3】:

    fruit 对象是可写的 - 它可以设置为指向不同的字符串字面量。

    字符串字面量 "banana""apple" 是不可写的。您可以修改fruit 以指向字符串文字,但如果您这样做,则不应尝试修改fruit 指向的东西:

    char *fruit = "banana"; // fruit points to first character of string literal
    fruit = "apple";        // okay, fruit points to first character of different string literal
    *fruit = 'A';           // not okay, attempting to modify contents of string literal
    fruit[1] = 'P';         // not okay, attempting to modify contents of string literal
    

    尝试修改字符串文字的内容会导致未定义的行为 - 您的代码可能会按预期工作,或者您可能会遇到运行时错误,或者可能会发生完全意想不到的事情。为了安全起见,如果你定义一个变量指向一个字符串字面量,你应该声明它const:

    const char *fruit = "banana";  // can also be written char const *
    

    您仍然可以分配fruit 以指向不同的字符串:

    fruit = "apple";
    

    但是如果你试图修改fruit 指向的内容,编译器会骂你。

    如果你想定义一个只能指向一个特定字符串字面量的指针,那么你也可以const-限定指针:

    const char * const fruit = "banana"; // can also be written char const * const
    

    这样,如果您尝试写入fruit 指向的内容,或者尝试将fruit 设置为指向不同的对象,编译器就会对您大喊大叫。

    【讨论】:

      【解决方案4】:

      基本上,当你执行时

      char* fruit = "banana";
      

      你设置了一个指针fruit 指向“banana”的第一个字母。打印出来时,C 基本上从 'b' 开始并一直打印字母,直到最后遇到 \0 空字符。

      到时候说

      fruit = "apple";
      

      您已将指针 fruit 更改为现在指向“apple”的第一个字母

      【讨论】:

        【解决方案5】:

        首先,char* 不是只读的。 char * consts 是。它们与char const * 不同。文字字符串(例如,“banana”)应该是,但不是必须的。

        char * const  cpfruit = "banana";
        cpfruit = "apple";        // error
        
        char const * cpfruit = "banana";
        cpfruit[0] = 'x';        // error
        
        char * ncfruit = "banana";
        ncfruit[0] = 'x';        // compile will allow, but may cause run-time error.
        

        【讨论】:

          【解决方案6】:

          您将变量 fruit 指向不同的字符串。您只是覆盖地址(位置)。编译器将看到您的常量字符串“banana”和“apple”,并将它们分别存储在程序内存中。假设字符串“banana”进入位于地址1 的内存单元,而“apple”被存储到内存地址2。现在当你这样做时:

          fruit = "banana";
          

          编译器只会将1 分配给变量fruit,这意味着它指向包含字符串banana 的地址1。当你这样做时:

          fruit = "apple";
          

          编译器将分配2变量fruit,这意味着它指向地址2,其中存储了字符串apple

          【讨论】:

          • 没什么,但编译器会看到 字符串文字 "banana""apple" 并在 .rodata(只读数据)部分为它们提供存储空间. (我认为你在说同样的话)
          • 是的,只是想在这里保持简单。
          【解决方案7】:

          您的课程教给您的是正确的!

          当您首先定义 char* fruit = "banana" 时,您基本上将 fruit 作为指向 常量字符 的指针。字符串的 7 个字节(包括空终止符)位于目标文件的 .ro 部分(部分名称显然会因平台而异)。

          当您将 char 指针fruit 重置为“apple”时,它只是指向只读部分中包含“apple”的另一个内存位置

          基本上当你说fruit是一个常量时,它指的是fruit是一个指向const内存的指针。如果您将其定义为 const pointer to a const string :-
          char* const fruit = "banana";
          编译器会阻止您将其重置为“apple”

          【讨论】:

          • "fruit 作为指向常量字符的指针" --> 不同意。 @rackandboneman 评论说得很好。 fruitchar *。以其原始值,试图改变它所指向的是UB。
          【解决方案8】:

          当您使用char *p="banana"; 时,字符串banana 存储在只读内存位置。之后,当您输入p="apple"; 时,字符串 apple 存储在其他内存位置,并且指针现在指向新的内存位置。

          您可以通过在每次分配后打印p 来确认这一点。

          #include<stdio.h>
          int main(void)
          {
              char *p = "Banana";
              printf("p contains address of string constant 'Banana' at 0x%p\n", p);
          
              p="Apple";
              printf("p contains address of string constant 'Apple' at 0x%p\n", p);
          
          }
          

          【讨论】:

          • 我认为打印地址是说服 OP 的好主意。如果它显示了它是如何完成的,我会赞成你的答案。
          • 顺便说一句,我不知道有任何标准要求将字符串文字放入只读内存中。在某些平台上,这甚至无法强制执行——因为没有任何 MMU。
          • @hagello 感谢您的编辑。我添加了显示不同地址位置的代码,但格式完全关闭。将不得不为此努力。但希望它能说服 OP。
          • 您的代码没有证明字符串文字存储在只读内存中。几乎可以肯定,它们并没有按字面意思存储在 ROM(其内容在物理上无法修改的内存)中。它们很可能存储在操作系统配置为只读的内存中。
          • 您打印的是p 的值,而不是p 的地址。 p 的地址将是 &amp;p
          【解决方案9】:

          当定义一个 C 字符串,又名 char 数组时,使用双引号 "...",格式如下:

          char * <varName> = "<someString>"
          

          只有数组的元素是不可变的(它们的内容不能改变)。换句话说,&lt;varName&gt; 具有const char * 类型(指向只读内存的可变指针)。每次使用双引号 &lt;varName&gt; = "&lt;otherString&gt;" 调用赋值运算符时,它都会自动更改指针值。下面的例子应该对不同的可能性给出一个完整的概述:

          #include <stdio.h>
          
          int main()
          {
              char * var_1 = "Lorem";
              printf("1. %s , %p\n", var_1, var_1); // --> 1. Lorem , 0x400640
          
              var_1 = "ipsu";
              printf("2. %s , %p\n", var_1, var_1); // --> 2. ipsu , 0x400652
          
              // var_1[0] = 'x'; // --> Segmentation fault
          
              var_1++;
              printf("3. %s , %p\n", var_1, var_1); // --> 3. psu , 0x400653
          
              char var_2[] = {'L', 'o', 'r', 'e', 'm', '\0'};
              printf("4. %s , %p\n", var_2, var_2); // --> 4. Lorem , 0x7ffed0fc5381
          
              var_2[0] = 'x';
              printf("5. %s , %p\n", var_2, var_2); // --> 5. xorem , 0x7ffed0fc5381
          
              // var_2++; //error: lvalue required as increment operand
          
              char var_3[] = "Lorem";
              printf("6. %s , %p\n", var_3, var_3); // --> 6. Lorem , 0x7ffe36a42d5c
          
              // var_3 = "ipsu"; // --> error: assignment to expression with array type
          
              var_3[0] = 'x';
              printf("7. %s , %p\n", var_3, var_3); // --> 7. xorem , 0x7ffe36a42d5c
          
              char * const var_4 = "Lorem";
          
              // var_4 = "ipsu"; // --> error: assignment of read-only variable
          
              // var_4[0] = 'x'; // --> Segmentation fault
          
              char const * var_5 = "Lorem";
              printf("8. %s , %p\n", var_5, var_5); // --> Lorem , 0x400720
          
              var_5 = "ipsu";
              printf("9. %s , %p\n", var_5, var_5); // --> ipsu , 0x400732
          
              // var_5[0] = 'x'; // --> error: assignment of read-only location
          
              const char * var_6 = "Lorem";
              printf("10. %s , %p\n", var_6, var_6); // --> 10. Lorem , 0x400760
          
              var_6 = "ipsu";
              printf("11. %s , %p\n", var_6, var_6); // --> 11. ipsu , 0x400772
          
              // var_6[0] = 'x'; // --> error: assignment of read-only location
          
              const char const * var_7 = "Lorem"; // clang only --> warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
              printf("12. %s , %p\n", var_7, var_7); // --> 12. Lorem , 0x400760
          
              var_7 = "ipsu";
              printf("13. %s , %p\n", var_7, var_7); // --> 13. ipsu , 0x400772
          
              // var_7[0] = 'x'; // --> error: assignment of read-only location
          
              char const const * var_8 = "Lorem"; // clang only --> warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
              printf("14. %s , %p\n", var_8, var_8); // --> 14. Lorem , 0x400790
          
              var_8 = "ipsu";
              printf("15. %s , %p\n", var_8, var_8); // --> 15. ipsu , 0x4007a2
          
              // var_8[0] = 'x'; // --> error: assignment of read-only location
          
              char const * const var_9 = "Lorem";
          
              // var_9 = "ipsu"; // --> error: assignment of read-only variable
          
              // var_9[0] = 'x'; // --> error: assignment of read-only location
          
              const char var_10[] = {'L', 'o', 'r', 'e', 'm', '\0'};
          
              // var_10[0] = 'x'; // --> error: assignment of read-only location
          
              // var_10++; // --> error: lvalue required as increment operand
          
              char const var_11[] = {'L', 'o', 'r', 'e', 'm', '\0'};
          
              // var_11[0] = 'x'; // --> error: assignment of read-only location 
          
              // var_11++; // --> error: lvalue required as increment operand
          
              const char var_12[] = "Lorem";
          
              // var_12[0] = 'x'; // --> error: assignment of read-only location
          
              // var_12++; // --> error: lvalue required as increment operand
          
              char const var_13[] = "Lorem";
          
              // var_13[0] = 'x'; // --> error: assignment of read-only location
          
              // var_13++; // --> error: lvalue required as increment operand
          
          
              return 0;
          }
          

          此代码已在 GCC、Clang 和 Visual Studio 上进行了测试。

          基本上有三种可能:

          • 不可变指针,可变内容

            • char &lt;varName&gt;[] = {'L', 'o', 'r', 'e', 'm', '\0'};
            • char &lt;varName&gt;[] = "Lorem";
          • 可变指针,不可变内容

            • char * &lt;varName&gt; = "Lorem";
            • char const * &lt;varName&gt; = "Lorem";
            • const char * &lt;varName&gt; = "Lorem";
            • const char const * &lt;varName&gt; = "Lorem";
            • char const const * &lt;varName&gt; = "Lorem";
          • 不可变指针,不可变内容

            • char * const &lt;varName&gt; = "Lorem";
            • char const * const &lt;varName&gt; = "Lorem";
            • const char * const &lt;varName&gt; = "Lorem";
            • const char &lt;varName&gt;[] = {'L', 'o', 'r', 'e', 'm', '\0'};
            • char const &lt;varName&gt;[] = {'L', 'o', 'r', 'e', 'm', '\0'};
            • const char &lt;varName&gt;[] = "Lorem";
            • char const &lt;varName&gt;[] = "Lorem";

          结论:

          • &lt;typing&gt; &lt;varName&gt;[] = &lt;string&gt; 总是返回一个不可变的指针,并且内容的可变性独立于 &lt;array&gt; 格式("Lorem"{'L', 'o', 'r', 'e', 'm', '\0'}
          • &lt;typing&gt; * &lt;varName&gt; = "someString" 总是返回不可变的内容
          • &lt;typing&gt; * const &lt;varName&gt; = "someString" 总是返回不可变的内容和指针
          • char const &lt;other&gt;char const &lt;other&gt;const char const &lt;other&gt;char const const &lt;other&gt; 始终创建不可变的内容。

          我试图详细总结 C 的数组行为here

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-11-22
            • 1970-01-01
            • 2017-06-28
            • 2021-01-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多