Valgrind:REALLOC 未初始化的值是由堆分配创建的 [英] Valgrind: REALLOC Uninitialised value was created by a heap allocation

查看:32
本文介绍了Valgrind:REALLOC 未初始化的值是由堆分配创建的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请阅读并尝试应用在 stackOverflow 上找到的解决方案后,问题仍未解决.

Please, after reading and trying to apply solutions found on stackOverflow the problem has not been solved.

条件跳转或移动取决于未初始化的值:有条件的跳转或移动取决于未初始化的值:未初始化的值是由堆分配创建的.

Conditional jump or move depends on uninitialised value(s): Conditional jump or move depends on uninitialised value(s): Uninitialised value was created by a heap allocation.

Valgrind 弹出的错误:

Error popped up by Valgrind:

错误

我正在尝试逐行实现文件读取并为它们动态重新分配一个数组.

I'm trying to implement file reading line by line and dynamically realloc an array for them.

在线错误:result = realloc(result, currLen * sizeof(char *));

Error on line: result = realloc(result, currLen * sizeof(char *));


void readFile(char *fileName) {
    FILE *fp = NULL;
    size_t len = 0;

    int currLen = 2;
    char **result = calloc(currLen, sizeof(char *));

    fp = fopen(fileName, "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    if (result == NULL)
        exit(EXIT_FAILURE);

    int i = 0;
    while (getline(&(*(result + i)), &len, fp) != -1) {
        if (i >= currLen - 1) {
            currLen *= 2;
            result = realloc(result, currLen * sizeof(char *));
        }
        ++i;
    }

    fclose(fp);

    for (int j = 0; j < currLen; ++j) {
        free(*(result + j));
    }

    free(result);
    result = NULL;
}

int main() {
    readFile("");

    exit(EXIT_SUCCESS);
}

推荐答案

在最初发布的代码中,result 通过 calloc 进行初始分配,这将零初始化其中的内容,作为指针,空初始化.稍后,当通过 realloc 扩展序列时,不会采用这种可供性.如果原始数组看起来像这样,则有效:

In the original posted code, result undergoes an initial allocation via calloc, which zero-initializes the content therein, and being pointers, null-initializes. Later on, when expanding the sequence via realloc, no such affordance is taken. In effect if the original array looked like this:

[ NULL, NULL ]

添加两个元素后,看起来像这样:

and after adding two elements, looks like this:

[ addr1, addr2 ]

重新分配开始并为您提供这个:

the realloc kicks in and gives you this :

[ addr1, addr2, ????, ???? ]

在伤口上加盐,getline 还要求长度参数反映行中存在的分配大小.但是您要继承先前循环迭代的长度,因此不仅在第一次扩展后指针错误,而且在 getline第一次 调用之后长度永远不会正确>(导致您的实际崩溃;其他问题只是您尚未看到的).

Adding salt to the wound, getline also requires the length argument being reflective of the allocation size present in the line. But you're carrying over the length from the prior loop iteration, so not only is the pointer wrong after the first expansion, the length is never correct after the first invocation of getline (leading to your actual crash; the rest of the problems are just not something you saw yet).

解决所有问题

  1. 每次迭代使用单独的指针和长度,
  2. 确保在 getline 调用之前将它们正确初始化为 null,0
  3. 如果您成功读取了该行,扩展行指针缓冲区.
  4. 存储指针,丢弃长度,并在下一次迭代之前将两者重置为 null,0.
  1. Use a separate pointer and length for each iteration,
  2. Ensure they're properly initialized to null,0 before the getline call
  3. If you read the line successfully, then expand the line pointer buffer.
  4. Store the pointer, discard the length, and reset both to null,0 before the next iteration.

实际上,它看起来像这样:

In practice, it looks like this:

#define _POSIX_C_SOURCE  200809L
#include <stdio.h>
#include <stdlib.h>

char **readFile(const char *fileName, size_t *lines)
{
    FILE *fp = fopen(fileName, "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    // initially empty, no size or capacity
    char **result = NULL;
    size_t size = 0;
    size_t capacity = 0;

    size_t len = 0;
    char *line = NULL;
    while (getline(&line, &len, fp) != -1)
    {
        if (size == capacity)
        {
            size_t new_capacity = (capacity ? 2 * capacity : 1);
            void *tmp = realloc(result, new_capacity * sizeof *result);
            if (tmp == NULL)
            {
                perror("Failed to expand lines buffer");
                exit(EXIT_FAILURE);
            }

            // recoup the expanded buffer and capacity
            result = tmp;
            capacity = new_capacity;
        }

        result[size++] = line;

        // reset these to NULL,0. they trigger the fresh allocation
        //  and size storage on the next iteration.
        line = NULL;
        len = 0;
    }

    // if getline allocated a buffer on the failure case
    //  get rid of it (didn't see that coming).
    if (line)
        free(line);

    fclose(fp);

    *lines = size;
    return result;
}

int main()
{
    size_t count = 0;
    char **lines = readFile("/usr/share/dict/words", &count);
    if (lines)
    {
        for (size_t i = 0; i < count; ++i)
        {
            fputs(lines[i], stdout);
            free(lines[i]);
        }

        free(lines);
    }

    return 0;
}

在 Linux/Mac 系统上,/usr/share/dict/words 包含大约 25 万个英语单词.在我的库存 Mac 上,它是 235886(你的会有所不同).调用者获取行指针和计数,并负责释放其中的内容.

On a stock Linux/Mac system /usr/share/dict/words contains about a quarter-million words in the English language. On my stock Mac, its 235886 (yours will vary). The callers gets the line pointer and the count, and is responsible for freeing the content therein.

输出

A
a
aa
aal
aalii
aam
Aani
aardvark
aardwolf
Aaron
Aaronic
Aaronical
Aaronite
Aaronitic
Aaru
.... a ton of lines omitted ....
zymotically
zymotize
zymotoxic
zymurgy
Zyrenian
Zyrian
Zyryan
zythem
Zythia
zythum
Zyzomys
Zyzzogeton

Valgrind 总结

==17709== 
==17709== HEAP SUMMARY:
==17709==     in use at exit: 0 bytes in 0 blocks
==17709==   total heap usage: 235,909 allocs, 235,909 frees, 32,506,328 bytes allocated
==17709== 
==17709== All heap blocks were freed -- no leaks are possible
==17709== 


替代方案:让 getline 重用其缓冲区


Alternative: Let getline reuse its buffer

不能保证 getline 分配的缓冲区有效地匹配行长度.事实上,唯一的保证是,在成功执行时,函数返回包括分隔符(但不包括终止符)在内的字符数,并且内存保存该数据.实际分配大小可能远不止于此,而该空间实际上被浪费了.

There is no guarantee the buffer getline allocates matches the line length efficiently. In fact, the only guarantee is, on successful execution, the function returns the number of chars including the delimiter (but not the terminator), and the memory holds that data. The actual allocation size could be considerably more than that, and that space is effectively wasted.

为了证明这一点,请考虑以下内容.相同的代码,但我们不会在每个循环中重置缓冲区,而不是直接存储其指针,而是存储该行的 strdup.请注意,这仅在该行 包含嵌入的空字符时才有效.这允许 getline 重用其缓冲区,并且仅在需要时为每次读取进行扩展.我们负责制作行数据的实际副本(并且我们使用 POSIX strdup).执行时仍然没有泄漏,但请注意 valgrind 摘要,特别是分配的字节数与上面先前版本的字节数相比.

To demonstrate this, consider the following. The same code, but we do NOT reset the buffer on each loop, and rather than store its pointer directly, we store a strdup of the line. Note this only works if the line does not contain embedded null chars. This allows getline to reuse its buffer, and only expand if needed, for each read. We're responsible for making the actual copy of the line data (and we do using POSIX strdup). When executed there are still no leaks, but note the valgrind summary, specifically the number of bytes allocated in comparison to the number of bytes from the previous version above.

char **readFile(const char *fileName, size_t *lines)
{
    FILE *fp = fopen(fileName, "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    // initially empty, no size or capacity
    char **result = NULL;
    size_t size = 0;
    size_t capacity = 0;

    size_t len = 0;
    char *line = NULL;
    while (getline(&line, &len, fp) != -1)
    {
        if (size == capacity)
        {
            size_t new_capacity = (capacity ? 2 * capacity : 1);
            void *tmp = realloc(result, new_capacity * sizeof *result);
            if (tmp == NULL)
            {
                perror("Failed to expand lines buffer");
                exit(EXIT_FAILURE);
            }

            // recoup the expanded buffer and capacity
            result = tmp;
            capacity = new_capacity;
        }

        // make copy here. let getline reuse 'line'
        result[size++] = strdup(line);
    }

    // free whatever was left
    if (line)
        free(line);

    fclose(fp);

    *lines = size;
    return result;
}

Valgrind 总结

==17898== 
==17898== HEAP SUMMARY:
==17898==     in use at exit: 0 bytes in 0 blocks
==17898==   total heap usage: 235,909 allocs, 235,909 frees, 6,929,003 bytes allocated
==17898== 
==17898== All heap blocks were freed -- no leaks are possible
==17898== 

分配的数量是相同的(这告诉我们 getline 预先分配了一个足够大的缓冲区,永远不需要扩展),但实际分配的总空间相当更高效,因为现在我们将字符串存储在分配以匹配其长度的缓冲区中;不是任何 getline 作为读取缓冲区.

The number of allocations is the same (which tells us getline allocated a large enough buffer up front to never need expansion), but the actual total allocated space is considerably more efficient, as now we are storing strings in buffers allocated to match their length; not whatever getline stood up as a read buffer.

这篇关于Valgrind:REALLOC 未初始化的值是由堆分配创建的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆