`realloc():无效的下一个大小`,同时试图处理未知大小的输入

`realloc(): invalid next size` while trying to handle input of unknown size

我有以下代码: 如果传递 NULL 函数 get_unlimited_input 分配一个新字符串,否则它只是将字符附加到现有字符串。 最后它会截断多余的字节。 (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 = '[=10=]';
    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)

有什么问题?

除其他问题外,您 trim 在 return 之前缓冲区中的所有额外 space。但是如果你将一个缓冲区传递给函数,你会假设它仍然有额外的 space。因此,您不能将缓冲区 returned 从函数传回函数。但你就是这么做的。

    } else {
        current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE;
    }
...
    buffer = realloc(buffer, cursor - buffer);

此外,正如 KamilCuk 所指出的,您不会在 returned 字符串中为终止符保留 space,因此对其调用 strlen 是不安全的。

您应该记录对函数输入的要求是什么以及保证函数输出满足什么要求。这使得发现此类错误变得更加容易。

只要你看到,“如果一个缓冲区被传递给这个函数,它必须有额外的space”和“从这个函数中得到的缓冲区return没有任何额外的space",很明显你不能将 returned 缓冲区传回函数,因为输出保证不满足输入要求。

基本问题是,当您使用非空指针调用 get_unlimited_input(先前调用的先前存在的缓冲区)时,它假定缓冲区大小为 strlen(buffer) + DEFAULT_BUFFER_SIZE,这是错误的。先前的调用实际上已经重新分配了缓冲区以匹配不包括终止 NUL 的字符串的长度(这意味着终止 NUL 本身很可能会丢失。)

您可以通过在存储 NUL 之后和重新分配之前递增游标来解决这些问题(因此重新分配将足够大),然后在将非空指针传递给时更改 current_size 的计算成为 strlen(buffer) + 1

另一个问题是,当您从 getchar 获取 EOF 时,您随后将该 EOF 转换为 char 并将其存储在缓冲区中。 EOF 不是有效字符——使用 getchar return 和 int 而不是 char 的全部意义在于它可以将 EOF 标记为不同于任何字符.因此,在 EOF 上,您在缓冲区中存储了一些随机垃圾字符(或非字符),这可能只是显示为垃圾,或者可能导致崩溃或输出错误(取决于系统)。

如果您从 mallocreallocfree 收到运行时错误,则表示您已损坏堆。堆损坏的常见原因包括在释放内存块后使用它(这包括调用 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 第一次调用的末尾或 get_unlimited_input 的最开始第二个。堆栈跟踪为您提供了发生溢出的确切代码地址,您可以使用它在调试器中设置断点;你会看到它接近函数的末尾。正如其他人已经指出的那样,

    *cursor = '[=11=]';
    buffer = realloc(buffer, cursor - buffer);

是错误的:您没有为 '[=19=]' 终止符留出空间。你需要

    *(cursor++) = '[=12=]';
    buffer = realloc(buffer, cursor - buffer);

    *cursor = '[=13=]';
    buffer = realloc(buffer, cursor - buffer + 1);

我还没有检查其他错误 ()。

您的代码中存在多个问题,导致堆损坏,如诊断所示:

  • 您对当前分配大小的假设不正确:current_size = strlen(buffer) + DEFAULT_BUFFER_SIZE; 过于乐观。由于您在返回之前将缓冲区重新分配为 cursor - buffer 字节,因此字符串末尾没有松弛部分。

  • 在将字节存储到数组后测试 '\n'EOF。这可能是换行符的预期行为,但对于 EOF 来说是不正确的,它不是一个字符。

  • buffer = realloc(buffer, cursor - buffer); 重新分配 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 == '[=10=]')
            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] = '[=10=]';
    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;
}