char*和const char*之间的区别是什么?

272

什么是两者之间的区别?

char* name
指向一个常量字符串字面值,并且
const char* name

在 C 语言中,你所说的“常量字符串字面值”是什么意思(不是 C++)? - gbulmer
1
char *name 可以指向一个常量字符串字面值。 - Iceman
“constant string literal” 中的 “constant” 是多余的,因为所有字符串字面值在理论上都是常量实体。可以将变量的内容设置为常量或可变的。 “const” 声明只会在编译时错误地抛出,如果您尝试更改由 “name” 指向的字符的内容。 - Cupcake
1
简单来说:"char *name" 中的 name 是指向字符的指针,即两者都可以在此更改。 "const char *name" 中的 name 是指向常量字符的指针,即指针可以更改但字符不能更改。 - akD
2
从右到左读取这些内容。 - JP Zhang
9个回答

561

char*指向一个可变的字符/字符串的可变指针。

const char*指向一个不可变的字符/字符串的可变指针。您无法更改此指针指向位置的内容。此外,当您尝试这样做时,编译器会给出错误消息。由于相同的原因,从const char *转换为char*已被弃用。

char* const是一个不可变的指针(它不能指向任何其他位置),但指向的位置的内容是可变的

const char* const是一个不可变的指针,指向一个不可变的字符/字符串。


4
通过在上述语句之后使用变量并参考该变量,可以消除混淆。 - ankit.karwasra
4
@ankit.karwasra,你还漏掉了一个:char const * - Pacerier
3
在运行时改变char *会导致段错误吗? - Divyanshu Maithani
1
那么如果我想让编译器在我错误地忘记并更改数据时报错,我就使用 const,对吗? - Accountant م
7
这取决于创建char *的位置。例如:char *s = "A string"将"A string"放入二进制文件的代码段(只读内存)中。向这个内存写入会导致段错误。但是,char *s = malloc(sizeof(char) * 10)在堆上分配内存,而这个内存区域是可写的,因此在写入时不会发生段错误。 - d4rwel
显示剩余2条评论

47
char *name

你可以更改name所指向的字符,也可以更改它所指向的字符。

const char* name
你可以改变指向name的字符,但你不能修改它所指向的字符。
更正:你可以改变指针,但不能修改name指向的字符(参见https://msdn.microsoft.com/en-us/library/vstudio/whkd4k6a(v=vs.100).aspx的"Examples")。 在这种情况下,const限定符适用于char,而不是星号。 根据MSDN页面和http://en.cppreference.com/w/cpp/language/declarations*前面的const是decl-specifier序列的一部分,而*后面的const是declarator的一部分。 一个声明说明序列可以跟随多个declarator,这就是为什么const char * c1, c2声明c1const char *c2const char的原因。
从评论中可以看出,你的问题似乎是关于指针指向字符串字面值时这两种声明之间的区别。
在这种情况下,你不能修改name所指向的字符,因为这可能会导致未定义的行为。 字符串字面值可能分配在只读内存区域(由实现定义),用户程序不应以任何方式修改它。任何尝试这样做都会导致未定义的行为。
因此,在这种情况下(与字符串字面值一起使用),这两种声明的唯一区别在于第二个声明会稍微优于第一个声明。编译器通常会在你尝试修改字符串字面值时给出警告。
在线示例:http://ideone.com/7ZdBk
#include <string.h>
int main()
{
    char *str1 = "string Literal";
    const char *str2 = "string Literal";
    char source[] = "Sample string";

    strcpy(str1,source);    //No warning or error, just Undefined Behavior
    strcpy(str2,source);    //Compiler issues a warning

    return 0;
}

输出:

cc1: 将警告作为错误来处理
prog.c: 在函数 'main' 中:
prog.c:9: 错误: 传递给 'strcpy' 的第1个参数从指针目标类型中丢失了限定符

请注意编译器对第二种情况发出警告,但对第一种情况没有发出警告。


谢谢。我混淆了常量字符串字面值,它被定义为:char* name = "String Literal";改变“String Literal”是未定义的行为。 - Iceman
@user1279782:等等!你是在说指向字符串字面值的指针吗?如果是这样,无论哪种情况下,你都不应该修改name指向的字符。这可能导致未定义行为。 - Alok Save
4
这个答案要么含糊不清,要么完全错误。我会把“你不能改变名称指向的字符,但是你可以修改它所指向的字符”的解释理解为无法修改指针本身,但可以修改它所指向的内存位置,这是不正确的:http://ideone.com/6lUY9s 如果你需要纯C语言版本,请参考:http://ideone.com/x3PcTP - shroudednight
1
@shroudednight:您需要更多地了解未定义行为,并区分允许和不应该做的事情。 :) - Alok Save
请进行更正:strcpy(str1,source); //编译器在此处发出警告,并且行为未定义。 strcpy(str2,source); //编译器发出错误。 - Devesh Agrawal
显示剩余2条评论

27
char mystring[101] = "My sample string";
const char * constcharp = mystring; // (1)
char const * charconstp = mystring; // (2) the same as (1)
char * const charpconst = mystring; // (3)

constcharp++; // ok
charconstp++; // ok
charpconst++; // compile error

constcharp[3] = '\0'; // compile error
charconstp[3] = '\0'; // compile error
charpconst[3] = '\0'; // ok

// String literals
char * lcharp = "My string literal";
const char * lconstcharp = "My string literal";

lcharp[0] = 'X';      // Segmentation fault (crash) during run-time
lconstcharp[0] = 'X'; // compile error

// *not* a string literal
const char astr[101] = "My mutable string";
astr[0] = 'X';          // compile error
((char*)astr)[0] = 'X'; // ok

1
根据问题,您的指针都没有指向“常量字符串字面值”。 - caf
1
值得注意的是,更改char *的值会导致分段错误,因为我们试图修改一个字符串字面量(它存在于只读内存中)。 - Divyanshu Maithani
1
你能解释一下最后一行吗?我没看懂。 - lorem1213
你能解释一下这最后一行代码吗:((char*)astr)[0] = 'X'; - undefined

13

无论将指向字符串字面值的指针声明为char *还是const char *,都不能修改字符串字面值。

然而,不同之处在于,如果指针是const char *,那么如果您试图修改所指向的值,编译器必须发出诊断警告;但是如果指针是char *,则不会发出警告。


1
在任何情况下,您都不能修改字符串字面量,无论它是声明为char *还是const char 。我同意程序员不应该尝试修改它,但您是说每个平台上的每个C编译器都会拒绝代码、安排代码在运行时失败或其他什么吗?我认为,一个文件可以有定义和初始化,另一个文件可能包含“extern ... name”,并且有“name = 'X';”。在“适当的操作系统”上,这可能会失败,但在嵌入式系统上,我期望它会执行某些特定于平台/编译器的操作。 - gbulmer
@gbulmer:在正确的C程序中,您无法修改字符串文字。试图修改它的不正确的C程序的结果无关紧要。 - caf
@gblumer:一个有用的定义是指那些不违反 C 语言标准所规定的任何限制的程序。换句话说,修改字符串常量的程序和对空指针进行解引用或进行除以 0 的操作一样是不正确的。 - caf
caf - 我想这可能是你的意思。然后,“在任何情况下,你都不能修改字符串字面量”似乎有些言过其实。准确地说,“在这两种情况下,C语言标准指定的约束已经被打破,无论如何......编译器或运行时系统不可能在所有情况下识别标准的违规行为。”我假设标准认为效果是未定义的? - gbulmer
@gbulmer:是的,这种行为是未定义的。我还要说,在正确的C程序中,您不能解引用空指针(无论它在某些特定实现上可能不会产生有害结果),我认为这不会被视为一个有争议的立场。 - caf
1
当一个标准无法明确表达任何内容时,我认为将行为定义为“未定义”似乎是恰当的边界和有帮助的。声称关系“正确的C程序”“不能解引用空指针”听起来等同于证明停机问题。但我不介意。我不会这样做并期望“逍遥法外” :-) - gbulmer

8

案例1:

char *str = "Hello";
str[0] = 'M'  //Warning may be issued by compiler, and will cause segmentation fault upon running the programme

上述代码将str指向程序二进制映像中硬编码的字面值“Hello”,该映像在内存中标记为只读,这意味着对此字符串字面值的任何更改都是非法的,并会引发分段错误。

情况2:

const char *str = "Hello";
str[0] = 'M'  //Compile time error

第三种情况:

char str[] = "Hello";
str[0] = 'M'; // legal and change the str = "Mello".

3

问题是什么是两者之间的区别

char *name

它指向一个常量字符串字面值,并且

const char *cname

即给定。
char *name = "foo";

并且

const char *cname = "foo";

这两者之间没有太大的区别,都可以看作是正确的。由于C代码的悠久历史,字符串文字一直被视为char[]类型,而不是const char[]类型,因此有很多旧代码也接受char *而不是const char *,即使它们不修改参数。

总体上,这两者的主要区别在于*cnamecname[n]将评估为const char类型的lvalue,而*namename[n]将评估为char类型的lvalue,它们是可修改的lvalue。符合标准的编译器需要在赋值的目标不是可修改的lvalue时产生诊断消息;对于char类型的lvalue赋值,它不必产生任何警告:

name[0] = 'x'; // no diagnostics *needed*
cname[0] = 'x'; // a conforming compiler *must* produce a diagnostic message

编译器不一定会在任一情况下停止编译;只要对于分配给cname[0],它产生一个警告即可。所得到的程序不是一个正确的程序。该构造的行为是未定义的。它可能会崩溃,甚至更糟的是,它可能不会崩溃,并且可能会改变内存中的字符串字面量。

2

第一个您可以更改,如果您想的话,但第二个则无法更改。请了解有关const正确性的信息(有一些很好的区别指南)。还有char const * name,您不能重定向它。


到底有什么可以改变的? - Antti Haapala -- Слава Україні

1
我在这里补充一下,最新的编译器,例如 VS 2022,不允许使用字符串字面值初始化 char*char* ptr = "Hello"; 会抛出一个错误,而 const char* ptr = "Hello"; 则是合法的。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接