【问题标题】:C remove special characters from stringC从字符串中删除特殊字符
【发布时间】:2019-11-09 18:06:29
【问题描述】:

我是 C 的新手,我创建了一个函数,可以从字符串中删除特殊字符并返回一个新字符串(没有特殊字符)。

乍一看,这似乎运行良好,我现在需要在一个(巨大的)文本文件(一百万个句子)的行上运行这个函数。在几千行/句子(大约 4,000 行)之后,我得到了一个段错误。

我在 C 语言中的内存分配和字符串方面没有太多经验,我试图找出我的代码的问题,不幸的是没有任何运气。 代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

char *preproccessString(char *str) {
    // Create a new string of the size of the input string, so this might be bigger than needed but should never be too small
    char *result = malloc(sizeof(str));
    // Array of allowed chars with a 0 on the end to know when the end of the array is reached, I don't know if there is a more elegant way to do this
    // Changed from array to string for sake of simplicity
    char *allowedCharsArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    // Initalize two integers
    // i will be increased for every char in the string
    int i = 0;
    // j will be increased every time a new char is added to the result
    int j = 0;
    // Loop over the input string
    while (str[i] != '\0') {
        // l will be increased for every char in the allowed chars array
        int l = 0;
        // Loop over the chars in the allowed chars array
        while (allowedCharsArray[l] != '\0') {
            // If the char (From the input string) currently under consideration (index i) is present in the allowed chars array
            if (allowedCharsArray[l] == toupper(str[i])) {
                // Set char at index j of result string to uppercase version of char currently under consideration
                result[j] = toupper(str[i]);
                j++;
            }
            l++;
        }
        i++;
    }
    return result;
}

这是程序的其余部分,我认为问题可能在这里。

int main(int argc, char *argv[]) {
    char const * const fileName = argv[1];
    FILE *file = fopen(fileName, "r");
    char line[256];

    while (fgets(line, sizeof(line), file)) {
        printf("%s\n", preproccessString(line)); 
    }

    fclose(file);

    return 0;
}

【问题讨论】:

  • char *result 应该分配到 str + 1 char *result = malloc(strlen(str) + 1); 的长度
  • @MichaelBianconi 这并没有解决问题,它仍然在同一位置给我一个段错误。
  • 为什么不用像tr这样的预写工具来做呢?
  • 你能不能逗我一下,在result[j] = toupper(str[i])); j++;之后加一个break;
  • 您没有向result 字符串添加空终止符。将result[i] = 0; 放在return result; 之前

标签: c string replace char segmentation-fault


【解决方案1】:

以下建议代码:

  1. 干净编译
  2. 执行所需的功能
  3. 正确检查错误
  4. 正确检查输入字符串参数的长度
  5. 利用strchr()的特性也检查终止NUL字节
  6. 限制局部变量的可见范围
  7. 调用函数应该通过将返回值传递给free()来正确清理
  8. 调用函数应检查返回值是否为 NULL
  9. 在进行隐式转换时通知编译器用户知道并接受。
  10. allowedCharsArray 移动到“文件静态范围”,因此不必在每次通过循环时重新初始化并标记为“const”以帮助编译器捕获错误

现在建议的代码:(注意:按 cmets 编辑)

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

char *preproccessString(char *str) 
{
    // Create a new string of the size of the input string, so this might be bigger than needed but should never be too small
    char *result = calloc( sizeof( char ),  strlen(str)+1);
    if( !result )
    {
        perror( "calloc failed" );
        return NULL;
    }

    // Array of allowed chars 
    static const char *allowedCharsArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    // Loop over the input string
    for( int  j=0, i=0; str[i]; i++) 
    {
        if( strchr( allowedCharsArray, (char)toupper( str[i] ) ) )
        {
            // Set char at index j of result string to uppercase version of char currently under consideration
            result[j] = (char)toupper(str[i]);
            j++;
        }
    }
    return result;
}

【讨论】:

  • 你觉得 preproccessString(const char*) 怎么样?
  • 发布的代码不能干净地编译:result[j] = '\0'; 指的是仅在for 语句中定义的j。我一直犯这个错误,我希望这些变量的范围不同,但标准就是这样。
  • toupper(str[i]) 应该是 toupper((unsigned char)str[i]) ,以便在默认签名 char 的架构上具有完全定义的行为。
  • 我同意,'j' 的范围需要扩大。作为事后的想法,我添加了 NUL 终止符字节的附加。我真正应该做的是将malloc() 替换为calloc()。我已经相应地编辑了答案
  • 函数:toupper() 需要 int 作为参数,因此 str[i] 将被正确提升 但是,将其转换为 unsigned char 是无用的,并且与函数的语法不匹配:toupper()
【解决方案2】:

您的代码中存在一些主要问题:

  • 分配的内存量不正确,sizeof(str)指针 中的字节数,而不是它指向的字符串的长度,这也是不正确的。你应该写char *result = malloc(strlen(str) + 1);

  • preproccessString 中分配的内存永远不会被释放,这会导致内存泄漏,并可能导致程序在非常大文件上耗尽内存。

  • 您没有在result 字符串的末尾设置空终止符

次要问题:

  • 您不检查文件名是否通过,也不检查fopen() 是否成功。
  • preproccessString有错别字,应该是preprocessString
  • 您可以通过传递适当大小的目标数组来避免内存分配。
  • 您可以使用 isalpha 而不是测试每个字母
  • 在将char 值传递给toupper 时,您应该将它们转换为unsigned char,因为char 可能是有符号类型,而对于除EOF 之外的负值,toupper 是未定义的。
  • 您的源文件中的 cmets 太多,其中大部分都很明显,但会降低代码的可读性。

这是修改后的版本:

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

// transform the string in `str` into buffer dest, keeping only letters and uppercasing them.
char *preprocessString(char *dest, const char *str) {
    int i, j;
    for (i = j = 0; str[i] != '\0'; i++) {
        if (isalpha((unsigned char)str[i])
            dest[j++] = toupper((unsigned char)str[i]);
    }
    dest[j] = '\0';
    return dest;
}

int main(int argc, char *argv[]) {
    char line[256];
    char dest[256];
    char *filename;
    FILE *file;

    if (argc < 2) {
        fprintf(stderr, "missing filename argument\n");
        return 1;
    }
    filename = argv[1];
    if ((file = fopen(filename, "r")) == NULL) {
        fprintf(stderr, "cannot open %s: %s\n", filename, strerror(errno));
        return 1;
    }
    while (fgets(line, sizeof(line), file)) {
        printf("%s\n", preprocessString(dest, line)); 
    }
    fclose(file);

    return 0;
}

【讨论】:

    【解决方案3】:

    你有几个问题。

    1. 您没有分配足够的空间。 sizeof(str) 是指针的大小,而不是字符串的长度。您需要使用
    char *result = malloc(strlen(str) + 1);
    

    + 1 用于终止空字节。

    1. 您没有在结果字符串中添加终止空字节。添加
    result[j] = '\0';
    

    return result;之前

    1. 一旦您发现该字符与允许的字符匹配,则无需继续遍历其余允许的字符。在j++ 之后添加break

    2. 您的main() 函数永远不会释放preprocessString() 的结果,因此您可能内存不足。

    while (fgets(line, sizeof(line), file)) {
        char *processed = preproccessString(line);
        printf("%s\n", processed); 
        free(processed);
    }
    
    

    如果你让调用者传入结果字符串,而不是在函数中分配它,你可以解决大多数这些问题。只需在main() 函数中使用两个char[256] 数组即可。

    int main(int argc, char *argv[])
    {
        char const* const fileName = argv[1];
        FILE* file = fopen(fileName, "r");
        char line[256], processed[256];
    
        while (fgets(line, sizeof(line), file)) {
            processString(line, processed);
            printf("%s\n", processed); 
        }
    
        fclose(file);
    
        return 0;
    }
    

    然后只需更改函数,使参数为:

    void preprocessString(const char *str, char *result)
    

    【讨论】:

    • 我很确定它应该是result[j] = '\0';,但是我尝试了 j 和 i,我在 malloc() 中添加了 1,然后我添加了中断,同时仍然是一个段错误位置。
    • 见我上面的评论。
    • 发布完整的节目。
    • @b3nj4m1n:由于此答案中给出的更正没有帮助,请向我们展示调用代码。你会在任何地方释放结果吗?
    • 还有整个没有释放分配的内存的事情,虽然它看起来不像示例程序泄漏到足以在仅 4000 行之后耗尽堆。
    【解决方案4】:

    一个好的经验法则是确保每个 malloc/calloc 调用都有一个空闲的。

    此外,Valgrind 是一个很好的工具来记录未来。它非常擅长捕捉这类错误。

    【讨论】:

      【解决方案5】:

      我认为问题在于您使用的是从堆中分配内存的 malloc,并且由于您一次又一次地调用此函数,因此内存不足。 要解决此问题,您必须在 preprocessString 函数返回的指针上调用 free() 函数 在你的主块中

      char *result=preprocessString(inputstring);
      //Do whatever you want to do with this result
      free(result);
      

      【讨论】:

      • 你是我的英雄...我从 printf 中调用了该函数,所以我假设它默认会被释放...
      • @b3nj4m1n C 不进行任何自动垃圾回收。
      • 但是除非你的文件有很多兆字节,否则你不太可能耗尽内存。
      • 这个答案不完整,您仍然需要解决我在答案中提到的所有其他问题。
      • 释放分配的内存似乎可以解决问题,但程序仍然有未定义的行为,除了字符串很短的文件:分配的内存量不正确,应该是char *result = malloc(strlen(str) + 1);跨度>
      猜你喜欢
      • 2011-04-11
      • 2016-01-23
      • 2014-05-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多