如果仅读取char*s,为什么我可以覆盖它们



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

char* fruit = "banana";
printf("fruit is %sn", fruit);
fruit = "apple";
printf("fruit is %sn", fruit);

然后它可以很好地编译并给了我:

fruit is banana
fruit is apple

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

呈现的代码片段不会更改字符串文字本身。它仅更改存储在指针fruit的值。

您可以想象这些行

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

以下方式

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

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

另一方面,如果您尝试写

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

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

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

7,如果它们的阵列是否明显,则未指定元素具有适当的值。如果程序尝试修改这样的数组,行为不确定。

在您的程序中,表达式 "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"的后缀发生在其他地方;换句话说,共享相同的存储空间。修改文字可能会产生令人惊讶的效果;修改可以出现在其他文字中。(

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

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

或:

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

如果我们忽略了数组大小,它将自动从初始化字符串字面化的大小,并包含用于null终止字节的空间。在该功能中,我们需要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

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以指向其他对象,则编译器会大喊大叫。

基本上,当您执行

char* fruit = "banana";

您将指针fruit设置为"香蕉"的第一个字母。当打印出来时,c基本上从" b"开始,并继续打印字母,直到登录 null字符。

然后说

fruit = "apple";

您已经将指针fruit更改为" Apple"

首先,首先,char*未读取 - 仅有的。char * const s是。它们与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.

定义c字符串(aka char array,doup dould quotations "..."(时,以下面的格式:

char * <varName> = "<someString>"

只有数组的元素是不可变的(无法更改其内容(。换句话说,<varName>具有const char *类型(朝向仅读取内存的可变指针(。每当您使用双引号<varName> = "<otherString>"调用分配运算符时,都会自动更改指针值。以下示例应详细概述不同的可能性:

#include <stdio.h>
int main()
{
    char * var_1 = "Lorem";
    printf("1. %s , %pn", var_1, var_1); // --> 1. Lorem , 0x400640
    var_1 = "ipsu";
    printf("2. %s , %pn", var_1, var_1); // --> 2. ipsu , 0x400652
    // var_1[0] = 'x'; // --> Segmentation fault
    var_1++;
    printf("3. %s , %pn", var_1, var_1); // --> 3. psu , 0x400653
    char var_2[] = {'L', 'o', 'r', 'e', 'm', ''};
    printf("4. %s , %pn", var_2, var_2); // --> 4. Lorem , 0x7ffed0fc5381
    var_2[0] = 'x';
    printf("5. %s , %pn", var_2, var_2); // --> 5. xorem , 0x7ffed0fc5381
    // var_2++; //error: lvalue required as increment operand
    char var_3[] = "Lorem";
    printf("6. %s , %pn", var_3, var_3); // --> 6. Lorem , 0x7ffe36a42d5c
    // var_3 = "ipsu"; // --> error: assignment to expression with array type
    var_3[0] = 'x';
    printf("7. %s , %pn", 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 , %pn", var_5, var_5); // --> Lorem , 0x400720
    var_5 = "ipsu";
    printf("9. %s , %pn", var_5, var_5); // --> ipsu , 0x400732
    // var_5[0] = 'x'; // --> error: assignment of read-only location
    const char * var_6 = "Lorem";
    printf("10. %s , %pn", var_6, var_6); // --> 10. Lorem , 0x400760
    var_6 = "ipsu";
    printf("11. %s , %pn", 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 , %pn", var_7, var_7); // --> 12. Lorem , 0x400760
    var_7 = "ipsu";
    printf("13. %s , %pn", 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 , %pn", var_8, var_8); // --> 14. Lorem , 0x400790
    var_8 = "ipsu";
    printf("15. %s , %pn", 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', ''};
    // 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', ''};
    // 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', ''};
    • 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', ''};
    • char const <varName>[] = {'L', 'o', 'r', 'e', 'm', ''};
    • const char <varName>[] = "Lorem";
    • char const <varName>[] = "Lorem";

结论:

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

我试图在此处详细介绍C的阵列行为。

您将变量fruit指向另一个字符串。您仅覆盖地址(位置(。编译器将看到您常数的字符串"香蕉"one_answers" Apple",并将它们分开存储在程序内存中。假设字符串"香蕉"进入位于地址1的内存单元,而" Apple"存储在存储器ADDESS 2中。现在当您这样做:

fruit = "banana";

编译器将仅将1分配给变量fruit,这意味着它指向地址1,这引起了字符串banana的困扰。当您这样做时:

fruit = "apple";

编译器将分配2变量fruit,这意味着它指向ADDESS 2,其中String apple被存储。

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

首先定义char* fruit = "banana"时,您基本上将fruit作为指向常数字符的指针。字符串的7个字节(包括零终止(位于对象文件的.ro部分中(显然,截面名称会根据平台而变化(。

当您将Char指针水果重置为" Apple"时,它只是指向仅读取部分中的另一个内存位置,其中包含" Apple"

从本质上说,当您说水果是常数时,它是指fruit是指向const内存的指针。如果您将其定义为const pointer to a const string: -
char* const fruit = "banana";
编译器将阻止您将其重置为" Apple"

当您使用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%pn", p);
    p="Apple";
    printf("p contains address of string constant 'Apple' at 0x%pn", p);
}

最新更新