动态分配字符串数组失败。 realloc()错误

时间:2018-08-03 03:39:39

标签: 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{
        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)

我已经看过有关该主题的其他主题,但似乎都没有帮助。我无法将在此学到的任何知识应用到我的问题上。

我使用了调试器(VS Code和gdb)。数据已成功写入buffer数组的索引0、1、2、3,但显示错误:无法访问索引4的地址0xfbad2488的内存,并在异常时暂停。

关于该主题的另一个线索表明,某处可能存在一个野指针。但是我什么都看不到。

我花了几天的时间试图解决这个问题。任何帮助将不胜感激。

谢谢。

1 个答案:

答案 0 :(得分:2)

您的算法在很多方面都是错误的,包括:

  • buffer按值传递buffer = ...是分配项的任何修改对调用方来说都不是什么。在C语言中,参数始终是按值传递的(包括数组,但它们的“值”是对第一个元素的临时指针的转换,因此无论您是否要使用它,都可以得到一个by-ref同义词)。

  • 您的realloc使用错误。它应该基于循环的迭代(作为计数乘以char *的大小来扩展)。您只有后者,没有计数乘数。因此,您使用该char *调用分配的单个realloc决不会超过。

  • 您的循环终止条件错误。您的fscanf调用应检查要处理的预期参数个数,在您的情况下为 1 。相反,您要寻找的是任何非零值,当您按下EOF时,该值将为零。因此,循环永远不会终止。

  • 您的fscanf调用不受缓冲区溢出的保护:您正在为读取的每个字符串分配一个静态大小的字符串,但并不限制%s格式化为指定的静态大小。这是缓冲区溢出的秘诀。

  • 从未检查过IO功能是否成功:以下API可能会失败,但您绝不会检查这种可能性:fopenfscanf,{ {1}},realloc。否则,您违反了Henry Spencer's 6th Commandment for C Programmers:“如果在遇到困难的情况下通告某个函数返回错误代码,即使检查的大小增加了三倍,也应检查该代码,您的代码并在您的打字手指中产生疼痛,因为如果您认为“这不可能发生在我身上”,诸神肯定会因你的傲慢而惩罚你。”

  • 没有用于将分配的字符串计数传达给调用方的机制:此函数的调用方期望得到结果malloc。假设您修复了该列表中的第一项,您仍然没有为调用方提供任何方法来知道char**返回时该指针序列有多长时间。超出参数和/或形式的结构可能是对此的解决方案。或者也许是一个终止readFile指针来指示列表已完成。

  • (中等)您从不检查NULL :相反,您只是将argc直接发送到argv,并假定文件名是位于readFile,并且始终有效。不要那样做argv[1]应该使用readFile或单个FILE*文件名,并采取相应的措施。这样会更健壮。

  • (次要):额外分配:即使解决了上述问题,您仍然会在序列中留下一个额外的缓冲区分配; 无法阅读的内容。在这种情况下,它并不重要,因为调用者不知道首先分配了多少个字符串(请参阅上一项)。


总结以上所有内容将需要对您发布的几乎所有内容进行基本重写。最后,代码看起来是如此不同,几乎不值得尝试挽救这里的内容。相反,请查看您所做的事情,查看此列表,然后看看问题出在哪里。有很多选择。

样品

const char *

输出(针对/ usr / share / dict / words运行)

#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;
}

改进

此代码中的辅助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的声明看起来很奇怪,但是对于返回大小为N的指针数组,它是正确的。见下文:

readFile

输出与以前相同。


另一项改进:几何增长

通过一些简单的更改,我们可以通过仅以两倍大小的增长模式进行操作来显着减少#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

输出

输出与以前相同,但是我添加了工具来显示何时发生扩展以说明扩展和最终收缩。我将忽略其余的输出(超过20万行的单词)

#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;
}