奇怪的行为删除 C 字符串中的重复字符

Odd behavior removing duplicate characters in a C string

我在用于简单的基于替换的加密的程序中使用以下方法。该方法专门用于去除encryption/decryption键中的重复字符。

该方法和程序的其余部分一样有效,它适用于我试过的 99% 的键。但是,当我将键 "goodmorning" 或任何由相同字母以任何顺序组成的键(例如 "dggimnnooor")传递给它时,它会失败。此外,字符数多于 "goodmorning" 的键以及字符数少于

的键都有效。

我 运行 通过 lldb 使用相同参数的可执行文件,它可以工作。我已经在机器 运行 CentOS 上克隆了我的存储库,它按原样工作。

但是我在编译时没有收到任何警告或错误。

//setting the key in main method
char * key;
key = removeDuplicates(argv[2]);

//return 1 if char in word
int targetFound(char * charArr, int num, char target){
  int found = 0;

  if(strchr(charArr,target))
    found = 1;

  return found;
}

//remove duplicate chars
char * removeDuplicates(char * word){
  char * result;
  int len = strlen(word);
  result = malloc (len * sizeof(char));
  if (result == NULL)
    errorHandler(2);

  char ch;
  int i;
  int j;
  for( i = 0, j = 0; i < len; i++){
    ch = word[i];
    if(!targetFound(result, i, ch)){
      result[j] = ch;
      j++;
    }
  }

  return result;
}

每个请求:如果将 "feather" 传递给此函数,则结果字符串将为 "feathr".

我在您的代码中发现了几个问题:

  1. 您没有用空字符终止输出。
  2. 当输入中没有重复字符时,您没有分配足够的内存来保存空字符。

因此,您的程序具有未定义的行为。

改变

result = malloc (len * sizeof(char));

result = malloc (len+1); // No need for sizeof(char)

函数前添加如下内容returns.

result[j] = '[=12=]';

另一个主要问题是您在 result 上使用 strchr,当您调用 targetFound 时它不是空终止字符串。这也导致了未定义的行为。您需要使用:

char * removeDuplicates(char * word){
  char * result;
  int len = strlen(word);
  result = malloc (len+1);
  if (result == NULL)
  {
    errorHandler(2);
  }

  char ch;
  int i;
  int j;

  // Make result an empty string.
  result[0] = '[=13=]';
  for( i = 0, j = 0; i < len; i++){
    ch = word[i];
    if(!targetFound(result, i, ch)){
      result[j] = ch;
      j++;

      // Null terminate again so that next call to targetFound()
      // will work.
      result[j] = '[=13=]';
    }
  }

  return result;
}

第二种选择是不在 targetFound 中使用 strchr。请改用 num 并实现等效功能。

int targetFound(char * charArr, int num, char target)
{
   for ( int i = 0; i < num; ++i )
   {
      if ( charArr[i] == target )
      {
         return 1;
      }
   }
   return 0;
}

这将使您避免多次将空字符分配给 result。您只需要在最后终止 result

char * removeDuplicates(char * word){
  char * result;
  int len = strlen(word);
  result = malloc (len+1);
  if (result == NULL)
  {
    errorHandler(2);
  }

  char ch;
  int i;
  int j;

  for( i = 0, j = 0; i < len; i++){
    ch = word[i];
    if(!targetFound(result, i, ch)){
      result[j] = ch;
      j++;
    }
  }

  result[j] = '[=15=]';
  return result;
}

As R Sahu already said, you are not terminating your string with a NUL character. Now I'm not going to explain why you need to do this, but you always need to terminate your strings with a NUL character, which is '[=13=]'. If you want to know why, head over here 以获得很好的解释。然而,这不是您的代码的唯一问题。

主要问题是您调用的函数 strchr 是为了查明您的 result 是否已经包含某些字符 希望您传递一个 NUL终止字符串,但你的变量没有NUL终止,因为你不断向它附加字符。

为了解决您的问题,我建议您改用地图。映射您已经使用的所有字符,如果它们不在地图中,则将它们同时添加到地图和结果中。这更简单(不需要调用 strchr 或任何其他函数),更快(不需要每次都扫描所有字符串),最重要的是正确。

这是一个简单的解决方案:

char *removeDuplicates(char *word){
    char *result, *map, ch;
    int i, j;

    map = calloc(256, 1);
    if (map == NULL)
        // Maybe you want some other number here?
        errorHandler(2);

    // Add one char for the NUL terminator:
    result = malloc(strlen(word) + 1);
    if (result == NULL)
        errorHandler(2);

    for(i = 0, j = 0; word[i] != '[=10=]'; i++) {
        ch = word[i];

        // Check if you already saw this character:
        if(map[(size_t)ch] == 0) {
            // If not, add it to the map:
            map[(size_t)ch] = 1;

            // And to your result string:
            result[j] = ch;
            j++;
        }
    }

    // Correctly NUL terminate the new string;
    result[j] = '[=10=]';

    return result;
}

为什么这在其他机器上可以,但在你的机器上不行?

您是未定义行为的受害者。不同系统上的不同编译器以不同方式处理未定义的行为。例如,GCC 可能决定在这种特殊情况下不做任何事情,并使 strchr 继续在内存中搜索,直到找到 '[=13=]' 字符,而这正是发生的情况。您的程序一直在搜索 NUL 终止符并且永不停止,因为谁知道 '[=13=]' 可能在您的字符串之后的内存位置?这既危险又不正确,因为程序没有读取为它保留的内存,例如,另一个编译器可能决定在那里停止搜索,并给你一个正确的结果。然而,这不是理所当然的事情,您应该始终避免未定义的行为