字符串数组的动态分配失败。重新分配()错误
Dynamic allocation of array of strings fails. realloc() error
我正在尝试编写一个简单的程序,该程序将从文件中读取单词并打印作为参数传递给它的特定单词的出现次数。
为此,我使用 fscanf
读取单词并将它们复制到动态分配的字符串数组中。
出于某种原因,我收到一条错误消息。
这里是 readFile
函数的代码:
void readFile(char** buffer, char** argv){
unsigned int i=0;
FILE* file;
file = fopen(argv[1], "r");
do{
buffer = realloc(buffer, sizeof(char*));
buffer[i] = malloc(46);
}while(fscanf(file, "%s", buffer[i++]));
fclose(file);
}
这里是 main
函数:
int main(int argc, char** argv){
char** buffer = NULL;
readFile(buffer, argv);
printf("%s\n", buffer[0]);
return 0;
}
我收到以下错误消息:
realloc(): invalid next size
Aborted (core dumped)
我查看了有关此主题的其他主题,但其中 none 似乎有帮助。我无法将在那里学到的知识应用到我的问题中。
我使用了调试器(VS Code with gdb)。数据已成功写入 buffer
数组的索引 0、1、2、3,但显示错误:无法访问索引 4 的地址 0xfbad2488 处的内存并因异常而暂停。
关于此主题的另一个话题表明某处可能存在野指针。但是我到处都看不到。
我花了好几天时间想弄清楚这个问题。任何帮助将不胜感激。
谢谢。
你的算法在很多方面都是错误的,包括:
buffer
按值传递。 buffer = ...
是赋值的任何修改对调用者来说都意味着 nothing。在 C 中,参数总是按值传递(包括数组,但它们的 "value" 是到第一个元素的临时指针的转换,因此无论您是否需要,您都会在那里得到一个 by-ref 同义词)。
你的realloc
用法是错误的。它应该基于循环的迭代扩展为计数乘以 char *
的大小。你只有后者,没有计数乘数。因此,您永远不会为 realloc
调用分配超过一个 char *
。
你的循环终止条件是错误的。你的 fscanf
调用应该检查要处理的预期参数数量,在你的情况下是 1。相反,您正在寻找任何非零值,EOF
将在您点击它时出现。因此,循环永远不会终止。
您的 fscanf
调用不受缓冲区溢出保护:您正在为每个读取的字符串分配一个静态大小的字符串,但不是将 %s
格式限制为指定的静态大小。这是缓冲区溢出的秘诀。
从未检查过任何 IO 函数 success/failure:以下 API 可能会失败,但您永远不会检查这种可能性:fopen
, fscanf
, realloc
, malloc
。如果不这样做,你就违反了 Henry Spencer's 6th Commandment for C Programmers : "If a function be advertised to return an error code in the event of difficulties, thou shalt check for that code, yea, even though the checks triple the size of thy code and produce aches in thy typing fingers, for if thou thinkest ``it cannot happen to me'', the gods shall surely punish thee for thy arrogance."
没有将分配的字符串计数传达给调用者的机制:此函数的调用者期望结果 char**
。假设您修复了此列表中的第一项,您仍然没有向调用者提供任何了解指针序列在 readFile
returns 时的长度的方法。一个输出参数 and/or 一个正式的结构是一个可能的解决方案。或者可能是一个终止 NULL
指针来指示列表已完成。
(中等)您从不检查 argc
:相反,您直接将 argv
发送到 readFile
,并假设文件名位于 argv[1]
并且始终有效。不要那样做。 readFile
应该采用 FILE*
或单个 const char *
文件名,并据此采取行动。它会更健壮。
(Minor) : Extra allocation : 即使修复了上述项目,你仍然会在你的序列中留下一个额外的缓冲区分配; 未能阅读的那个。在这种情况下这并不重要,因为调用者不知道首先分配了多少字符串(参见上一项)。
要支持以上所有内容,需要对您发布的几乎所有内容进行基本重写。最后,代码看起来会大不相同,几乎不值得尝试挽救这里的内容。相反,看看你做了什么,看看这个列表,看看哪里出了问题。有很多可供选择。
样本
#include <stdio.h>
#include <stdlib.h>
#define STR_MAX_LEN 46
char ** readFile(const char *fname)
{
char **strs = NULL;
int len = 0;
FILE *fp = fopen(fname, "r");
if (fp != NULL)
{
do
{
// array expansion
void *tmp = realloc(strs, (len+1) * sizeof *strs);
if (tmp == NULL)
{
// failed. cleanup prior success
perror("Failed to expand pointer array");
for (int i=0; i<len; ++i)
free(strs[i]);
free(strs);
strs = NULL;
break;
}
// allocation was good; save off new pointer
strs = tmp;
strs[len] = malloc( STR_MAX_LEN );
if (strs[len] == NULL)
{
// failed. cleanup prior sucess
perror("Failed to allocate string buffer");
for (int i=0; i<len; ++i)
free(strs[i]);
free(strs);
strs = NULL;
break;
}
if (fscanf(fp, "%45s", strs[len]) == 1)
{
++len;
}
else
{
// read failed. we're leaving regardless. the last
// allocation is thrown out, but we terminate the list
// with a NULL to indicate end-of-list to the caller
free(strs[len]);
strs[len] = NULL;
break;
}
} while (1);
fclose(fp);
}
return strs;
}
int main(int argc, char *argv[])
{
if (argc < 2)
exit(EXIT_FAILURE);
char **strs = readFile(argv[1]);
if (strs)
{
// enumerate and free in the same loop
for (char **pp = strs; *pp; ++pp)
{
puts(*pp);
free(*pp);
}
// free the now-defunct pointer array
free(strs);
}
return EXIT_SUCCESS;
}
输出(运行对/usr/share/dict/words)
A
a
aa
aal
aalii
aam
Aani
aardvark
aardwolf
Aaron
Aaronic
Aaronical
Aaronite
Aaronitic
Aaru
Ab
aba
Ababdeh
Ababua
abac
abaca
......
zymotechny
zymotic
zymotically
zymotize
zymotoxic
zymurgy
Zyrenian
Zyrian
Zyryan
zythem
Zythia
zythum
Zyzomys
Zyzzogeton
改进
这段代码中的次要malloc
完全没有意义。您使用的是固定长度的字最大大小,因此您可以轻松地将数组重组为指针以使用它:
char (*strs)[STR_MAX_LEN]
并完全消除每个字符串的 malloc
代码。这确实留下了如何告诉调用者分配了多少字符串的问题。在之前的版本中,我们使用 NULL
指针来指示列表结束。在这个版本中,我们可以简单地使用零长度字符串。这样做会使 readFile
的声明看起来很奇怪,但是对于返回一个指向大小为 N 的数组的指针来说,它是正确的。见下文:
#include <stdio.h>
#include <stdlib.h>
#define STR_MAX_LEN 46
char (*readFile(const char *fname))[STR_MAX_LEN]
{
char (*strs)[STR_MAX_LEN] = NULL;
int len = 0;
FILE *fp = fopen(fname, "r");
if (fp != NULL)
{
do
{
// array expansion
void *tmp = realloc(strs, (len+1) * sizeof *strs);
if (tmp == NULL)
{
// failed. cleanup prior success
perror("Failed to expand pointer array");
free(strs);
strs = NULL;
break;
}
// allocation was good; save off new pointer
strs = tmp;
if (fscanf(fp, "%45s", strs[len]) == 1)
{
++len;
}
else
{
// read failed. make the final string zero-length
strs[len][0] = 0;
break;
}
} while (1);
fclose(fp);
}
return strs;
}
int main(int argc, char *argv[])
{
if (argc < 2)
exit(EXIT_FAILURE);
char (*strs)[STR_MAX_LEN] = readFile(argv[1]);
if (strs)
{
// enumerate and free in the same loop
for (char (*s)[STR_MAX_LEN] = strs; (*s)[0]; ++s)
puts(*s);
free(strs);
}
return EXIT_SUCCESS;
}
输出与之前相同。
另一个改进:几何增长
通过一些简单的更改,我们可以显着减少 realloc
调用(我们目前正在为每个添加的字符串执行一个调用),方法是仅以双倍大小的增长模式进行调用。如果每次我们重新分配时,我们将先前分配的大小加倍,我们将在下一次分配之前使越来越多的 space 可用于读取更大数量的字符串:
#include <stdio.h>
#include <stdlib.h>
#define STR_MAX_LEN 46
char (*readFile(const char *fname))[STR_MAX_LEN]
{
char (*strs)[STR_MAX_LEN] = NULL;
int len = 0;
int capacity = 0;
FILE *fp = fopen(fname, "r");
if (fp != NULL)
{
do
{
if (len == capacity)
{
printf("Expanding capacity to %d\n", (2 * capacity + 1));
void *tmp = realloc(strs, (2 * capacity + 1) * sizeof *strs);
if (tmp == NULL)
{
// failed. cleanup prior success
perror("Failed to expand string array");
free(strs);
strs = NULL;
break;
}
// save the new string pointer and capacity
strs = tmp;
capacity = 2 * capacity + 1;
}
if (fscanf(fp, "%45s", strs[len]) == 1)
{
++len;
}
else
{
// read failed. make the final string zero-length
strs[len][0] = 0;
break;
}
} while (1);
// shrink if needed. remember to retain the final empty string
if (strs && (len+1) < capacity)
{
printf("Shrinking capacity to %d\n", len);
void *tmp = realloc(strs, (len+1) * sizeof *strs);
if (tmp)
strs = tmp;
}
fclose(fp);
}
return strs;
}
int main(int argc, char *argv[])
{
if (argc < 2)
exit(EXIT_FAILURE);
char (*strs)[STR_MAX_LEN] = readFile(argv[1]);
if (strs)
{
// enumerate and free in the same loop
for (char (*s)[STR_MAX_LEN] = strs; (*s)[0]; ++s)
puts(*s);
// free the now-defunct pointer array
free(strs);
}
return EXIT_SUCCESS;
}
输出
输出与以前相同,但我添加了检测以显示何时发生扩展以说明扩展和最终收缩。我将省略其余的输出(超过 20 万行字)
Expanding capacity to 1
Expanding capacity to 3
Expanding capacity to 7
Expanding capacity to 15
Expanding capacity to 31
Expanding capacity to 63
Expanding capacity to 127
Expanding capacity to 255
Expanding capacity to 511
Expanding capacity to 1023
Expanding capacity to 2047
Expanding capacity to 4095
Expanding capacity to 8191
Expanding capacity to 16383
Expanding capacity to 32767
Expanding capacity to 65535
Expanding capacity to 131071
Expanding capacity to 262143
Shrinking capacity to 235886
我正在尝试编写一个简单的程序,该程序将从文件中读取单词并打印作为参数传递给它的特定单词的出现次数。
为此,我使用 fscanf
读取单词并将它们复制到动态分配的字符串数组中。
出于某种原因,我收到一条错误消息。
这里是 readFile
函数的代码:
void readFile(char** buffer, char** argv){
unsigned int i=0;
FILE* file;
file = fopen(argv[1], "r");
do{
buffer = realloc(buffer, sizeof(char*));
buffer[i] = malloc(46);
}while(fscanf(file, "%s", buffer[i++]));
fclose(file);
}
这里是 main
函数:
int main(int argc, char** argv){
char** buffer = NULL;
readFile(buffer, argv);
printf("%s\n", buffer[0]);
return 0;
}
我收到以下错误消息:
realloc(): invalid next size
Aborted (core dumped)
我查看了有关此主题的其他主题,但其中 none 似乎有帮助。我无法将在那里学到的知识应用到我的问题中。
我使用了调试器(VS Code with gdb)。数据已成功写入 buffer
数组的索引 0、1、2、3,但显示错误:无法访问索引 4 的地址 0xfbad2488 处的内存并因异常而暂停。
关于此主题的另一个话题表明某处可能存在野指针。但是我到处都看不到。
我花了好几天时间想弄清楚这个问题。任何帮助将不胜感激。
谢谢。
你的算法在很多方面都是错误的,包括:
buffer
按值传递。buffer = ...
是赋值的任何修改对调用者来说都意味着 nothing。在 C 中,参数总是按值传递(包括数组,但它们的 "value" 是到第一个元素的临时指针的转换,因此无论您是否需要,您都会在那里得到一个 by-ref 同义词)。你的
realloc
用法是错误的。它应该基于循环的迭代扩展为计数乘以char *
的大小。你只有后者,没有计数乘数。因此,您永远不会为realloc
调用分配超过一个char *
。你的循环终止条件是错误的。你的
fscanf
调用应该检查要处理的预期参数数量,在你的情况下是 1。相反,您正在寻找任何非零值,EOF
将在您点击它时出现。因此,循环永远不会终止。您的
fscanf
调用不受缓冲区溢出保护:您正在为每个读取的字符串分配一个静态大小的字符串,但不是将%s
格式限制为指定的静态大小。这是缓冲区溢出的秘诀。从未检查过任何 IO 函数 success/failure:以下 API 可能会失败,但您永远不会检查这种可能性:
fopen
,fscanf
,realloc
,malloc
。如果不这样做,你就违反了 Henry Spencer's 6th Commandment for C Programmers : "If a function be advertised to return an error code in the event of difficulties, thou shalt check for that code, yea, even though the checks triple the size of thy code and produce aches in thy typing fingers, for if thou thinkest ``it cannot happen to me'', the gods shall surely punish thee for thy arrogance."没有将分配的字符串计数传达给调用者的机制:此函数的调用者期望结果
char**
。假设您修复了此列表中的第一项,您仍然没有向调用者提供任何了解指针序列在readFile
returns 时的长度的方法。一个输出参数 and/or 一个正式的结构是一个可能的解决方案。或者可能是一个终止NULL
指针来指示列表已完成。(中等)您从不检查
argc
:相反,您直接将argv
发送到readFile
,并假设文件名位于argv[1]
并且始终有效。不要那样做。readFile
应该采用FILE*
或单个const char *
文件名,并据此采取行动。它会更健壮。(Minor) : Extra allocation : 即使修复了上述项目,你仍然会在你的序列中留下一个额外的缓冲区分配; 未能阅读的那个。在这种情况下这并不重要,因为调用者不知道首先分配了多少字符串(参见上一项)。
要支持以上所有内容,需要对您发布的几乎所有内容进行基本重写。最后,代码看起来会大不相同,几乎不值得尝试挽救这里的内容。相反,看看你做了什么,看看这个列表,看看哪里出了问题。有很多可供选择。
样本
#include <stdio.h>
#include <stdlib.h>
#define STR_MAX_LEN 46
char ** readFile(const char *fname)
{
char **strs = NULL;
int len = 0;
FILE *fp = fopen(fname, "r");
if (fp != NULL)
{
do
{
// array expansion
void *tmp = realloc(strs, (len+1) * sizeof *strs);
if (tmp == NULL)
{
// failed. cleanup prior success
perror("Failed to expand pointer array");
for (int i=0; i<len; ++i)
free(strs[i]);
free(strs);
strs = NULL;
break;
}
// allocation was good; save off new pointer
strs = tmp;
strs[len] = malloc( STR_MAX_LEN );
if (strs[len] == NULL)
{
// failed. cleanup prior sucess
perror("Failed to allocate string buffer");
for (int i=0; i<len; ++i)
free(strs[i]);
free(strs);
strs = NULL;
break;
}
if (fscanf(fp, "%45s", strs[len]) == 1)
{
++len;
}
else
{
// read failed. we're leaving regardless. the last
// allocation is thrown out, but we terminate the list
// with a NULL to indicate end-of-list to the caller
free(strs[len]);
strs[len] = NULL;
break;
}
} while (1);
fclose(fp);
}
return strs;
}
int main(int argc, char *argv[])
{
if (argc < 2)
exit(EXIT_FAILURE);
char **strs = readFile(argv[1]);
if (strs)
{
// enumerate and free in the same loop
for (char **pp = strs; *pp; ++pp)
{
puts(*pp);
free(*pp);
}
// free the now-defunct pointer array
free(strs);
}
return EXIT_SUCCESS;
}
输出(运行对/usr/share/dict/words)
A
a
aa
aal
aalii
aam
Aani
aardvark
aardwolf
Aaron
Aaronic
Aaronical
Aaronite
Aaronitic
Aaru
Ab
aba
Ababdeh
Ababua
abac
abaca
......
zymotechny
zymotic
zymotically
zymotize
zymotoxic
zymurgy
Zyrenian
Zyrian
Zyryan
zythem
Zythia
zythum
Zyzomys
Zyzzogeton
改进
这段代码中的次要malloc
完全没有意义。您使用的是固定长度的字最大大小,因此您可以轻松地将数组重组为指针以使用它:
char (*strs)[STR_MAX_LEN]
并完全消除每个字符串的 malloc
代码。这确实留下了如何告诉调用者分配了多少字符串的问题。在之前的版本中,我们使用 NULL
指针来指示列表结束。在这个版本中,我们可以简单地使用零长度字符串。这样做会使 readFile
的声明看起来很奇怪,但是对于返回一个指向大小为 N 的数组的指针来说,它是正确的。见下文:
#include <stdio.h>
#include <stdlib.h>
#define STR_MAX_LEN 46
char (*readFile(const char *fname))[STR_MAX_LEN]
{
char (*strs)[STR_MAX_LEN] = NULL;
int len = 0;
FILE *fp = fopen(fname, "r");
if (fp != NULL)
{
do
{
// array expansion
void *tmp = realloc(strs, (len+1) * sizeof *strs);
if (tmp == NULL)
{
// failed. cleanup prior success
perror("Failed to expand pointer array");
free(strs);
strs = NULL;
break;
}
// allocation was good; save off new pointer
strs = tmp;
if (fscanf(fp, "%45s", strs[len]) == 1)
{
++len;
}
else
{
// read failed. make the final string zero-length
strs[len][0] = 0;
break;
}
} while (1);
fclose(fp);
}
return strs;
}
int main(int argc, char *argv[])
{
if (argc < 2)
exit(EXIT_FAILURE);
char (*strs)[STR_MAX_LEN] = readFile(argv[1]);
if (strs)
{
// enumerate and free in the same loop
for (char (*s)[STR_MAX_LEN] = strs; (*s)[0]; ++s)
puts(*s);
free(strs);
}
return EXIT_SUCCESS;
}
输出与之前相同。
另一个改进:几何增长
通过一些简单的更改,我们可以显着减少 realloc
调用(我们目前正在为每个添加的字符串执行一个调用),方法是仅以双倍大小的增长模式进行调用。如果每次我们重新分配时,我们将先前分配的大小加倍,我们将在下一次分配之前使越来越多的 space 可用于读取更大数量的字符串:
#include <stdio.h>
#include <stdlib.h>
#define STR_MAX_LEN 46
char (*readFile(const char *fname))[STR_MAX_LEN]
{
char (*strs)[STR_MAX_LEN] = NULL;
int len = 0;
int capacity = 0;
FILE *fp = fopen(fname, "r");
if (fp != NULL)
{
do
{
if (len == capacity)
{
printf("Expanding capacity to %d\n", (2 * capacity + 1));
void *tmp = realloc(strs, (2 * capacity + 1) * sizeof *strs);
if (tmp == NULL)
{
// failed. cleanup prior success
perror("Failed to expand string array");
free(strs);
strs = NULL;
break;
}
// save the new string pointer and capacity
strs = tmp;
capacity = 2 * capacity + 1;
}
if (fscanf(fp, "%45s", strs[len]) == 1)
{
++len;
}
else
{
// read failed. make the final string zero-length
strs[len][0] = 0;
break;
}
} while (1);
// shrink if needed. remember to retain the final empty string
if (strs && (len+1) < capacity)
{
printf("Shrinking capacity to %d\n", len);
void *tmp = realloc(strs, (len+1) * sizeof *strs);
if (tmp)
strs = tmp;
}
fclose(fp);
}
return strs;
}
int main(int argc, char *argv[])
{
if (argc < 2)
exit(EXIT_FAILURE);
char (*strs)[STR_MAX_LEN] = readFile(argv[1]);
if (strs)
{
// enumerate and free in the same loop
for (char (*s)[STR_MAX_LEN] = strs; (*s)[0]; ++s)
puts(*s);
// free the now-defunct pointer array
free(strs);
}
return EXIT_SUCCESS;
}
输出
输出与以前相同,但我添加了检测以显示何时发生扩展以说明扩展和最终收缩。我将省略其余的输出(超过 20 万行字)
Expanding capacity to 1
Expanding capacity to 3
Expanding capacity to 7
Expanding capacity to 15
Expanding capacity to 31
Expanding capacity to 63
Expanding capacity to 127
Expanding capacity to 255
Expanding capacity to 511
Expanding capacity to 1023
Expanding capacity to 2047
Expanding capacity to 4095
Expanding capacity to 8191
Expanding capacity to 16383
Expanding capacity to 32767
Expanding capacity to 65535
Expanding capacity to 131071
Expanding capacity to 262143
Shrinking capacity to 235886