这个程序怎么了?

时间:2009-11-06 05:17:49

标签: c string segmentation-fault

char *s = "hello ppl.";
for (i = 0; i < strlen(s); i++) {
    char c = s[i];
    if (c >= 97 && c <= 122) {
        c += 2;
        s[i] = c;
    }
}

我想将字符串旋转两个字符:"hello ppl." -> "jgnnq rrn."

我得到segmentation fault。代码有什么问题?

6 个答案:

答案 0 :(得分:31)

代码:

 char *s = "hello ppl.";

为您提供指向可能只读内存的指针。那是因为C中的字符串常量是不可修改的。当您尝试写入该内存时,您很可能会遇到分段违规。标准的相关部分(C99 6.4.5/6上的String literals)声明:

  

如果这些数组的元素具有适当的值,则未指定这些数组是否相同。如果程序试图修改这样的数组,则行为是未定义的。

因此,虽然内存 不是只读的,但你仍然试图通过修改它来破坏规则。

请改为尝试:

char s[] = "hello ppl.";

在概念上与:

相同
char s[11];               // for the whole string plus null terminator
strcpy (s, "hello ppl.");

换句话说,它会将您想要更改的字符串放入可写内存中。以下代码:

#include <stdio.h>
#include <string.h>
int main(void) {
    int i;
    char s[] = "hello ppl.";
    for (i = 0; i < strlen(s); i++) {
        char c = s[i];
        if (c >= 97 && c <= 122) {
            c += 2;
            s[i] = c;
        }
    }
    printf("%s\n",s);
    return 0;
}

根据需要为您提供"jgnnq rrn."

我想指出哪些不是致命的其他事情:

  • 使用97122等“魔术”数字通常不是一个好主意。使用'a'和'z'就像使用'a'和'z'一样简单,更清晰。
  • 如果你真的想要旋转,你不能盲目地将2加到'y'和'z'。你必须特别对待它们(减去24),以便它们正确映射到'a'和'b'。
  • C标准不保证字母字符是连续的。如果你知道你正在使用ASCII,你可能还可以,但我想我只是提到它。顺便说一句,它确保保证数字字符。

话虽如此,我宁愿使用如下的映射表:

#include <stdio.h>
#include <string.h>
int main (void) {
    char *lkupPtr, *strPtr;
    char str[] = "hello ppl.";
    const char * const from = "abcdefghijklmnopqrstuvwzyz";
    const char * const to   = "cdefghijklmnopqrstuvwzyzab";

    for (strPtr = str; *strPtr != '\0'; strPtr++)
        if (lkupPtr = strchr (from, *strPtr)) != NULL)
            *strPtr = to[(int)(lkupPtr - from)];

    printf("%s\n",str);
    return 0;
}

这样可以解决我上面提到的所有问题,如果您处于国际化环境(而不仅仅是纯ASCII或EDCDIC),您可以根据需要添加更多映射。

在我看来,这应该足够快,除了最苛刻的要求之外的所有要求(我的PC上每秒钟超过300万个字符)。如果您对性能的需求几乎无法满足,但又不想选择针对特定CPU的手工组装,您可以尝试类似以下内容。

它仍然完全符合C标准,但由于所有繁重的计算工作在开始时完成,因此可以提供更好的性能。它创建一个包含所有可能字符值的表,初始化它以便默认情况下每个字符都转换为自身,然后更改您感兴趣的特定字符。

删除翻译本身对字符的任何检查。

#include <stdio.h>
#include <string.h>
#include <limits.h>

static char table[CHAR_MAX + 1];
static void xlatInit (void) {
    int i;
    char * from = "abcdefghijklmnopqrstuvwzyz";
    char * to   = "cdefghijklmnopqrstuvwzyzab";
    for (i = 0; i <= CHAR_MAX; i++) table[i] = i;
    while (*from != '\0') table[*from++] = *to++;
}

int main (void) {
    char *strPtr;
    char str[] = "hello ppl.";

    xlatInit(); // Do this once only, amortize the cost.

    for (strPtr = str; *strPtr != '\0'; strPtr++)
        *strPtr = table[*strPtr];

    printf("%s\n",str);
    return 0;
}

答案 1 :(得分:6)

变量s指向只读存储器。这意味着它无法修改。你会想要使用:

char varname[] = "...";

需要注意:

char varname[] = "...";

将数据放在堆栈上。确保您没有返回指向函数本地数据的指针。如果是这种情况,您将需要查看malloc以在堆中分配内存。

另一个问题:

for (i = 0; i < strlen(s); i++) {...} is O(N^2)

原因是strlen(s)是每次循环时都执行的O(N)操作。改进将是:

int len = strlen(s);
for(i=0;i<len;i++) { ... }

这样我们只进行一次strlen(s)计算并重用结果。

答案 2 :(得分:5)

char *s = "hello ppl."中,您没有分配任何内存,而是指向可能驻留在程序的只读内存中的字符串。理想情况下应该是const char*。现在,如果您尝试修改它,它将崩溃。

答案 3 :(得分:5)

代码:

char *s = "hello ppl.";

在字符串表中创建一个条目,通常在代码段(程序的只读空间)中。任何尝试更改它都会通过尝试修改只读内存来导致段错误。创建/初始化要修改的字符串的适当方法是:

char s[] = "hello ppl.";

答案 4 :(得分:3)

正如其他人所说,

char *s = "hello ppl.";

指向只读内存,因为它是一个字符串文字。它应该是

char s[] = "hello ppl.";

在读写内存中创建一个数组并将字符串复制到其中。

忽略非ASCII字符集,可以最有效地解决问题:

void Convert(char *s)
{
  for(char *sp = s; *sp; sp++)
    if(*sp >= 'a' && *sp <= 'z')
      *sp = (*sp - 'a' + 2) % 26 + 'a';
}

如果您正在处理EBCDIC或任何其他不具有连续字母字符的字符集,您可以使用地图:

char *from = "abcdefghijklmnopqrstuvwxyz";
char *to   = "cdefghijklmnopqrstuvwxyzab";
char map[CHAR_MAX+1];

void Initialize()
{
  for(int i=0; from[i]; i++)
    map[from[i]] = to[i];
}

void Convert(char *s)
{
  for(char *sp = s; *sp; sp++)
    if(map[*sp])
      *sp = map[*sp];
}

编译器会将这些中的每一个优化为近乎最佳的汇编语言。

更新在原始问题中没有单独的Initialize()调用,所以我优化了代码以使“Initialize(); Convert(s);”尽可能快地。如果你能够提前调用Initialize()并且只关心“转换”的速度有多快;运行,最佳代码将首先填充数组,如下所示:

char *from = "abcdefghijklmnopqrstuvwxyz";
char *to   = "cdefghijklmnopqrstuvwxyzab";
char map[CHAR_MAX+1];

void Initialize()
{
  int i;
  for(i=0; i<=CHAR_MAX; i++)  // New code here fills the array
    map[i] = i;
  for(i=0; from[i]; i++)
    map[from[i]] = to[i];
}

void Convert(char *s)
{
  for(char *sp = s; *sp; sp++)  // 'if' removed
    *sp = map[*sp];
}

如果你调用“Initialize(); Convert(s);”这个修改过的代码慢了375%,但是如果你已经调用了Initialize()并且你只是定时“转换”,它会快3% ;”

答案 5 :(得分:0)

char *s = "hello ppl.";
in this s is pointing to string literal which is constant string 
as constant strings are stored on data segment of memory area (read only memory area)
so you can access anything from that area but if you want to modify 
os doesn't allow you and trap an segfault

modified code could be ::
char s[] = "hello ppl.";
for (i = 0; i < strlen(s); i++) {
    char c = s[i];
    if (c >= 97 && c <= 122) {
        c += 2;
        s[i] = c;
    }
}

in this s string stored on stack so you can modify,access there is no restriction here.