char *str="STRING"和char str[] = "STRING"的区别是什么?

30

我在编写一个简单的函数,用于从字符串中删除特定字符时,遇到了这个奇怪的问题:

void str_remove_chars( char *str, char to_remove)
{
    if(str && to_remove)
    {
       char *ptr = str;
       char *cur = str;
       while(*ptr != '\0')
       {
           if(*ptr != to_remove)
           {
               if(ptr != cur)
               {
                   cur[0] = ptr[0];
               }
               cur++;
           }
           ptr++;
       }
       cur[0] = '\0';
    }
}
int main()
{
    setbuf(stdout, NULL);
    {
        char test[] = "string test"; // stack allocation?
        printf("Test: %s\n", test);
        str_remove_chars(test, ' '); // works
        printf("After: %s\n",test);
    }
    {
        char *test = "string test";  // non-writable?
        printf("Test: %s\n", test);
        str_remove_chars(test, ' '); // crash!!
        printf("After: %s\n",test);
    }

    return 0;
}

我不明白的是为什么第二个测试失败了? 在我看来,第一种表示方式 char *ptr = "string"; 和这种方式是等价的: char ptr[] = "string";.

难道不是这样吗?


这是一篇非常好的关于此主题的文章:http://eli.thegreenplace.net/2009/10/21/are-pointers-and-arrays-equivalent-in-c/ - jyz
6个回答

52
这两个声明不同。 char ptr[] = "string"; 声明了一个大小为7的char数组,并用字符tring\0进行初始化。你可以修改该数组的内容。 char *ptr = "string"; 声明了ptr为一个char指针,并将其初始化为字符串常量"string"的地址,该字符串是只读的。修改字符串常量是一种未定义行为。你看到的(段错误)是未定义行为的一种表现。

而且对于不同的声明,sizeof(ptr)也会给出不同的结果。第一个将返回数组的长度,包括终止的空字符。第二个将返回指针的长度,通常为4或8。 - Prof. Falken
同样,在第二个位置上它也是正确的,即ptr的内容可以被改变。但是这些内容是指向文字常量的指针,而不是字符本身。 - Darron
3
+1,非常好的回答。同样重要的是要理解,对于char *ptr = "string";这个语句,指针ptr可以被指向其他位置,因此它所指向的内容可能会发生变化,但是字符组成的字面量"string"本身是无法改变的。 - dawg
值得一提的是性能问题。声明一个初始化的自动数组变量将在每次变量进入作用域时填充整个数组内容。声明一个初始化的自动指针变量只会在变量进入作用域时分配指针(单词写入)。如果字符串很长或块经常被访问(如循环的每次迭代),差异可能非常显著! - R.. GitHub STOP HELPING ICE
@AmigableClarkKant,实际上,sizeof(ptr)不是数组的长度,除非ptr被声明为char数组。如果ptr被定义为一个有3个元素的int数组,sizeof(ptr)将返回每个元素的sizeof(int)之和。 - 爱国者
@爱国者 是的,我知道你是个爱国者。 :-) 我在自己的代码中广泛使用该属性。 - Prof. Falken

6

严格来说,声明char *ptr只是保证你得到一个指向字符类型的指针。有些操作系统会将字符串作为编译后应用程序代码段的一部分,并将其设置为只读。问题在于,你对预定义的字符串的性质做出了一些假设(即它是可写的),而实际上,你从未显式地为该字符串创建内存。某些编译器和操作系统的实现可能允许你尝试做这件事。

另一方面,通过声明char test[],在此情况下,实际上为整个字符数组在堆栈中分配了可读写的内存。


3
据我所记,
char ptr[] = "string";

创建一个在堆栈上的字符串副本,所以这个字符串时可变的。

形式:

char *ptr = "string";

这只是向后兼容的实现

const char *ptr = "string";

你不被允许(就未定义的行为而言)修改它的内容。编译器可能会将这样的字符串放置在只读内存区域中。


3

char *test = "string test";是错误的,应该使用const char*。这段代码之所以能够编译,只是因为向后兼容的原因。由const char*指向的内存是只读内存,当你尝试写入它时,会引发未定义行为。另一方面,char test[] = "string test"在堆栈上创建了一个可写的字符数组。这与任何其他普通的本地变量一样,您可以对其进行写操作。


1
我不会说这是错的。你可能希望稍后将 test 指向一个可修改的字符串,并保留一个标志(在另一个变量中)来指示它是否已被替换为可修改的内容。但在大多数情况下,使用 const 可能是一个好的实践。 - R.. GitHub STOP HELPING ICE

0
char *str = strdup("test");
str[0] = 'r';

这是正确的代码,创建了一个可变字符串。str在堆中被分配了内存,值为“test”。


嗯。所以,每次执行该函数时,您都会分配5个字节? - Gui13

0

很好的回答@codaddict。

此外,sizeof(ptr)将针对不同的声明返回不同的结果。

第一个声明,数组声明,将返回数组的长度包括终止空字符。

第二个声明,char* ptr = "a long text...";将返回指针的长度,通常为4或8。


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