我的课程告诉我,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
为什么呢?我误解了只读是什么意思吗?很抱歉,如果这很明显,但我不熟悉编码,我无法在线找到答案。
答案 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);
}