【问题标题】:How to allocate memory of triple pointer in a C function如何在 C 函数中分配三重指针的内存
【发布时间】:2020-02-19 04:46:47
【问题描述】:

我是 C 新手,我有一个案例,我想读取一个每行一个单词的简单文本文件,我想将它保存到一个数组中。但是,我想在函数 main 中声明一个双指针,然后通过引用另一个函数来分配内存并填充双指针。如果不抛出分段错误,我似乎无法让它工作。任何建议将不胜感激。

这是我尝试过的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
test.txt = content between the "---"
---
first
second
third
fourth
fifth
sixth
seventh
---
*/

void allocmem (char ***tptr) {

    FILE *infile = fopen("test.txt", "r");
    int bufferLength = 5;
    char *buffer = malloc(sizeof(char) * bufferLength);

    int c, i, row, rows;
    c = i = row = 0;
    rows = 7;

/*
    I want to allocate an array of char strings to store the list from test.txt in
    I have tried different variants here
*/

    tptr = malloc(sizeof(char**) * rows);
    for (int r = 0; r < rows; r++) {
        *tptr[r] = malloc(sizeof(char*) * bufferLength);
    }

/*
    // another attempt
    *tptr = malloc(sizeof(char*) * rows);
    for (int r = 0; r < rows; r++) {
        *tptr[r] = malloc(sizeof(char) * bufferLength);
    }
*/

    while ((c = getc(infile)) != EOF) {

        if (c == '\n') {
            buffer[++i] = '\0';
            printf("buffer: %s\n", buffer);

            // I am also a little unsure how to append my buffer to the pointer array here too
            strcpy(*tptr[row++], buffer);
            memset(buffer, '\0', bufferLength);
            i = 0;
        } else {
            buffer[i++] = c;
        }

    }

    fclose(infile);

}

int main () {

    // double pointer to hold array value from test.txt in
    char **dptr;

    // allocate memory and read test.txt and store each row into the array
    allocmem(&dptr);

/*  
    print each element of dptr here yielding the expected results of:

    first
    second
    third
    fourth
    fifth
    sixth
    seventh
*/

    return 0;
}

【问题讨论】:

  • buffer[i++] = c; 是个问题,因为i 可能超过bufferLength
  • "我想读取一个简单的文本文件" --> 考虑读取文件两次:一次查找最长行和行数。下次保存数据。有更有效的方法,但这是一个很好的学习起点。
  • 使用(char ***tptr),目标不是“分配三重指针的内存”,而是“分配**指针的内存”。所以*tptr = malloc(....);
  • @chux-ReinstateMonica 感谢您的提醒。我在这里稍微截断了代码。我没有按照您的建议向前看,我正在检查添加下一个字符是否会超过缓冲区长度,如果是,我会调整缓冲区的大小。还要感谢您澄清双指针的内存分配。我可以在下面的解决方案中看到这一点。

标签: c pointers malloc


【解决方案1】:

要回答您的问题,可以通过type ***ptr 分配一个三重指针(正如@David C. Rankin 所说),但这根本不切实际。该函数可以简单地使用malloccalloc分配内存并存储结果,最后将指向结果的指针写入参数给出的指针取消引用它,例如*argument_ptr = result_pointer。我重写了代码以避免分段错误。代码如下:

void allocmem (char ***tptr) {
    // The aim of this function is to return the array of strings read from the file
    FILE *infile = fopen("test.txt", "r"); // Read text file
    const int bufferLength = 10, number_of_strings = 7; // Read string 7 times, for a maximum length of 10
    char **array = (char**)calloc(sizeof(char*), number_of_strings); // Allocate memory for a bunch of strings (char*), but the memory for the characters are not initialized
    for (int i = 0; i < number_of_strings; i++){
        char *string = (char*)calloc(sizeof(char), bufferLength); // Memory for the string, a.k.a. character array
        fgets(string, bufferLength, infile); // Read the string (word) from file
        char *c;
        if (c = strchr(string, '\n')) *c = '\0'; // Remove the newline character if exists
        array[i] = string; // Assign the string to the array
    }
    fclose(infile); // Close the file
    *tptr = array; // Return the array created by modifying the given pointer
}

如果不引发分段错误,我似乎无法让它工作。

第 35 行和第 53 行的分段错误是由错误的解引用引起的。在第 35 行,您应该简单地使用 tptr[r] 来访问 char** 指针,在第 53 行也是如此。

【讨论】:

  • 这是一个很好的答案,因为它使用本地双指针进行了说明(更容易使用,至少可以绕开我的脑袋),然后将其分配给取消引用的三重指针以取回值主要功能
  • @chux-ReinstateMonica 是的,你是对的。我假设文本文件以新行结尾。现在它已经修复了。
  • 不仅最后一行可能缺少'\n',超过bufferLength的行在string中也不会有'\n'
【解决方案2】:

虽然链接的示例比您在此处所需的要复杂得多,但原理是相同的。当您声明一个函数void 并消除返回指向新分配内存块的指针的能力时,您需要传递您需要为其分配存储空间的指针的地址。 p>

为什么是的地址

当您最初分配或重新分配时,内存块的地址将会改变。如果您只是将一个指针传递给您的函数以进行分配,则该函数会收到原始指针的 副本,该指针具有其自己的且非常不同的地址。在 C 中,所有参数都按值传递,并且函数接收一个副本。对函数中的副本所做的任何更改都会在函数返回时丢失。

但是,如果您传递变量的地址(指针间接一级的附加级别),该函数会收到指向您的变量的指针的副本,但该指针保存原始地址指针。因此,现在您可以进行更改并将新的内存块分配给函数中取消引用的指针,这会将内存块的新起始地址分配给原始指针,并且更改将在调用函数中看到。

在您的情况下,您想从文件中读取未知数量的字符串,并将它们添加到 void 函数中分配的字符串集合。如果您只是简单地在main() 中分配/重新分配,您可以简单地声明一个char **ptrs;(双指针),然后分配/重新分配您从文件中读取的每个字符串所拥有的指针数量,为字符串,并将块的地址分配给您新分配的指针,然后将字符串复制到您的指针 - 并重复直到用完字符串。

void 函数中分配和复制时唯一改变的是您将char **ptrs; 的地址传递给添加一级指针间接的函数(因此函数参数类型将为char ***,然后在函数中你必须删除一个额外的间接级别来操作指针.你还需要传递一个变量的地址来保存当前分配的指针数量——所以你知道你需要多少@987654329 @函数内。

总而言之,你的函数可以写成:

/* allocate pointer for *strings, allocate/copy s to (*strings)[*nptr] */
void allocmem (char ***strings, size_t *nptrs, const char *s)
{
    size_t len = strlen (s);    /* determine length of s */
    /* reallocate *string to a temporary pointer, adding 1 pointer */
    void *tmp = realloc (*strings, (*nptrs + 1) * sizeof **strings);

    if (!tmp) { /* validate EVERY allocation/reallocation */
        perror ("realloc-allocptr-strings");
        exit (EXIT_FAILURE);
    }
    *strings = tmp;     /* assign newly allocated block of pointers to *strings */

    (*strings)[*nptrs] = malloc (len + 1);      /* allocate storage for s */
    if (!(*strings)[*nptrs]) {  /* ditto */
        perror ("malloc-(*strings)[*nptrs]");
        exit (EXIT_FAILURE);
    }
    memcpy ((*strings)[*nptrs], s, len + 1);    /* copy s to allocated storage */

    *nptrs += 1;    /* increment pointer count after successful allocation/copy */
}

(注意:分配失败时,程序简单地退出——但对于实际代码,您需要一些方法来知道是否发生了两个分配以及字符串是否被复制。保存之前/之后nptrs 的计数可以提供部分图片,但无法告诉您哪个分配失败 - 如果 realloc() 失败,那么由于您使用了指向 realloc()tmp 指针,因此您的指针和字符串就像它们一样在函数调用被保留之前存在并且仍然可以通过原始指针访问)

(note2:(*strings)[*nptrs]*strings 周围的括号是必需的,因为在C Operator Precedence 中,[..] 的优先级高于取消引用运算符'*')

把一个例子放在一起来读取你的文件和存储字符串是你分配的指针和字符串的集合,你可以这样做:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXC 1024   /* if you need a constant, #define one (or more) */

/* allocate pointer for *strings, allocate/copy s to (*strings)[*nptr] */
void allocmem (char ***strings, size_t *nptrs, const char *s)
{
    size_t len = strlen (s);    /* determine length of s */
    /* reallocate *string to a temporary pointer, adding 1 pointer */
    void *tmp = realloc (*strings, (*nptrs + 1) * sizeof **strings);

    if (!tmp) { /* validate EVERY allocation/reallocation */
        perror ("realloc-allocptr-strings");
        exit (EXIT_FAILURE);
    }
    *strings = tmp;     /* assign newly allocated block of pointers to *strings */

    (*strings)[*nptrs] = malloc (len + 1);      /* allocate storage for s */
    if (!(*strings)[*nptrs]) {  /* ditto */
        perror ("malloc-(*strings)[*nptrs]");
        exit (EXIT_FAILURE);
    }
    memcpy ((*strings)[*nptrs], s, len + 1);    /* copy s to allocated storage */

    *nptrs += 1;    /* increment pointer count after successful allocation/copy */
}

int main (int argc, char **argv) {

    char buf[MAXC],         /* temp storage for each line read from file */
         **strings = NULL;  /* must initialize pointer NULL */
    size_t nptrs = 0;       /* number of pointers allocated */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (fgets (buf, MAXC, fp)) {         /* read each line into buf */
        buf[strcspn (buf, "\n")] = 0;       /* trim \n from end of buf */
        allocmem (&strings, &nptrs, buf);   /* add string to strings */
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);

    for (size_t i = 0; i < nptrs; i++) {    /* output all stored strings */
        printf ("strings[%zu] : %s\n", i, strings[i]);
        free (strings[i]);      /* free storage for string */
    }
    free (strings);             /* free pointers */
}

使用/输出示例

$ ./bin/3starstrings dat/test1-7.txt
strings[0] : first
strings[1] : second
strings[2] : third
strings[3] : fourth
strings[4] : fifth
strings[5] : sixth
strings[6] : seventh

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此 (2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/3starstrings dat/test1-7.txt
==23799== Memcheck, a memory error detector
==23799== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==23799== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==23799== Command: ./bin/3starstrings dat/test1-7.txt
==23799==
strings[0] : first
strings[1] : second
strings[2] : third
strings[3] : fourth
strings[4] : fifth
strings[5] : sixth
strings[6] : seventh
==23799==
==23799== HEAP SUMMARY:
==23799==     in use at exit: 0 bytes in 0 blocks
==23799==   total heap usage: 17 allocs, 17 frees, 5,942 bytes allocated
==23799==
==23799== All heap blocks were freed -- no leaks are possible
==23799==
==23799== For counts of detected and suppressed errors, rerun with: -v
==23799== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

如果您还有其他问题,请查看并告诉我(请记住,被称为 3 星程序员 并不是恭维 - 从您的函数返回一个指针而不是 :)

【讨论】:

  • 谢谢大卫,你在这里的解释真的让我明白了很多。我认为最大的收获是不要成为三星级程序员。结合您在此处提到的内容和@FearlessSniper 解决方案,可以更轻松地处理函数本地的双指针,然后将其分配给三指针以将输入文件的值返回给主函数。很好,彻底的答案。
  • 很高兴它有帮助。从函数中返回指针是避免成为 3 星程序员的正确方法——但是......要点应该是,对 C 的透彻理解将使您能够同样好地处理任何一种情况。在某些情况下,成为 3 星程序员是实现目标的唯一途径:) 祝您编码顺利!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-22
  • 2019-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-11
  • 2016-02-21
相关资源
最近更新 更多