【问题标题】:Valgrind: REALLOC Uninitialised value was created by a heap allocationValgrind:REALLOC 未初始化的值是由堆分配创建的
【发布时间】:2021-06-14 06:34:56
【问题描述】:

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

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

Valgrind 弹出错误:

error

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

在线出错: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);
}

【问题讨论】:

  • 该错误出现在哪一行代码上?
  • 仅供参考,&amp;(*(result + i)) ==> result + i 。解引用地址完全没有意义。此外,指针指向指针result 所引用的指针的扩展区域在realloc 之后的扩展区域中是indeterminate。只有来自 calloc 的原始序列将被空初始化(例如,即使在 realloc 之后仍将是,并最终填充有 getline 作用于空填充语义)。
  • getline 要求第一个参数是指向 NULL 的指针或指向有效内存块的指针。由于realloc 没有初始化内存的扩展部分,它包含未初始化的值,因此抱怨getline 的错误正在使用这些值。
  • @MooingDuck 谢谢,更新了问题。
  • @WhozCraig 我试图实现你所说的,但后来我得到 Leak_DefinitelyLost on while (getline(result + i, &len, fp) != -1) ... line 请问你来展示这应该如何实现?

标签: c memory valgrind


【解决方案1】:

在原始发布的代码中,result 通过calloc 进行初始分配,它将其中的内容零初始化,并且作为指针,进行空初始化。稍后,当通过 realloc 扩展序列时,就没有这样的可供性​​了。实际上,如果原始数组如下所示:

[ NULL, NULL ]

添加两个元素后,如下所示:

[ addr1, addr2 ]

realloc 启动并为您提供 this

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

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

解决所有这些问题

  1. 每次迭代使用单独的指针和长度,
  2. 确保在 getline 调用之前将它们正确初始化为 null,0
  3. 如果您成功读取该行,然后扩展行指针缓冲区。
  4. 存储指针,丢弃长度,并在下一次迭代之前将两者重置为 null,0。

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

#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(你的会有所不同)。调用者获取行指针和计数,并负责释放其中的内容。

输出

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 重用其缓冲区

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

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

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 站起来作为读取缓冲区。

【讨论】:

  • 这是一个超级解决方案。非常感谢你。
  • 对不起,但是在你的代码上 valgrind 抛出 Leak_DefinitelyLost -> vg_replace_malloc.c -> 1 个块中的 120 个字节肯定会在丢失记录 1 of 1 中丢失。系统:Linux taurus 4.19.0- 6-amd64 #1 SMP Debian 4.19.67-2+deb10u2 (2019-11-11) x86_64 GNU/Linux。当我们在 getline() 函数中读取 line 变量时会发生这种情况。我可以请你帮忙吗?
  • @Ignatella 显然 getline 可以分配缓冲区空间并将其返回给您,即使在它返回 -1 的退出情况下也是如此。文档的那部分是粗略的,或者我对它的看法是。无论如何,代码已更新以添加该案例的显式免费,摘要报告反映了这一点。不错的收获,顺便说一句。没想到会这样。
猜你喜欢
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多