realloc 报告不正确的校验和

realloc reports incorrect checksum

我有这个 C 程序尝试一个文本行读取函数,它应该能够处理任意长度的行。它通过维护一个缓冲区来工作,只要需要更多空间,缓冲区的大小就会加倍。

实际方法在这里:

/*******************************************************************************
* Attempts to expand the line buffer. If succeeded, TRUE is returned.          *
*******************************************************************************/
static char* try_expand(char* buffer, int* p_buffer_length)
{
    *p_buffer_length *= 2;

    puts("Before realloc");

    char* s = realloc(buffer, *p_buffer_length);

    puts("After realloc");

    if (s)
    {
        return s;
    }

    // Once here, realloc failed.
    char* s2 = malloc(*p_buffer_length);

    if (!s2)
    {
        return NULL;
    }

    strncpy(s2, buffer, *p_buffer_length / 2);
    free(buffer);
    return s2;
}

我在Mac OS X上工作,每当发生缓冲区扩展时,程序就会崩溃,系统报告:

malloc:对象 0x100105568 的 *** 错误:释放对象的校验和不正确 - 对象可能在释放后被修改。

其他一切都在这里:

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

#define HELP_FLAG           "-h"
#define VERSION_FLAG        "-v"
#define FLAG_DESC           "%-5s"
#define INITIAL_BUFFER_SIZE 8
#define FALSE               0
#define TRUE                (~FALSE)

/*******************************************************************************
* This routine removes all leading and trailing whitespace from a string,      *
* doing that in-place. (Total of two passes.)                                  *
*******************************************************************************/
static char* trim_inplace(char* start)
{
    return start;
    /*
    for (char* end = &start[strlen(start) - 1];
         isspace(*end) && end >= start; --end)
    {
        *end = '[=11=]';
    }

    while (isspace(*start))
    {
        ++start;
    }

    return start;*/
}
/*******************************************************************************
* Processes a single line and handles everything needed for dealing with lines *
* of arbitrary length.                                                         *
*******************************************************************************/
static int process_line(char** p_buffer, int* p_buffer_length, FILE* file)
{
    size_t current_index = 0;

    for (;;)
    {
        char* ret = fgets(*p_buffer + current_index, *p_buffer_length, file);

        if (!ret)
        {
            //puts("!ret is true.");
            return FALSE;
        }

        // Find out whether we have a newline character, which would imply that
        // we have an entire line read.
        for (size_t i = 0; i < *p_buffer_length; ++i)
        {
            if ((*p_buffer)[i] == '\n')
            {
                //(*p_buffer)[i + 1] = '[=11=]';
                puts(trim_inplace(*p_buffer));
                return TRUE;
            }
        }

        // -1 for skipping the NULL-terminator.
        current_index += *p_buffer_length - 1;
        char* new_buffer;

        // Once here, the current line does not fit in 'p_buffer'. Expand the
        // array by doubling its capacity.
        if (!(new_buffer = try_expand(*p_buffer, p_buffer_length)))
        {
            perror("Could not expand the line buffer");
            free(*p_buffer);
            exit(EXIT_FAILURE);
        }
        else
        {
            *p_buffer = new_buffer;
        }
    }
}

/*******************************************************************************
* Processes a file.                                                            *
*******************************************************************************/
static void process_file(char** p_buffer, int* p_buffer_length, FILE* file)
{
    while (!feof(file))
    {
        process_line(p_buffer, p_buffer_length, file);
    }
}

/*******************************************************************************
* Prints the help message and exits.                                           *
*******************************************************************************/
static void print_help()
{
    printf("Usage: trim [" HELP_FLAG "] [" VERSION_FLAG "] "          \
           "[FILE1, [FILE2, [...]]]\n"                                \
           "    " FLAG_DESC " Print the help message and exit.\n"     \
           "    " FLAG_DESC " Print the version message and exit.\n"  \
           "    If no files specified, reads from standard input.\n",
           HELP_FLAG,
           VERSION_FLAG);
}

/*******************************************************************************
* Prints the version string.                                                   *
*******************************************************************************/
static void print_version()
{
    printf("trim 1.618\n" \
           "By Rodion \"rodde\" Efremov 08.04.2015 Helsinki\n");
}


/*******************************************************************************
* Prints the erroneous flag.                                                   *
*******************************************************************************/
static void print_bad_flag(const char* flag)
{
    printf("Unknown flag \"%s\"\n", flag);
}

/*******************************************************************************
* Checks the flags.                                                            *
*******************************************************************************/
static void check_flags(int argc, char** argv)
{
    for (size_t i = 1; i < argc; ++i)
    {
        if (strcmp(argv[i], HELP_FLAG) == 0)
        {
            print_help();
            exit(EXIT_SUCCESS);
        }
        else if (strcmp(argv[i], VERSION_FLAG) == 0)
        {
            print_version();
            exit(EXIT_SUCCESS);
        }
        else if (argv[i][0] == '-')
        {
            print_bad_flag(argv[i]);
            exit(EXIT_FAILURE);
        }
    }
}

/*******************************************************************************
* The entry point for a trivial line trimmer.                                  *
*******************************************************************************/
int main(int argc, char** argv)
{
    check_flags(argc, argv);

    int buffer_length = INITIAL_BUFFER_SIZE;
    char* buffer = malloc(buffer_length);

    if (argc < 2)
    {
        // If realloc changes the location of memory, we need to know this.
        process_file(&buffer, &buffer_length, stdin);
        fclose(stdin);
        return EXIT_SUCCESS;
    }

    for (size_t i = 1; i < argc; ++i)
    {
        FILE* file = fopen(argv[i], "r");

        if (!file)
        {
            perror("Error opening a file");
            return (EXIT_FAILURE);
        }

        process_file(&buffer, &buffer_length, file);
        fclose(file);
    }
}

我所做的唯一观察是,如果输入行只需要行缓冲区的一次扩展,那么一切都是 O.K。但是,如果输入行大到至少需要两次扩展,程序就会崩溃。我在这里做错了什么?

当您阅读 process_line() 中的更多块时,您传递了错误的大小:

fgets(*p_buffer + current_index, *p_buffer_length, file);

应该是

fgets(*p_buffer + current_index, *p_buffer_length - current_index, file);