从函数内调用 malloc 和 realloc 会产生意想不到的结果

Calling malloc and realloc from within a function gives unexpected results

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

char _getline(char *s)
{
    char c;
    s = (char *)malloc(sizeof(char));

    int i;
    for (i = 0; (s[i] = getchar()) != EOF && s[i] != '\n'; ++i)
    {
        s = (char *)realloc(s, (i + 1) * sizeof(char));
    }
    c = s[i];
    s = (char *)realloc(s, (i + 1) * sizeof(char));
    ++i;
    s[i] = '[=10=]';
    return c; 
}

int main()
{
    char *s = "word";
    char c;
    _getline(s);
    printf("%s\n", s);
    free(s);
    return 0;
}

输出是:

input
word
munmap_chunk(): invalid pointer
Aborted (core dumped)

当我在 main 中做同样的事情时,我没有得到错误,但是当我尝试打印字符串时,我得到了 [=13=]。此外,当我尝试将指针的地址传递给 _getline 时,我遇到了分段错误。这是尝试:

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

char _getline(char **s)
{
    char c;
    *s = (char *)malloc(sizeof(char));

    int i;
    for (i = 0; (*s[i] = getchar()) != EOF && *s[i] != '\n'; ++i)
    {
        *s = (char *)realloc(*s, (i + 1) * sizeof(char));
    }
    c = *s[i];
    *s = (char *)realloc(*s, (i + 1) * sizeof(char));
    ++i;
    *s[i] = '[=12=]';
    return c; 
}

int main()
{
    char *s = "word";
    char c;
    _getline(&s);
    printf("%s\n", s);
    free(s);
    return 0;
}

我做错了什么?

在您的第一次尝试中,您误解了传递指针的工作原理。当您在 _getline 中重新分配 s 时,这只会影响它的 s,而不影响 mains,因此 main 打印 word 然后尝试 free 一个字符串文字,可以预见它会以糟糕的方式结束。要修复它,请将 _getline(s) 更改为 _getline(&s),使 _getline 使用 char ** 而不是 char *,并更改其对 [=10= 的所有使用] 到 *s。请注意,更改其所有用途并不是简单的文本替换;在某些情况下,您必须改用 (*s)。例如,s[i] 需要变为 (*s)[i]。如果你只是 *s[i],它会被错误地解析为 *(s[i])

另外,你有一个差一个错误:当你 realloc 而不是 i + 1 时,你需要使用 i + 2,因为数组索引从 0 开始,但第一个元素仍然占用 space.

顺便说一句,与您当前看到的错误无关,但是您对 EOF 的测试没有按照您希望的方式工作,因为它之前被转换为 char你测试它,EOF 超出了 char 的范围。 (确切的问题取决于 char 的符号。如果它是 signed,那么 \xFF 将错误地算作 EOF,如果它是 unsigned,那么 EOF 将不会被识别,并且会错误地算作 \xFF。)

您遇到了 *s[i] = getchar() 的问题。由于 C's operator precedence rules,数组索引在指针取消引用之前得到应用。因此,例如,如果 i 为 1,则您会将 s[0] 之后的数据视为指向 char 的指针并取消引用它。这很可能是一个完全无效的内存位置。

你要的是(*s)[i].

注意:getline()是一个C库函数的名称,所以不要使用它。

建议代码如下:

  1. 干净地编译。
  2. 执行所需的功能。
  3. 正确检查(并处理)错误。
  4. 通过返回动态指针并让 main() 将返回的指针分配给局部变量,避免在 main() 中更新指针(针对每个字符输入)的问题。

注意:size_t 用于 i,因为这是 realloc() 期望的参数类型。

现在,建议的代码:

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

char *myGetline( void )
{   
    int ch;
    char * line = NULL;

    for ( size_t i = 0; ( ch = getchar()) != EOF && ch != '\n'; ++i)
    {
        char * temp = realloc(line, i + 2);
        if( ! temp )
        {
            perror( "realloc failed" );
            free( line );
            exit( EXIT_FAILURE );
        }

        line = temp;

        line[i] = (char)ch;
        line[i+1] = '[=10=]';       
    }

    return line; 
}

int main( void )
{
    char *s = myGetline();
    printf("%s\n", s);
    free(s);
    return 0;
}

几个问题:

  • 带有前导下划线的标识符,如 _getline 保留用于实现;你不应该用前导下划线命名你的函数或变量。由于 getline 已经是库函数的名称,因此请改用 myGetline 之类的名称。
  • 请记住,C 通过值传递所有参数 - _getline 函数中的形式参数 s 与实际参数在内存中是不同的对象smain 中,因此对一个的更改不会反映在另一个中。为了让函数写入参数,您必须传递指向该参数的指针:
    void foo( T *ptr ) // for any type T, *including pointer types*
    {
      *ptr = new_T_value( ); // write a new value to the thing ptr points to
    }
    
    int main( void )
    {
      T var;
      foo( &var ); // write a new value to var
    }
    
    如果 T 是指针类型,也会发生同样的事情 - 让我们用指针类型 P * 替换 T:
    void foo( P * *ptr ) 
    {
      *ptr = new_Pstar_value( ); // write a new value to the thing ptr points to
    }
    
    int main( void )
    {
      P * var;
      foo( &var ); // write a new value to var
    }
    
    这两个片段中的语义完全相同——我们正在通过 *ptrvar 写入一个新值。只是var*ptrtypes不一样而已。 因此,对于您的代码,myGetline 的原型需要是
    void myGetline( char **s )
    
    在您的代码主体中,您需要分配给 *s:
    *s = malloc( sizeof **s );
    
  • 不要强制转换 malloccallocrealloc 的结果,除非 你正在编写 C++(在这种情况下你应该如果可以的话,根本不要使用 *alloc 函数)或使用 ancient pre-ANSI C 编译器。
  • 因为 main 中的 s 而不是 _getline 修改的,当你将它传递给 free.只有从 *alloc 函数之一编辑的指针值 return 可以传递给 free
  • realloc 是一个相对昂贵的操作,会导致缓冲区在内存中移动,所以你不想对每个字符都这样做。更好的策略是根据需要将缓冲区大小加倍,从而最大限度地减少 realloc 调用的次数。此外,由于它可以 return a NULL 如果它不能重新分配缓冲区,你不想将结果分配回原始指针 - 你应该先将它分配给临时并检查它不是 NULL,否则您可能会丢失对该内存的引用:
    void myGetline( char **s )
    {
      size_t size = 2; // size of the buffer
      size_t len  = 0; // length of the string stored in the buffer;
      char c;
    
      *s = malloc( sizeof **s * size );
      if ( !*s )
        return;
    
      while ( (c = getchar()) != EOF && c != '\n' )
      {
        if ( len + 1 == size ) // double the buffer size
        {
          char *tmp = realloc( *s, sizeof **s * ( size * 2 ) );
          if ( !tmp )
          {
            fprintf( stderr, "Cannot extend input buffer, returning what we have so far\n" );
            return;
          }
    
          *s = tmp;
          size *= 2;
        }
        (*s)[len++] = c; // we want to index into what s *points to*, not s itself
        (*s)[len] = 0;   // zero terminate as we go
      }
    }