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= 之后长度永远不正确](导致您的实际崩溃;其余问题只是您还没有看到)。
解决这一切
- 每次迭代使用单独的指针和长度,
- 确保在
getline
调用 之前将它们正确初始化为 null,0
- 如果您成功读取该行,然后扩展行指针缓冲区。
- 存储指针,丢弃长度,并在下一次迭代之前将两者重置为 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
作为读取缓冲区。
拜托,在阅读并尝试应用在 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= 之后长度永远不正确](导致您的实际崩溃;其余问题只是您还没有看到)。
解决这一切
- 每次迭代使用单独的指针和长度,
- 确保在
getline
调用 之前将它们正确初始化为 null,0
- 如果您成功读取该行,然后扩展行指针缓冲区。
- 存储指针,丢弃长度,并在下一次迭代之前将两者重置为 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
作为读取缓冲区。