【问题标题】:wrong redimension of a string in cc中字符串的错误重新尺寸
【发布时间】:2021-05-04 18:16:48
【问题描述】:

我正在尝试创建一个函数来接收动态字符串并从中删除所有出现的字符也作为参数传递。 字符串最终应该包含足够的空间来包含未删除的字符

void delete(char *cad, char c){
    int i, cont = 0;
    char *aux = NULL;
        
    i = 0;
    while(cad[i] != '\0'){
        if(cad[i] != c){
            aux = (char*)realloc(aux, sizeof(char) * cont + 1);
            aux[cont] = cad[i];
            cont++;
        }
    i++;    
    }
    
    cad = (char*)realloc(cad, sizeof(char) * cont);
    i = 0;
    while(aux[i] != '\0'){
        cad[i] = aux[i];
        i++;
    }
    
}

现在我有一个segmentation fault

【问题讨论】:

  • 另外,*cadcad[0] 相同。所以(*cad)[i]cad[0][i] 相同,这没有任何意义。
  • 在 C 中,'*'[..] 都用作取消引用。
  • @Someprogrammerdude 我改变了一些东西,但仍然有一个segmentation fault
  • @sonlas10 那是因为每次 while 循环找到一个不是 c 的字符时,您都会为变量 aux 分配一个新地址。

标签: c dynamic-memory-allocation c-strings function-definition


【解决方案1】:
  1. 您没有检查 realloc 的结果。
  2. IMO 最好将指针返回到新字符串,而不是使用双指针。双指针可能导致难以跟踪内存泄漏,并且函数不适用于 const 字符串 - 例如字符串文字
  3. 您没有null character 终止字符串。

在这个例子中,我没有改变你的分配算法,但在现实生活中更有效的方法是首先计算你需要分配多少内存,分配它,然后再次处理字符串:

char *delete(const char *cad, char c){
    size_t nchars = 0;
    char *aux = NULL;
    char *temp;
        
    while(*cad)
    {
        if(*cad != c)
        {
            temp = realloc(aux, sizeof(*temp) * nchars + 1);
            if(temp)
            {
                aux = temp;
                aux[nchars++] = *cad;
            }
            else
            {
                /* handle allocation error */
                free(aux);
                aux = NULL;
                break;
            }
        }
        cad++;
    }
    if(aux) aux[nchars] = 0;
    return aux;
}

一些小的变化:在sizeof 中使用对象而不是类型,并且不转换malloc 的结果。还可以添加NULL指针参数检查。

【讨论】:

    【解决方案2】:

    每次您在while 循环中使用realloc 时,实际上每次都在为变量aux 提供一个新地址。

    我建议你不要这样做,并在函数开始时分配你想要分配的内存。

    在分配内存之前,您需要计算需要多少内存。也就是说,计算你要删除多少元素。

    如果您希望我进一步阐明或添加代码片段,请随时在 cmets 中提问。

    【讨论】:

      【解决方案3】:

      我不会多次调用realloc(),而是对字符进行就地替换;这种替换将未使用的分配字符留在字符串的末尾,并由下面的 delete_no_realloc() 函数说明。

      如果您想在分配的字符串中删除这些未使用的结束字符,则只需调用一次realloc(),如下面的delete() 函数所示。

      注意,当一个函数在一个指针参数上使用realloc()时,它必须获得这个指针的地址,然后用realloc()的结果来调整它。

      /**
        gcc -std=c99 -o prog_c prog_c.c \
            -pedantic -Wall -Wextra -Wconversion \
            -Wwrite-strings -Wold-style-definition -Wvla \
            -g -O0 -UNDEBUG -fsanitize=address,undefined
      **/
      
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      
      size_t // new length
      delete_no_realloc(char *cad,
                        char c)
      {
        size_t w=0;
        for(size_t r=0; cad[r]; ++r)
        {
          char ch=cad[r];
          if(ch!=c)
          {
            cad[w++]=ch; // store and advance write index
          }
        }
        cad[w]='\0'; // ensure string termination
        return w;
      }
      
      void
      delete(char **cad_ptr,
             char c)
      {
        char *cad=*cad_ptr; // forget this embarrassing indirection
        size_t new_length=delete_no_realloc(cad, c);
        cad=realloc(cad, new_length+1);
        if(cad==NULL)
        {
          abort();
        }
        *cad_ptr=cad; // don't forget to adjust the string
      }
      
      int
      main(void)
      {
        const char *msg="this is a message";
        char *cad=malloc(strlen(msg)+1);
        if(cad==NULL)
        {
          abort();
        }
        strcpy(cad, msg);
        printf("before: <%s>\n", cad);
        delete(&cad, 's'); // pass the address of the string
        printf("after: <%s>\n", cad);
        free(cad);
        return 0;
      }
      

      【讨论】:

        【解决方案4】:

        您可以简化您的delete() 函数,只需在原始字符串中使用readwrite 索引,删除找到的所有c 字符,然后调用realloc() 将存储重新分配给完全适合剩余的字符。

        你可以这样做:

        void delete (char **cad, char c)
        {
            if (!*cad || !**cad)            /* check if cad is NULL or empty-string */
                return;
            
            size_t write = 0;               /* write index */
            
            for (size_t read = 0; (*cad)[read]; read++) {   /* loop over each char in cad */
                if ((*cad)[read] != c)                      /* if char not c */
                    (*cad)[write++] = (*cad)[read];         /* copy incrementing write */
            }
            (*cad)[write] = 0;                              /* nul-terminate */
            
            void *tmp = realloc (*cad, write + 1);          /* realloc to exact size */
            if (!tmp) {                                     /* validate realloc */
                perror ("realloc-cad");
                return;
            }
            
            *cad = tmp;         /* assign reallocated block to *cad */
        }
        

        一个完整的例子是:

        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        
        void delete (char **cad, char c)
        {
            if (!*cad || !**cad)            /* check if cad is NULL or empty-string */
                return;
            
            size_t write = 0;               /* write index */
            
            for (size_t read = 0; (*cad)[read]; read++) {   /* loop over each char in cad */
                if ((*cad)[read] != c)                      /* if char not c */
                    (*cad)[write++] = (*cad)[read];         /* copy incrementing write */
            }
            (*cad)[write] = 0;                              /* nul-terminate */
            
            void *tmp = realloc (*cad, write + 1);          /* realloc to exact size */
            if (!tmp) {                                     /* validate realloc */
                perror ("realloc-cad");
                return;
            }
            
            *cad = tmp;         /* assign reallocated block to *cad */
        }
        
        
        int main (int argc, char **argv) {
            
            if (argc < 3) {
                fputs ("usage: ./prog \"string with c\" c\n", stderr);
                return 1;
            }
            
            size_t len = strlen (argv[1]);
            char *s = malloc (len + 1);
            
            if (!s) {
                perror ("malloc-s");
                return 1;
            }
            memcpy (s, argv[1], len + 1);
            printf ("%s (%zu chars)\n", s, len);
            
            delete (&s, *argv[2]);
            printf ("%s (%zu chars)\n", s, strlen(s));
            
            free (s);
        }
        

        使用/输出示例

        $ ./bin/delete_c_realloc "nmyn ndogn nhasnn nnfleasnnn" n
        nmyn ndogn nhasnn nnfleasnnn (28 chars)
        my dog has fleas (16 chars)
        

        检查一下,如果您有任何问题,请告诉我。

        【讨论】:

          【解决方案5】:

          您的函数实现存在四个主要问题。

          第一个是该函数按值接受指向源字符串的指针。即参数cad 由用作参数的指针的值初始化。因此更改变量cad 不会影响原始指针。

          第二个是你没有检查realloc的调用是否成功。因此,该函数可以调用未定义的行为。

          第三个是每次追加新字符时重新分配字符串效率低。

          最后第四个是结果动态分配的数组不包含字符串,因为您忘记附加终止零字符'\0'

          如果您想在函数内更改原始指针的值,您应该从函数返回函数中获得的结果指针并将其分配给调用者中的原始指针。或者您应该通过引用将原始指针传递给函数。在 C 中,通过引用传递意味着通过指向对象的指针间接传递对象(可以是指针)。

          这是一个演示程序,显示了当函数通过引用接受原始指针时的函数实现。

          该函数还返回一个指向结果字符串的指针,可以在调用者中检查函数内动态内存的重新分配是否成功。

          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          
          char * remove_char( char **s, char c )
          {
              char * result = *s;
              
              if ( c != '\0' )
              {
                  char *dsn = *s;
                  const char *src = *s;
                  
                  do
                  {
                      if ( *src != c )
                      {
                          if ( dsn != src )
                          {
                              *dsn = *src;
                          }
                          ++dsn;
                      }
                  } while ( *src++ );
                  
                  char *tmp = realloc( *s, ( dsn - *s ) * sizeof( char ) );
                  
                  if( tmp != NULL ) *s = tmp;
                  
                  result = tmp;
              }
              
              return result;
          }
          
          int main(void) 
          {
              char *s = malloc( 12 );
              
              strcpy( s, "H#e#l#l#o!" );
              
              puts( s );
              
              if ( remove_char( &s, '#' ) ) puts( s );
              
              free( s );
              
              return 0;
          }
          

          程序输出是

          H#e#l#l#o!
          Hello!
          

          另一种方法是编写一个函数,它不会更改源字符串,而是动态创建一个新字符串,该字符串包含源字符串,但不包括指定的字符。这样的函数更加灵活,因为您可以使用字符串文字来调用它。如果源字符串也是动态分配的,那么函数的调用者在成功调用后就可以释放源字符串。

          这是一个演示程序。

          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          
          char * remove_copy( const char *s, char c )
          {
              size_t src_len = strlen( s );
              size_t dsn_len = src_len;
              
              if ( c != '\0' )
              {
                  for ( const char *p = s; ( p = strchr( p, c ) ) != NULL; ++p )
                  {
                      --dsn_len;
                  }
              }
              
              char *result = malloc( ( dsn_len + 1 ) * sizeof( char ) );
              
              if ( result != NULL )
              {
                      const char *src_s = s;
                      char *dsn_s = result;
                      
                      if ( dsn_len != src_len )
                      {
                          for ( const char *p = src_s; 
                                ( p = strchr( src_s, c ) ) != NULL; 
                                src_s = p + 1 )
                          {
                              if ( p - src_s != 0 )
                              {
                                  memcpy( dsn_s, src_s, p - src_s );
                                  dsn_s += p - src_s;
                              }
                          }
                      }
                      
                      strcpy( dsn_s, src_s );
              }
          
              return result;
          }
          
          int main(void) 
          {
              char s[] = "H#e#l#l#o!";
              
              puts( s );
              
              char *p = remove_copy( s, '#' );
              if ( p != NULL ) puts( p );
              
              free( p );
              
              return 0;
          }
          

          程序输出与前面演示程序显示的相同

          H#e#l#l#o!
          Hello!
          

          【讨论】:

            猜你喜欢
            • 2013-12-28
            • 1970-01-01
            • 1970-01-01
            • 2018-09-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多