如何解决`realloc(): invalid next size` 在尝试处理未知大小的输入时
我有以下代码:
如果 get_unlimited_input
被传递,函数 NULL
分配一个新字符串,否则它只是将字符附加到现有字符串。
最后它会截断多余的字节。
(DEFAULT_BUFFER_SIZE
设置为 5
以测试多次重新分配的情况)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define DEFAULT_BUFFER_SIZE 5
char *get_unlimited_input(char *buffer) {
size_t current_size;
if (buffer == NULL) {
buffer = malloc(DEFAULT_BUFFER_SIZE * sizeof(char));
current_size = DEFAULT_BUFFER_SIZE;
} else {
current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE;
}
char *cursor = buffer + current_size - DEFAULT_BUFFER_SIZE;
for (;;) {
int current = getchar();
*cursor = (char)current;
cursor++;
if (current == '\n' || current == EOF)
break;
if (cursor >= buffer + current_size) {
current_size += DEFAULT_BUFFER_SIZE;
buffer = realloc(buffer,current_size);
cursor = buffer + current_size - DEFAULT_BUFFER_SIZE;
}
}
*cursor = '\0';
buffer = realloc(buffer,cursor - buffer);
return buffer;
}
int main() {
printf(">");
char *buffer = get_unlimited_input(NULL);
printf(">");
get_unlimited_input(buffer);
}
在大多数情况下它工作得很好,但是如果我先传递 117 个字符,然后传递 12 个它就会崩溃:
>.....................................................................................................................
>............
realloc(): invalid next size
Aborted (core dumped)
python3 -c "print('.'*117+'\n'+'.'*12)" | ./_buffer
realloc(): invalid next size
Aborted (core dumped)
有什么问题?
解决方法
除其他问题外,您还需要在返回缓冲区之前从缓冲区中删除所有额外空间。但是,如果您将缓冲区传递给该函数,则您假设它仍然有额外的空间。因此,您不能将从函数返回的缓冲区传递回函数。但你正是这样做的。
} else {
current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE;
}
...
buffer = realloc(buffer,cursor - buffer);
此外,正如 KamilCuk 所指出的,您不会在返回的字符串中为终止符留出空间,因此在其上调用 strlen
是不安全的。
您应该记录对函数输入的要求以及保证函数输出满足的要求。这使得发现此类错误变得更加容易。
一看,“如果给这个函数传递了一个缓冲区,它肯定有多余的空间”和“这个函数返回的缓冲区没有多余的空间”,很明显你不能传递返回的缓冲区回到函数,因为输出保证不满足输入要求。
,基本问题是,当您使用非空指针(来自早期调用的预先存在的缓冲区)调用 get_unlimited_input
时,它假定缓冲区大小为 strlen(buffer) + DEFAULT_BUFFER_SIZE
,这是错误的。之前的调用实际上会重新分配缓冲区以匹配不包括终止 NUL 的字符串的长度(这意味着终止 NUL 本身很可能会丢失。)
您可以通过在存储 NUL 之后和重新分配之前增加游标来解决这些问题(因此重新分配会足够大),然后在传递非空指针时将 current_size 的计算更改为 strlen(buffer) + 1
另一个问题是,当您从 getchar 获取 EOF 时,您将该 EOF 转换为 char 并将其存储在缓冲区中。 EOF 不是有效字符——让 getchar
返回 int
而不是 char
的全部意义在于它可以将 EOF 表示为与任何字符不同。因此,在 EOF 中,您将一些随机的垃圾字符(或非字符)存储在缓冲区中,这可能只是显示为垃圾,或者可能导致输出崩溃或错误(取决于系统)。
如果您收到来自 malloc
、realloc
或 free
的运行时错误,则意味着您已损坏堆。堆损坏的常见原因包括在释放内存块后使用它(这包括调用 free
两次)和缓冲区溢出(和下溢)。
损坏发生在运行时错误之前。它可能已经发生了很长时间,所以如果你只是在错误发生的时候开始调试程序,可能很难重建发生了什么。还有其他工具可以帮助您更准确地定位问题。在带有 GCC 或 Clang 的类 Unix 系统上,AddressSanitizer 非常有用。
# Settings for Ubuntu 20.04; you may need to adapt for your system
$ export ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-10/bin/llvm-symbolizer
$ gcc -O -Wall -Wextra a.c -fsanitize=address,undefined && python3 -c "print('.'*117+'\n'+'.'*12)" | ./a.out
=================================================================
==446177==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60c000000236 at pc 0x7f246fc0ea6d bp 0x7ffd4e309380 sp 0x7ffd4e308b28
READ of size 119 at 0x60c000000236 thread T0
#0 0x7f246fc0ea6c (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x67a6c)
#1 0x55c04dfb32e7 in get_unlimited_input (.../65891246/a.out+0x12e7)
#2 0x55c04dfb34b1 in main (.../65891246/a.out+0x14b1)
#3 0x7f246f06f0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#4 0x55c04dfb320d in _start (.../65891246/a.out+0x120d)
0x60c000000236 is located 0 bytes to the right of 118-byte region [0x60c0000001c0,0x60c000000236)
allocated by thread T0 here:
#0 0x7f246fcb4ffe in __interceptor_realloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dffe)
#1 0x55c04dfb345c in get_unlimited_input (.../65891246/a.out+0x145c)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x67a6c)
Shadow bytes around the buggy address:
0x0c187fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c187fff8000: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x0c187fff8010: fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa
0x0c187fff8020: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
0x0c187fff8030: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
=>0x0c187fff8040: 00 00 00 00 00 00[06]fa fa fa fa fa fa fa fa fa
0x0c187fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==446177==ABORTING
你。最重要的部分是堆栈跟踪,以及缓冲区溢出发生在“118 字节区域”的指示,这表明它发生在第一次调用 get_unlimited_input
的最后或开始时第二个。堆栈跟踪为您提供发生溢出的确切代码地址,您可以使用它在调试器中设置断点;你会看到它接近函数的结尾。正如其他人已经指出的那样,
*cursor = '\0';
buffer = realloc(buffer,cursor - buffer);
是错误的:您没有为 '\0'
终止符留出空间。你需要
*(cursor++) = '\0';
buffer = realloc(buffer,cursor - buffer);
或
*cursor = '\0';
buffer = realloc(buffer,cursor - buffer + 1);
我尚未检查其他错误 (it isn't the only one)。
,您的代码中存在多个问题,导致堆损坏,如诊断所示:
-
您对当前分配大小的假设不正确:
current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE;
过于乐观。由于您在返回之前将缓冲区重新分配为cursor - buffer
字节,因此字符串末尾没有松弛。 -
在将字节存储到数组后测试
'\n'
和EOF
。这可能是换行符的预期行为,但对于不是字符的EOF
来说是不正确的。 -
用
buffer
重新分配buffer = realloc(buffer,cursor - buffer);
也是不正确的:cursor
指向空终止符,因此您应该使用cursor + 1 - buffer
的大小来保持空分配块内的终止符。
这是修改后的版本:
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_BUFFER_SIZE 16 /* use address alignment as incremental size */
char *get_unlimited_input(char *buffer) {
size_t current_size,pos;
char *p;
if (buffer == NULL) {
pos = 0;
current_size = DEFAULT_BUFFER_SIZE;
buffer = malloc(DEFAULT_BUFFER_SIZE);
if (buffer == NULL)
return NULL;
} else {
pos = strlen(buffer);
current_size = pos + 1;
}
for (;;) {
int c = getchar();
if (c == EOF || c == '\0')
break;
if (pos + 1 == current_size) {
// reallocate the buffer
current_size += DEFAULT_BUFFER_SIZE;
p = realloc(buffer,current_size);
if (p == NULL)
break;
buffer = p;
}
buffer[pos++] = (char)c;
if (c == '\n')
break;
}
buffer[pos] = '\0';
p = realloc(buffer,pos + 1);
return (p != NULL) ? p : buffer;
}
int main() {
printf("> ");
char *buffer = get_unlimited_input(NULL);
printf("got: %s\n",buffer);
printf("> ");
get_unlimited_input(buffer);
printf("got: %s\n",buffer);
return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。