为什么这段字符串反转的C代码会导致分段错误?

31

我正在尝试编写代码来原地反转一个字符串(只是为了提高C编程和指针操作的能力),但我无法弄清楚为什么会出现段错误

#include <string.h>

void reverse(char *s);

int main() {
    char* s = "teststring";
    reverse(s);

    return 0;
}

void reverse(char *s) {
    int i, j;
    char temp;

    for (i=0,j = (strlen(s)-1); i < j; i++, j--) {
        temp = *(s+i);     //line 1
        *(s+i) = *(s+j);   //line 2
        *(s+j) = temp;     //line 3
    }
}

导致分割错误的是第2和第3行。我知道可能有更好的方法来解决这个问题,但我对于找出在我的代码中具体导致分割错误的原因很感兴趣。

更新:按要求包含了调用函数。


1
Segfault几乎总是意味着您正在尝试取消引用空指针。通过GDB运行您的代码,找到它发生段错误的那一行,并查看哪个指针在那里为null(0x000000)。 - Matt Ball
另外,你为什么要使用两个变量(i和j)?你完全可以用计数器来实现。 - Matt Ball
你能发布创建char*并将其传递给reverse()函数的代码吗? - noctonura
3
不要在循环条件中使用strlen() - Carl Norum
3
如果你仔细看的话,上面的代码示例中strlen并没有在循环条件中使用。 - AnT stands with Russia
显示剩余4条评论
8个回答

53

仅从代码上看无法确定问题。很可能,您正在传递指向无效内存、不可修改内存或其他类型的内存的指针,该内存不能以此处处理它的方式进行处理。

你是怎么调用你的函数的?

补充说明:您正在传递指向字符串字面值的指针。字符串字面值是不可修改的。您无法颠倒一个字符串字面量。请传递指向可修改字符串的指针。

char s[] = "teststring";
reverse(s); 

这里已经解释得非常清楚了。"teststring"是一个字符串字面值。字符串字面值本身是一个不可修改的对象。实际上,编译器可能(并且会)将其放在只读内存中。当你像那样初始化一个指针时

char *s = "teststring";

指针直接指向字符串字面值的开头。一般情况下,任何试图修改指针所指向内容的尝试都会失败。你可以读取它,但不能写入它。因此,强烈建议仅使用指向const类型的指针来指向字符串字面值。

const char *s = "teststring";

但是当你声明你的s为:

char s[] = "teststring";

你得到了一个完全独立的数组s,它位于普通可修改的内存中,并使用字符串字面值进行初始化。这意味着那个独立的可修改数组s将从字符串字面值中被复制其初始值。之后,你的s数组和字符串字面值将继续作为完全独立的对象存在。该字面值仍然是不可修改的,而s数组可修改。

基本上,后者声明在功能上等同于:

char s[11];
strcpy(s, "teststring");

这个方法有效。谢谢。但是你能否详细解释一下两种字符串初始化的区别呢?也就是说,为什么我这种方式创建了一个字符串字面量,而使用“数组”语法创建的是可修改的字符串? - james
1
@james 字符串字面量存储在只读存储器中。但是,通过执行"char []s = ...",您正在声明一个数组并将其初始化为字面量,而不是获取指向字面量的指针。 - asveikau
@james:请查看我回复中的附加文本。 - AnT stands with Russia

10

你的代码可能会因为多种原因而出现段错误。以下是我想到的一些可能性:

  1. s为空
  2. s指向一个只读内存中保存的const字符串
  3. s没有以NULL结尾

我认为第2种情况最有可能。您能否展示reverse的调用站点?

编辑

基于您的示例,答案肯定是#2。在C / C ++中,字符串文字是不可修改的。正确的类型实际上是const char *而不是char *。您需要将可修改的字符串传递到该缓冲区中。

快速示例:

char* pStr = strdup("foobar");
reverse(pStr);
free(pStr);

字面字符串 "teststring" 存储在只读内存中的某个位置,您不被允许对其进行写操作。这曾经是一种常见的做法,即分配一些内存的一种方式,但大多数现代系统都不允许这样做。JaredPar的示例有效,因为strdup分配了一块内存,而您则拥有它。 - Tim Allman

3

你是否正在测试类似这样的东西?

int main() {
    char * str = "foobar";
    reverse(str);
    printf("%s\n", str);
}

这使得str成为一个字符串常量,你可能无法编辑它(对我来说会导致段错误)。如果你定义char * str = strdup(foobar),它应该可以正常工作(对我来说是这样的)。

3

你的声明完全是错的:

char* s = "teststring";

“teststring”存储在代码段中,该段是只读的,就像代码一样。同时,s是指向“teststring”的指针,你试图更改只读内存范围的值,因此会出现分段错误。

但是,如果使用:

char s[] = "teststring";

s被初始化为“teststring”,当然这个字符串在代码段中,但是在这种情况下还有一个额外的复制操作,将其复制到堆栈中。


1

请参阅C FAQ列表中的问题1.32

What is the difference between these initializations?

char a[] = "string literal";
char *p  = "string literal";

My program crashes if I try to assign a new value to p[i].

Answer:

A string literal (the formal term for a double-quoted string in C source) can be used in two slightly different ways:

As the initializer for an array of char, as in the declaration of char a[], it specifies the initial values of the characters in that array (and, if necessary, its size).

Anywhere else, it turns into an unnamed, static array of characters, and this unnamed array may be stored in read-only memory, and which therefore cannot necessarily be modified. In an expression context, the array is converted at once to a pointer, as usual (see section 6), so the second declaration initializes p to point to the unnamed array's first element.

Some compilers have a switch controlling whether string literals are writable or not (for compiling old code), and some may have options to cause string literals to be formally treated as arrays of const char (for better error catching).

(emphasis mine)

另请参阅回归基础,作者为乔尔


1
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。- 来自审查 - Sree
1
@Sree,现在应该已经修复了。 - Sinan Ünür

0

正如上面提供的一些答案,字符串内存是只读的。然而,一些编译器提供了一个选项来编译可写字符串。例如,使用gcc,3.x版本支持-fwritable-strings,但新版本不支持。


0
你使用哪个编译器和调试器?如果使用gcc和gdb,我会用-g标志编译代码,然后在gdb中运行它。当它发生段错误时,我会使用回溯命令(gdb中的bt命令)并查看哪一行代码引起了问题。此外,我会逐步运行代码,同时在gdb中“观察”指针值,并知道问题出现的确切位置。
祝你好运。

-1

我认为strlen不能正常工作,因为s没有以NULL结尾。所以你的for循环的行为不是你期望的那样。 由于strlen的结果将优于s长度,你会在不应该写入的内存中写入。

此外,s指向只读内存中保存的常量字符串。你不能修改它。尝试使用gets函数初始化s,就像在strlen示例中所做的那样。


1
s为什么不是以空字符结尾的?字面字符串始终以空字符结尾。 - Jeremy Stein

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