修改字符串字面值

12
为什么运行这个程序没有输出?
#include<stdio.h>

int main()
{
    char* t="C++";
    t[1]='p';
    t[2]='p';
    printf("%s",t);
    return 0;
}

10
main 返回的是 int 类型,而不是 void 类型。 - GManNickG
2
如果没有'\n'printf()输出可能会在下一条语句执行之前不显示(默认情况下,流stdout是行缓冲的)。请尝试使用printf("%s\n", t);printf("%s", t); fflush(stdout); - pmg
@pmg:文本输出流是否需要关闭 '\n' 是实现定义的。如果需要,那么即使使用 fflush(stdout),行为也是未定义的。最好的方法是直接打印换行符,这样更容易和可靠。(在许多系统上,换行符不是必需的,但如果缺少它,输出将与下一个 shell 提示相邻,这很丑陋。) - Keith Thompson
我建议您接受Keith的答案,这是一个更好的答案,我已经点赞了它。 - Shafik Yaghmour
4个回答

23

C字符串字面量创建一个匿名的char数组。任何尝试修改该数组的行为都是未定义的。理想情况下,这应该通过将数组设为const来实现,但C并不总是有const,而将其添加到字符串字面量中会破坏现有代码。

char* t="C++";

这是合法的,但潜在风险很高。包含字符'C', '+', '+', '\0'的数组可能存储在读写内存或只读内存中,取决于编译器的心情。

t[1]='p';

在这里,您的程序行为是未定义的,因为您正在尝试修改字符串常量的内容。编译器不需要在编译时或运行时警告您,也不需要采取任何措施使其“工作”。

如果您想让编译器知道该字符串是只读的,最好自己添加const限定符:

const char *t = "C++";

编译器应该至少在您尝试修改字符串字面值时警告您,至少如果您通过t尝试这样做的话。如果您想要能够修改它,您应该将t设置为可写数组。
char t[] = "C++";

与其将t指针指向"C++"的开头,这样做将t变成一个数组,将"C++"的内容复制到其中。只要不超出其范围,您可以随意处理t的内容。
对于您的代码,以下是一些额外的注释:
#include<conio.h>

<conio.h> 是专门为 Windows (和 MS-DOS) 设计的。如果您的程序不需要在其他系统上运行,那么保留它是可以的。如果您希望它具有可移植性,请将其删除。

void main()

这是错误的;正确的声明应该是 int main(void) (在C语言中,int main() 是有问题的,但在C++中是正确的。)
printf("%s",t);

您的输出应以换行符结尾;如果没有,可能会发生各种各样的不良后果。请将其更改为:
printf("%s\n", t);

(该问题在闭合代码 }之前包含以下行:)
getch();

后来OP将其删除了。这是特定于Windows的。在Windows开发系统中,保持输出窗口不关闭可能是必要的,这是一个不幸的问题。如果您想要更标准的方法,getchar() 只需从标准输入读取一个字符,并让您按下 Enter 键完成(尽管它不会给您提示)。或者,如果您从IDE或命令提示符中运行程序,则大多数情况下它们不会立即关闭窗口。

最后,由于main返回类型为int,因此实际上应该这样做;您可以添加

return 0;

在闭合的}之前加上这句话并不是必须的,但这并不是一个坏主意。(C99添加了隐式的return 0;,但Microsoft不支持C99。)(更新于2019年:Microsoft对C99功能的支持略有改善。我不确定是否需要return 0;。)

对于 char s[] = "foobar";,将字符串文字内容复制到数组中是在编译时还是运行时完成的? - torez233
1
@torez233 是的。在抽象机器中,字符串字面值“foobar”会导致存在一个类型为char[7]的静态数组,其内容被复制到也是char[7]类型的s中。根据s的定义方式(特别是它是否是静态的),编译器可以生成不执行复制的代码,只要结果行为正确即可。没有任何东西可以引用对应于字符串字面值的匿名数组的地址,因此它实际上不需要存在于内存中。 - Keith Thompson

15

“C++”是一个字符串字面量,存储在只读位置,因此不能被修改。由此可见-

char* t="C++"; // t is pointing to a string literal stored in read only location

相反,你应该有 -
char t[] = "C++" ;  // Copying the string literal to array t

实际上要做的是 -

t[1] = 'p' ;

1
为什么没有错误时我们不会收到警告?代码编译正确,但执行时应用程序崩溃。 - Jon Wheelock

9

您的代码还存在其他几个问题。

  1. 指针通常用于指向已经存在的数据,这样您就可以像这样使用它:

    char arr[] = "C++";

    char* t = &arr[0];

此外,可修改的,

t[1] = 'p';

t[2] = 'p';

当然,使用字符串有一种特殊的方式——让指针指向一个字符串常量。就像你所使用的方式:

char *t = "C++";   // you cannot modify it in most operating systems
t[1] = 'p';
t[2] = 'p';

有一种更好的使用方式,更加便携和易于理解:

const char* t="C++"; 

2. 你的代码有很多不符合C标准的地方。

#include <stdio.h> // You'd better add a space between, for this is a good coding convention
#include <conio.h> // only supported by vc/vs in windows, you can use getchar() instead

int main()  // main returns int
{
    char* t = "C++";

    t[1] = 'p';
    t[2] = 'p';
    printf("%s\n", t);  // it's a good habit to add a '\n' when printing a string
    getchar();   // getchar() is supported by c standard library 

    return 0; // return 0 here
}

3.关于打印字符串

Linux是行缓冲的(如果您使用Windows,则忽略此信息:P),为了在控制台中更易于阅读,最好在您打印的字符串末尾添加'\n':

printf("%s\n",t);

如果你不想在字符串后面有回车符,在Windows中可以像这样使用:printf("%s",t); 在Linux中,您应该在stdlib.h中添加fflush()函数。
printf("%s",t);
fflush(stdout);

1
char* t="C++"; 没有任何问题。字符串字面量提供了一个静态分配的4个元素数组形式的“已经存在的数据”。(指针应该是 const,但不一定非得这样。) - Keith Thompson
fflush 声明在 <stdio.h> 中,而不是 <stdlib.h>。无论如何,最好打印换行符;如果您不想打印,那么还是应该这样做。 - Keith Thompson
#include<stdio.h> 是完全可移植的——但添加空格确实可以提高可读性。没有必要调用 fflush(stdout),当程序终止时所有输出文件都会被刷新。 - Keith Thompson

0

一些标准运行库的版本需要使用\n来使输出出现。

printf("%s\n",t);

2
我认为这根本不是问题。 - Sadique
4
我明白了。我回答了我认为被问到的问题。 - wallyk
事实证明,情况并非如此。 - Sadique
@Sadique:来自未来7年的问候!缺少的\n不是问题,但可能会成为一个问题。"最后一行是否需要终止换行符是实现定义的。"--N1570 7.21.2p2。如果实现需要一个终止换行符而程序没有提供,则行为是未定义的。在Windows和POSIX/Unix/Linux下,它将只打印字符串而没有换行符(如果将shell提示符连接到输出上,则可能会造成视觉上的混淆)。 - Keith Thompson

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