【问题标题】:Whats wrong with this program?这个程序有什么问题?
【发布时间】:2009-11-06 05:17:48
【问题描述】:
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." -&gt; "jgnnq rrn."

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

【问题讨论】:

  • 注意:输出应该以“j”而不是“i”开头
  • 嗯,很明显是段错误! :-)
  • '你不能修改字符串文字'是什么问题。
  • 警告:paxdiablo 接受的答案包含比原始代码慢得多的代码,也比给出的其他答案慢。因此,如果您想要一个有效的解决方案,请往下看。
  • @Ray - 除非 OP 对数百万个字符进行编码,否则您的解决方案速度提高 10 倍可能不会有丝毫不同。

标签: c string segmentation-fault


【解决方案1】:

代码:

 char *s = "hello ppl.";

给你一个指向它可能只读内存的指针。这是因为 C 中的字符串常量是不可修改的。当您尝试写入该内存时,您很可能会遇到分段违规。标准的相关部分(C99 6.4.5/6 on 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”同样简单,意图更清晰。
  • 如果真的要旋转,不能一味的给'y'和'z'加2。您必须对它们进行特殊处理(减去 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 的手工组装,您可以尝试类似以下的方法。 p>

它仍然完全符合 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;
}

【讨论】:

  • 您可能想要包含 string.h ,因为您正在给出一个可以粘贴和编译的示例。
  • 如果代码使用 '&lt;ctype.h&gt;' 中的 'islower()' 会更好——因为所有的原件都按所示编写。
  • 好点,@Tim。完毕。 @JL,另一个好点,但对于我首选的从/到解决方案来说不是必需的。
  • @Ray,“更好”是一个主观术语,我更喜欢“更快”等可衡量的术语。是的,您的代码现在更快了,但是,在您更改数组大小之前,它是 not 可移植的(因此是 cmets)。至于每秒处理 3 或 300 亿个字符是否真的存在差异,这取决于使用它的人。在我看来,除非您实际上必须处理非常大的数量(许多,许多 十亿),否则不会有什么不同。但是,正如我所说,如果您需要这种速度,那就进行优化——这将包括完全绕过 C 和手工制作汇编程序。
  • @paxdiablo,我想你的意思是const char * const 然后:) 如果你把 const 放在星号之前,那么它出现的顺序是任意的,(例如,我通常更喜欢char const *const),所以你有两个常量之一是多余的:)
【解决方案2】:

变量 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) 计算并重复使用结果。

【讨论】:

  • +1 用于评论循环中的 strlen()。我并不完全相信静态内存这个术语。只读内存可能会更好。
  • GCC 用-O2 优化了strlen。我猜结果在编译时是已知的,因为 s 在调用 strlen 时是已知的,它指向只读内存。
  • 那是编译器的...勇敢...。它根本不指向只读内存(char x[] = "ff"; 将它放在堆栈上)。我会对 gcc 如何解决这个问题感兴趣。还是您的意思是原文中的 strlen 是只读的?
  • @paxdiablo:我的意思是 OP 在他的问题中发布的原件。
【解决方案3】:

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

【讨论】:

  • 编译时,字符串文字在只读静态字符串表中键入为 const char*。对这个@Naveen 的好调用:字符串文字的类型信息在很多人身上都丢失了。
【解决方案4】:

代码:

char *s = "hello ppl.";

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

char s[] = "hello ppl.";

【讨论】:

    【解决方案5】:

    正如其他人提到的,

    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() 并且只关心“Convert(s);”的速度有多快运行,最优代码将首先填充数组,如下所示:

    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() 并且只计时“Convert(s)”,则速度会快 3% ;"。

    【讨论】:

    • 除了一个字符集可能有超过 256 个字符的问题,这还不错。但是为什么不将 Initialize 更改为首先设置 map[i] = i for i= 0..255,然后更改 from 和 to 中特定字符的映射?这样,您的查找代码就可以完全放弃“如果”。
    • @paxdiablo:这个解决方案已经比你的快得多了(在 Intel Core Duo T7400 VC++ 9 上使用给定的输入数据为 2.38ns vs 3.32ns)。不幸的是,您的建议会减慢速度,而不是加快速度。我尝试预先填充整个地图并省略“if”的代码,结果在 11.31ns 时速度要慢得多,几乎慢了 5 倍。这就是我使用“如果”的原因。当然,如果字符串更长或者初始化是一个单独的步骤,那么完整的映射会更快(我将其计时为 0.35ns,并提前完成了初始化步骤)。
    • 修复了硬编码的 256,使其可移植到 9 位和 16 位字符类型的机器上
    • +1 获得漂亮干净的代码。不知道为什么这是-1。我喜欢你的和 paxdiablo 的。
    • @Ray,你说你添加了if因为你的代码会因为完整的地图而变慢因为你打电话给@987654327 @ 每一次。但据我所知,在你的代码中你没有这样做。您是否错过了将Initialize(); 放在某个地方?
    【解决方案6】:
    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.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-25
      • 1970-01-01
      • 1970-01-01
      • 2019-12-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多