字符串数组的动态分配失败。realloc()错误
我正在尝试编写一个简单的程序,从文件中读取单词,并打印作为参数传递给它的特定单词的出现次数 为此,我使用字符串数组的动态分配失败。realloc()错误,c,string,file,dynamic-memory-allocation,C,String,File,Dynamic Memory Allocation,我正在尝试编写一个简单的程序,从文件中读取单词,并打印作为参数传递给它的特定单词的出现次数 为此,我使用fscanf读取单词并将它们复制到动态分配的字符串数组中 由于某种原因,我收到一条错误消息 以下是readFile函数的代码: void readFile(char** buffer, char** argv){ unsigned int i=0; FILE* file; file = fopen(argv[1], "r"); do{ buffe
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);
}
这是的主要功能:
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)
我已经看过关于这个主题的其他帖子,但它们似乎都没有帮助。我不能把我在那里学到的东西应用到我的问题上
我使用了一个调试器(VS代码与gdb)。数据已成功写入缓冲区
数组的索引0,1,2,3,但显示错误:无法访问索引4的地址0xfbad2488处的内存,并在异常时暂停
关于这个主题的另一条线索表明,某个地方可能有一个野生指针。但我哪儿都没看到
我花了好几天的时间想弄明白这一点。任何帮助都将不胜感激
谢谢。您的算法在许多方面都是错误的,包括:
缓冲区
通过值传递。如果buffer=…
是赋值,则任何修改对调用者都毫无意义。在C语言中,参数总是按值传递(包括数组,但它们的“值”是指向第一个元素的临时指针的转换,所以无论您是否需要,都会在其中获得by ref同义词)
- 您的
realloc
用法错误。它应该根据循环的迭代进行扩展,即计数乘以char*
的大小。只有后者,没有计数乘数。因此,您永远不会为该realloc
调用分配多个char*
- 您的循环终止条件错误。您的
fscanf
调用应该检查要处理的参数的预期数量,在您的情况下是1。相反,您要寻找的是任何非零值,当您点击它时,EOF
将是该值。因此,循环永远不会终止
- 您的
fscanf
调用不受缓冲区溢出的保护:您正在为每次读取的字符串分配一个静态大小的字符串,但没有将%s
格式限制为指定的静态大小。这是造成缓冲区溢出的原因
- 从未检查IO函数是否成功/失败:以下API可能失败,但您从未检查过这种可能性:
fopen
,fscanf
,realloc
,malloc
。如果不这样做,您就违反了:“如果一个函数在遇到困难时被广告返回一个错误代码,你应该检查该代码,是的,即使检查的大小是你代码的三倍,并且在你的打字手指上产生疼痛,因为如果你认为‘这不会发生在我身上’,上帝肯定会惩罚你的傲慢。”
- 没有将分配的字符串计数传递给调用者的机制:此函数的调用者希望得到一个结果
char**
。假设您修复了此列表中的第一项,则您仍然没有向调用者提供任何方法来知道readFile
返回时指针序列的长度。an out参数和/或形式化结构是一种可能的解决方案。或者可能是一个终止的NULL
指针,指示列表已完成
- (中等)您从不检查
argc
:相反,您只需将argv
直接发送到readFile
,并假定文件名位于argv[1]
并且始终有效。不要这样做。readFile
应该采用文件名*
或单个常量字符*
文件名,并相应地采取行动。这样会更加健壮
- (次要):额外分配:即使修复了上述各项,您仍会在序列中保留一个额外的缓冲区分配;即无法读取的缓冲区分配。在这种情况下,这并不重要,因为调用方不知道首先分配了多少字符串(请参阅上一项)
支撑以上所有内容需要对您发布的几乎所有内容进行基本重写。最终,代码看起来会如此不同,几乎不值得尝试修复这里的内容。相反,看看您所做的,看看下面的列表,看看哪里出了问题。有很多可供选择
样本
#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;
}
改进
此代码中的次要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;
}
输出
输出与以前相同,但我添加了显示何时发生扩展的工具来说明扩展和最终收缩。我将省略输出的其余部分(超过200k行字)
将容量扩展到1
将容量扩大到3
将容量扩大到7
将容量扩大到15个
将容量扩大到31
将容量扩大到63
将产能扩大到127
将容量扩大到255个
将产能扩大到511
将容量扩大到1023
扩大
#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;
}