【问题标题】:Odd behavior removing duplicate characters in a C string删除 C 字符串中的重复字符的奇怪行为
【发布时间】:2018-07-27 01:16:41
【问题描述】:

我在用于简单替换加密的程序中使用以下方法。此方法专门用于去除加解密密钥中的重复字符。

该方法与程序的其余部分一样有效,并且它适用于我尝试过的 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"

【问题讨论】:

  • 显示上下文,你如何称呼它等等。创建一个minimal reproducible example。解释发生了什么,以何种不受欢迎的方式。请。
  • 如果没有重复怎么办?您将如何为结果字符串中的字符串终止符留出空间?你在哪里添加终结者?
  • 请看链接。
  • 这就是 未定义行为 的问题,有时,甚至大部分时间,它似乎都可以正常工作。然后它就在没有警告的情况下中断。不在字符串中放置终止符会导致未定义的行为

标签: c string undefined-behavior null-terminated


【解决方案1】:

正如R Sahu 已经说过的那样,您不会用NUL 字符来终止您的字符串。现在我不打算解释你为什么需要这样做,但是你总是需要用NUL 字符来终止你的字符串,即'\0'。如果你想知道为什么,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] != '\0'; 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] = '\0';

    return result;
}

为什么这可以在其他机器上运行,而在您的机器上却不行?

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

【讨论】:

  • 感谢您提供的信息多于居高临下。
  • 您也可以简单地使用0(零或八进制常数零)作为nul-terminating 字符。 '\0' 的 ASCII 值为零,0 的输入更少...
  • @DavidC.Rankin 是的,更少的打字,但更多的混乱。看到0 可能会认为result 是一个整数数组或其他东西。由于两者之间没有区别,我更喜欢清晰而不是在我的源代码中保存三个字符。 == NULL vs == 0 等也是如此。
【解决方案2】:

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

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

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

改变

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

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

在函数返回之前添加以下内容。

result[j] = '\0';

另一个主要问题是您在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] = '\0';
  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] = '\0';
    }
  }

  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。您只需要在结尾处 null 终止 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] = '\0';
  return result;
}

【讨论】:

  • 那么,为什么它对所有其他键每次都有效?为什么它在运行 CentOS 的机器上一次又一次地“按原样”工作?
  • @wanderbread,试图理解未定义的行为是徒劳的。看似理智的行为也包含在其中。
  • 谢谢。我最初误读了result[j]='\0' 的位置
猜你喜欢
  • 2016-06-13
  • 2019-11-20
  • 2021-12-13
  • 1970-01-01
  • 2019-01-17
  • 1970-01-01
  • 2011-10-07
  • 1970-01-01
相关资源
最近更新 更多