【问题标题】:Calling malloc and realloc from within a function gives unexpected results从函数中调用 malloc 和 realloc 会产生意想不到的结果
【发布时间】:2020-06-13 19:45:48
【问题描述】:
#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] = '\0';
    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 中做同样的事情时,我没有收到错误,但是当我尝试打印字符串时,我得到一个 \0。此外,当我尝试将指针的地址传递给_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] = '\0';
    return c; 
}

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

我做错了什么?

【问题讨论】:

    标签: c pointers malloc realloc


    【解决方案1】:

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

    此外,您还有一个错误:当您使用 realloc 时,您需要使用 i + 2,而不是 i + 1,因为数组索引从 0 开始,但第一个元素仍然占用空间。

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

        注意: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] = '\0';       
            }
        
            return line; 
        }
        
        int main( void )
        {
            char *s = myGetline();
            printf("%s\n", s);
            free(s);
            return 0;
        }
        

        【讨论】:

          【解决方案4】:

          几个问题:

          • 带有前导下划线的标识符如_getline 保留用于实现;您不应使用前导下划线命名您的函数或变量。由于getline 已经是库函数的名称,请改用myGetline 之类的名称。
          • 请记住,C 传递所有参数按值 - _getline 函数中的形式参数 smain 中的实际参数 s 是不同的内存对象,所以对其中一项的更改不会反映在另一项中。为了让函数写入参数,您必须传递一个指向该参数的指针:
            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 是指针类型,也会发生同样的事情 - 让我们将 T 替换为指针类型 P *:
            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
            }
            
            两个 sn-ps 中的语义完全相同——我们正在向var*ptr 写入一个新值。只是var*ptrtypes不一样。 因此,对于您的代码,myGetline 的原型需要是
            void myGetline( char **s )
            在您的代码正文中,您需要分配给*s:
            *s = malloc( sizeof **s );
          • 不要转换 malloccallocrealloc 的结果除非您正在编写 C++(在这种情况下,您根本不应该使用 *alloc 函数如果你能帮助它)或使用 ancient pre-ANSI C 编译器。
          • 因为main 中的s 没有_getline 修改,所以当您将它传递给free 时,它仍然指向字符串文字。只有从*alloc 函数之一返回的指针值可以传递给free
          • realloc 是一个相对昂贵的操作,可能会导致缓冲区在内存中移动,因此您不想对每个字符都这样做。更好的策略是根据需要将缓冲区大小加倍,从而最大限度地减少realloc 调用的数量。此外,由于它可以在无法重新分配缓冲区时返回 NULL,因此您不想将结果分配回原始指针 - 您应该先将其分配给临时指针并检查它是否不是 @ 987654355@,否则您可能会丢失对该内存的引用:
            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
              }
            }

          【讨论】:

            猜你喜欢
            • 2021-10-04
            • 1970-01-01
            • 1970-01-01
            • 2015-10-25
            • 2015-02-12
            • 1970-01-01
            • 2019-04-05
            • 1970-01-01
            • 2021-12-28
            相关资源
            最近更新 更多