从函数内调用 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
,而不影响 main
的 s
,因此 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库函数的名称,所以不要使用它。
建议代码如下:
- 干净地编译。
- 执行所需的功能。
- 正确检查(并处理)错误。
- 通过返回动态指针并让
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
与实际参数在内存中是不同的对象s
在 main
中,因此对一个的更改不会反映在另一个中。为了让函数写入参数,您必须传递指向该参数的指针: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
}
这两个片段中的语义完全相同——我们正在通过 *ptr
向 var
写入一个新值。只是var
和*ptr
的types
不一样而已。
因此,对于您的代码,myGetline
的原型需要是 void myGetline( char **s )
在您的代码主体中,您需要分配给 *s
:*s = malloc( sizeof **s );
- 不要强制转换
malloc
、calloc
或 realloc
的结果,除非 你正在编写 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
}
}
#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
,而不影响 main
的 s
,因此 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库函数的名称,所以不要使用它。
建议代码如下:
- 干净地编译。
- 执行所需的功能。
- 正确检查(并处理)错误。
- 通过返回动态指针并让
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
与实际参数在内存中是不同的对象s
在main
中,因此对一个的更改不会反映在另一个中。为了让函数写入参数,您必须传递指向该参数的指针:
如果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 }
*ptr
向var
写入一个新值。只是var
和*ptr
的types
不一样而已。 因此,对于您的代码,myGetline
的原型需要是
在您的代码主体中,您需要分配给void myGetline( char **s )
*s
:*s = malloc( sizeof **s );
- 不要强制转换
malloc
、calloc
或realloc
的结果,除非 你正在编写 C++(在这种情况下你应该如果可以的话,根本不要使用*alloc
函数)或使用 ancient pre-ANSI C 编译器。 - 因为
main
中的s
是 而不是 被_getline
修改的,当你将它传递给free
.只有从*alloc
函数之一编辑的指针值 return 可以传递给free
。 realloc
是一个相对昂贵的操作,会导致缓冲区在内存中移动,所以你不想对每个字符都这样做。更好的策略是根据需要将缓冲区大小加倍,从而最大限度地减少realloc
调用的次数。此外,由于它可以 return aNULL
如果它不能重新分配缓冲区,你不想将结果分配回原始指针 - 你应该先将它分配给临时并检查它不是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 } }