字符串数组的动态分配失败。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;
}