如果char * s是只读的,为什么我可以覆盖它们?

时间:2017-05-31 21:04:06

标签: c arrays string string-literals

我的课程告诉我,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

为什么呢?我误解了只读是什么意思吗?很抱歉,如果这很明显,但我不熟悉编码,我无法在线找到答案。

9 个答案:

答案 0 :(得分:19)

显示的代码段不会更改字符串文字本身。它只会更改指针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未指明这些阵列是否与它们不同   元素具有适当的值。 如果程序尝试   修改这样的数组,行为是未定义的。

答案 1 :(得分:8)

在程序中,表达式"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

答案 2 :(得分:2)

基本上,当你执行

char* fruit = "banana";

您在“banana”的第一个字母处设置指针fruit。打印出来时,C基本上从'b'开始并保持打印字母,直到它在最后点击\0空字符。

然后说

fruit = "apple";

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

答案 3 :(得分:2)

首先,char*不是只读的。 char * const是。它们与char const *不同。文字字符串(例如“香蕉”)应该是,但不一定。

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.

答案 4 :(得分:2)

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设置为指向其他对象,编译器会对您大喊大叫。

答案 5 :(得分:2)

当使用双引号char定义C字符串(也称为"..."数组)时,格式如下:

char * <varName> = "<someString>"

只有数组的元素是不可变的(它们的内容不能更改)。换句话说,<varName>具有const char *类型(指向只读存储器的可变指针)。每次您用双引号<varName> = "<otherString>"调用赋值运算符时,它都会自动更改指针值。以下示例应全面概述各种可能性:

#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 <varName>[] = {'L', 'o', 'r', 'e', 'm', '\0'};
    • char <varName>[] = "Lorem";
  • 可变指针,不可变内容

    • char * <varName> = "Lorem";
    • char const * <varName> = "Lorem";
    • const char * <varName> = "Lorem";
    • const char const * <varName> = "Lorem";
    • char const const * <varName> = "Lorem";
  • 不可变的指针,不可变的内容

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

结论:

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

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

答案 6 :(得分:1)

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

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

当你将char指针水果重置为&#34; apple&#34;它只是指向只读部分中的另一个内存位置,其中包含&#34; apple&#34;

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

答案 7 :(得分:1)

您将变量fruit指向其他字符串。您只是覆盖了地址(位置)。编译器将看到你的常量字符串&#34; banana&#34;和&#34; apple&#34;并将它们分别存储在程序存储器中。让我们说一下字符串&#34; banana&#34;转到位于地址1和&#34; apple&#34;存储到内存addesss 2。现在你做的时候:

fruit = "banana";

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

fruit = "apple";

编译器将分配2变量fruit,这意味着它指向存储字符串2的addess apple

答案 8 :(得分:0)

使用char *p="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);

}