【问题标题】:realloc reports incorrect checksumrealloc 报告不正确的校验和
【发布时间】:2015-04-09 07:30:26
【问题描述】:

我有这个 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 上工作,每当发生缓冲区扩展时,程序崩溃并且系统报告:

ma​​lloc: *** 对象 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 = '\0';
    }

    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] = '\0';
                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);
    }
}

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

【问题讨论】:

  • 只是出于病态的好奇,为什么你认为malloc/free 可以工作而realloc 不行?
  • 发现堆损坏时会报这种错误。在这种情况下,发现是malloc 的调试版本。请注意,realloc 很可能会调用malloc,因此这很可能发生在调用realloc 的代码分支中。但是堆损坏发生在被发现之前,并且正如错误消息所暗示的那样,如果在指针指向的内存被释放之后写入指针,这通常会发生。
  • 当您使用free(pointer); 释放内存时,下一步应该是buffer = NULL;,这个简单的规则可以帮助您防止使用已释放的内存

标签: c command-line-tool


【解决方案1】:

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

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

应该是

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

【讨论】:

    猜你喜欢
    • 2011-08-04
    • 2023-03-27
    • 1970-01-01
    • 2012-09-03
    • 1970-01-01
    • 2015-03-08
    • 1970-01-01
    • 2016-03-28
    相关资源
    最近更新 更多