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

时间:2018-02-16 06:55:45

标签: c string undefined-behavior null-terminated

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

该方法功能正常,程序的其余部分也是如此,它适用于我尝试过的99%的密钥。但是,当我将密钥<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown"> Services</a> <ul class="dropdown-menu services-dropdown" role="menu"> @forelse(App\Model\Service::all() as $service) <li class="dropdown-submenu"> <a href="{{ url('services/'.$service->slug) }}">{{ $service->title }}</a> <ul class="dropdown-menu sub-services"> @foreach(App\Model\SubService::where('service_id', '=',$service->id)->get()) as $subservice) <li> <a href="{{ url('sub-services/'.$subservice->slug) }}">{{ $subservice->title }}</a> </li> @endforeach </ul> </li> @empty @endforelse </ul> </li> 或任何按相同字母组成的密钥(例如"goodmorning")传递给它时,它会失败。此外,包含比"dggimnnooor"更多字符的键以及具有较少字符的键。

我使用相同的参数通过"goodmorning"运行可执行文件并且它可以正常运行。我在运行CentOS的机器上克隆了我的存储库,它按原样运行。

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

lldb

每个请求:如果将//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"

2 个答案:

答案 0 :(得分:4)

正如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'在内存中的位置?这既危险又不正确,因为程序没有在为其保留的内存中读取,因此,例如,另一个编译器可能决定停止搜索,并给出正确的结果。然而, 这是理所当然的事情,你应该总是避免未定义的行为

答案 1 :(得分:1)

我在你的代码中看到了几个问题:

  1. 您没有使用空字符终止输出。
  2. 当输入中没有重复字符时,您没有分配足够的内存来保存空字符。
  3. 因此,您的程序有不确定的行为。

    更改

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

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

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

    result[j] = '\0';
    

    另一个问题,主要问题是,您在strchr上使用result,当您致电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;
    }
    

    第二种选择是不在strchr中使用targetFound。请改用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] = '\0';
      return result;
    }