Valgrind:REALLOC 未初始化的值是由堆分配创建的

Valgrind: REALLOC Uninitialised value was created by a heap allocation

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

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 弹出错误:

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

在线错误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 扩展序列时,不会采用这种可供性。实际上,如果原始数组如下所示:

[ NULL, NULL ]

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

[ addr1, addr2 ]

realloc 启动并给你 this :

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

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

解决这一切

  1. 每次迭代使用单独的指针和长度,
  2. 确保在 getline 调用
  3. 之前将它们正确初始化为 null,0
  4. 如果您成功读取该行,然后扩展行指针缓冲区。
  5. 存储指针,丢弃长度,并在下一次迭代之前将两者重置为 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 分配的缓冲区有效匹配行长度。事实上,唯一的保证是,在成功执行时,函数 returns 包括定界符(但不包括终止符)的字符数,并且内存保存该数据。实际分配大小可能远不止于此,space 实际上是浪费了。

为了证明这一点,请考虑以下内容。相同的代码,但我们不在每个循环中重置缓冲区,而不是直接存储它的指针,我们存储行的 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 预先分配了足够大的缓冲区,永远不需要扩展),但实际分配的总数 space 是 相当 更有效,因为现在我们将字符串存储在分配给它们长度的缓冲区中;不是任何 getline 作为读取缓冲区。