使用 malloc 的动态二维字符数组

Dynamic 2D char array using malloc

我有以下代码可以动态分配单个句子:

int  size=1;
char * text = (char*) malloc(size * sizeof(char));

size = (int)sizeof(text);

fgets(text, si, stdin);

//remove new line()

printf ("Sentence = <%s>\n", text);

我希望能够分配和存储以 '\n' 结尾的多行以进一步处理(格式),我不知道我将分配多少行或它们将持续多长时间。行的输入以 EOF 结束。它不必与 fgets 一起使用。 例如:

有什么想法吗?

这是一个经典问题,即如何处理动态分配和重新分配以存储未知数量的字符串。值得详细了解此过程,因为它将作为您读取未知数量值(无论它们是结构、浮点数、字符等)的任何其他情况的基础。

您可以使用许多不同类型的数据结构,列表、树等,但是基本方法(如您所说的 "2D 字符数组" ) 通过创建一个 pointer-to-pointer-to-type 的数组来处理(在这种情况下 typechar)然后分配 space for,填充数据,并在读取数据时将新内存块的起始地址分配给每个指针。 pointer-to-pointer-to-type 的简写形式就是 double-pointer(例如 char **array;,技术上指向字符的指针指向字符的指针*

为未知数量的行分配内存的通用且有效的方法是首先分配合理预期数量的指针(每个预期行 1 个)。这比调用 realloc 并为您阅读的每一行重新分配整个集合要高效得多。在这里,您只需保留一个读取行数的计数器,当您达到原始分配限制时,您只需重新分配两倍于您当前拥有的指针数。请注意,您可以自由添加您选择的任何增量金额。您可以每次简单地添加一个固定的数量,或者您可以使用原始数量的一些缩放倍数——这取决于您。 realloc 到两倍电流 只是标准方案之一。

最初分配指针时,作为重新分配的一部分,您可以通过将每个指针设置为 NULL 来获益。这对于原始分配来说很容易实现。只需使用 calloc 而不是 malloc。在重新分配时,它要求您设置分配给 NULL 的所有新指针。

为什么?这不是强制性的,但这样做可以让您在不知道行数的情况下迭代指针数组。这是如何运作的?例如,假设您初始化了 100 个指向 NULL 的指针,并为每个指针分配了多行。要遍历集合,您只需执行以下操作:

size_t i = 0;
while (array[i]) {
    ... do your stuff ...
}

只有你赋值的指针才会有值。所以循环只会在有值的指针上进行交互,在遇到第一个 NULL 指针时停止。 (第一个 NULL 只是作为您的 哨兵 值告诉您何时停止)。这也提供了将指向您的集合的指针传递给任何函数的能力,而无需传递包含的 lines/values 的数量。 (注意:没有理由不传递集合的大小,但在某些情况下这是一个好处)

下面的例子使用传统的方法迭代固定数量的行来打印这些行,然后释放分配的内存,但是在这两种情况下你没有理由不能简单地迭代有效指针完成同样的事情。

为您的线路分配存储空间时也是如此。如果使用 calloc 而不是 malloc,则将所有值初始化为 0 (nul)。由于初始化,所有字符串的存储都保证 nul-terminated。这同样适用于分配数字数组。通过将所有值初始化为 0,您可以防止任何意外尝试读取未初始化值(未定义行为)的可能性。虽然从数组中按顺序 fill/read 通常不是问题,但当使用随机存储和检索例程时,这可能是一个真正的问题。

分配内存时,您必须验证每次调用是否成功(例如 malloccallocrealloc,以及其他为您分配的函数调用,例如 strdup).这只是一个简单的检查,但需要养成每次都做的习惯,否则可能会尝试读写 from/to 未分配的内存。在下面的例子中,简单的函数被用作 callocrealloc 的包装器,提供必要的检查。虽然不需要使用类似的辅助函数,但它们有助于使代码的主体免于重复的内存检查等,这些会使代码更难阅读。

关于 realloc 的最后说明。 总是 使用一个临时变量来保存 realloc 的 return。为什么?成功时 realloc return 指向新分配的内存块的指针。失败时 returns NULL。如果您未能使用临时指针并且请求失败,您将无法访问(丢失地址)您之前存储的所有值。验证 realloc 成功后,只需将临时指针分配给原始指针即可。

下面的示例将从作为程序第一个参数给出的文件名中读取所有行(或默认为 stdin)。它使用 fgets 来读取每一行。每行都经过测试以确保成功读取该行中的所有字符。如果该行太长而无法放入提供的 space 中,则会给出一个简单的警告,并将其余部分读入以下存储行(您也可以 realloc 并在此处连接)。所有行都存储在 array 中。原来分配了MAXL(64)个指针,每行可以容纳MAXC(256)个字符。您可以更改以满足您的需要或将 MAXL 设置为 1 以强制从那里开始重新分配行。这些行只是简单地打印到终端,然后在程序退出之前释放所有内存。

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

#define MAXC 256    /* max chars per-line */
#define MAXL  64    /* initial num lines  */

void *xcalloc (size_t n, size_t s);
void *xrealloc_dp (void *ptr, size_t *n);

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

    char **array = NULL;
    char buf[MAXC] = {0};
    size_t i, idx = 0, maxl = MAXL;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    array = xcalloc (maxl, sizeof *array);    /* allocate maxl pointers */

    while (fgets (buf, MAXC, fp))  /* read all lines from fp into array */
    {
        size_t len = strlen (buf);

        /* validate complete line read */
        if (len + 1 == MAXC && buf[len - 1] != '\n')
            fprintf (stderr, "warning: line[%zu] exceeded '%d' chars.\n",
                    idx, MAXC);

        /* strip trailing '\r', '\n' */
        while (len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
            buf[--len] = 0;

        /* allocate & copy buf to array[idx], nul-terminate
         * note: this can all be done with array[idx++] = strdup (buf);
         */
        array[idx] = xcalloc (len + 1, sizeof **array);
        strncpy (array[idx], buf, len);
        array[idx++][len] = 0;

        /* realloc as required (note: maxl passed as pointer) */
        if (idx == maxl) array = xrealloc_dp (array, &maxl);
    }
    if (fp != stdin) fclose (fp);

    printf ("\n lines read from '%s'\n\n", argc > 1 ? argv[1] : "stdin");
    for (i = 0; i < idx; i++)
        printf ("   line[%3zu]  %s\n", i, array[i]);

    for (i = 0; i < idx; i++)
        free (array[i]);    /* free each line */
    free (array);           /* free pointers  */

    return 0;
}

/* simple calloc with error checking */
void *xcalloc (size_t n, size_t s)
{
    void *memptr = calloc (n, s);
    if (memptr == 0) {
        fprintf (stderr, "xcalloc() error: virtual memory exhausted.\n");
        exit (EXIT_FAILURE);
    }

    return memptr;
}

/*  realloc array of pointers ('memptr') to twice current
 *  number of pointer ('*nptrs'). Note: 'nptrs' is a pointer
 *  to the current number so that its updated value is preserved.
 *  no pointer size is required as it is known (simply the size
 *  of a pointer
 */
void *xrealloc_dp (void *ptr, size_t *n)
{
    void **p = ptr;
    void *tmp = realloc (p, 2 * *n * sizeof tmp);
    if (!tmp) {
        fprintf (stderr, "xrealloc_dp() error: virtual memory exhausted.\n");
        exit (EXIT_FAILURE);
    }
    p = tmp;
    memset (p + *n, 0, *n * sizeof tmp); /* set new pointers NULL */
    *n *= 2;

    return p;
}

编译

gcc -Wall -Wextra -O3 -o bin/fgets_lines_dyn fgets_lines_dyn.c

Use/Output

$ ./bin/fgets_lines_dyn dat/captnjack.txt

 lines read from 'dat/captnjack.txt'

   line[  0]  This is a tale
   line[  1]  Of Captain Jack Sparrow
   line[  2]  A Pirate So Brave
   line[  3]  On the Seven Seas.

内存Leak/Error检查

在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 个责任:(1) 始终保留指向内存块起始地址的指针,因此,(2) 它可以是不再需要时释放。您必须使用内存错误检查程序来确保您没有写入 beyond/outside 您分配的内存块并确认您已释放所有分配的内存。对于 Linux valgrind 是正常的选择。滥用内存块的微妙方法有很多,可能会导致真正的问题,没有理由不这样做。每个平台都有类似的内存检查器。它们都易于使用。只是运行你的程序通过它。

$ valgrind ./bin/fgets_lines_dyn dat/captnjack.txt
==22770== Memcheck, a memory error detector
==22770== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==22770== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==22770== Command: ./bin/fgets_lines_dyn dat/captnjack.txt
==22770==

 lines read from 'dat/captnjack.txt'

   line[  0]  This is a tale
   line[  1]  Of Captain Jack Sparrow
   line[  2]  A Pirate So Brave
   line[  3]  On the Seven Seas.
==22770==
==22770== HEAP SUMMARY:
==22770==     in use at exit: 0 bytes in 0 blocks
==22770==   total heap usage: 6 allocs, 6 frees, 1,156 bytes allocated
==22770==
==22770== All heap blocks were freed -- no leaks are possible
==22770==
==22770== For counts of detected and suppressed errors, rerun with: -v
==22770== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

只需查找所有堆块均已释放——不可能存在泄漏错误摘要:0 个上下文中的 0 个错误。如果两者都没有,请返回并找出原因。

这比预期的要长得多,但这是值得理解的。如果您还有其他问题,请告诉我。