这个程序怎么了?

Moe*_*oeb 0 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我得到了一个segmentation fault.代码有什么问题?

pax*_*blo 31

代码:

 char *s = "hello ppl.";
Run Code Online (Sandbox Code Playgroud)

给你一个指向它可能只读内存的指针.那是因为C中的字符串常量是不可修改的.当您尝试写入该内存时,您很可能会遇到分段违规.标准(C99 6.4.5/6on String literals)的相关部分指出:

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

所以,虽然内存不具有被只读,你仍然试图通过修改它打破规则.

试试这个:

char s[] = "hello ppl.";
Run Code Online (Sandbox Code Playgroud)

这在概念上与以下相同:

char s[11];               // for the whole string plus null terminator
strcpy (s, "hello ppl.");
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

"jgnnq rrn."按你的意愿给你.

还有一些我想指出哪些不是致命的事情:

  • 使用像' 97和'这样的'魔术'数字通常不是一个好主意122.使用'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;
}
Run Code Online (Sandbox Code Playgroud)

这样可以解决我上面提到的所有问题,如果您处于国际化环境(而不仅仅是纯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;
}
Run Code Online (Sandbox Code Playgroud)


Eva*_*van 6

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

char varname[] = "...";
Run Code Online (Sandbox Code Playgroud)

需要注意:

char varname[] = "...";
Run Code Online (Sandbox Code Playgroud)

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

另一个问题:

for (i = 0; i < strlen(s); i++) {...} is O(N^2)
Run Code Online (Sandbox Code Playgroud)

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

int len = strlen(s);
for(i=0;i<len;i++) { ... }
Run Code Online (Sandbox Code Playgroud)

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


Nav*_*een 5

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


Mat*_*att 5

代码:

char *s = "hello ppl.";
Run Code Online (Sandbox Code Playgroud)

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

char s[] = "hello ppl.";
Run Code Online (Sandbox Code Playgroud)