使用malloc

时间:2015-12-12 21:45:21

标签: c arrays multidimensional-array fgets getline

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

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);

我希望能够分配和存储以&#39; \ n&#39;结尾的多行。为了进一步使用(格式),我不知道我将分配多少行或者它们将会持续多长时间。行的输入以EOF结束。它并不一定与fgets有关。 例如:

  • SENTENCE1 \ n
  • SENTENCE2 \ n ....等

有什么想法吗?

1 个答案:

答案 0 :(得分:3)

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

您可以使用多种不同类型的数据结构,列表,树等,但基本方式(您称之为&#34; 2D char数组&#34; )通过创建一个指向指针到类型的指针(在这种情况下 type char)然后为其分配空间来处理,填充数据,并在读取数据时为每个指针分配新内存块的起始地址。 指针指向类型的简写只是双指针(例如char **array;,技术上是指针 - to-pointer-to-char 指向char * 如果你愿意的话)

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

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

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

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

只有指定了某些内容的指针才会有值。因此循环只会对具有值的指针进行交互,在遇到第一个NULL指针时停止。 (第一个NULL只是作为 sentinel 值告诉您何时停止)。这还提供了将指向集合的指针传递给任何函数的功能,而无需传递包含的行数/值。 (注意:没有理由不传递集合的大小,但有些情况下这是一个好处)

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

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

分配内存时,您必须验证每个调用是否成功(例如malloccallocrealloc,以及为您分配的其他函数调用strdup )。这只是一个简单的检查,但它习惯于每次都做或者尝试从未分配的内存中读取和写入的风险。在下面的示例中,简单函数用作callocrealloc的包装,提供必要的检查。虽然不需要使用类似的辅助函数,但它们有助于保持代码主体不受重复内存检查等的影响,从而使代码更难以阅读。

关于realloc的最后说明。 始终使用临时变量来保存realloc的返回值。为什么?成功realloc返回指向新分配的内存块的指针。失败时返回NULL。如果您未能使用临时指针并且请求失败,则您将失去对以前存储的所有值的访问权限(丢失地址)。验证realloc成功后,只需将临时指针指定给原始。

下面的示例将读取作为程序的第一个参数(或默认为stdin)给出的文件名中的所有行。它使用fgets来读取每一行。测试每一行以确保成功读取该行中的所有字符。如果该行太长而无法放入所提供的空间,则会给出一个简单的警告,其余部分将被读入以下存储行(您可以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

使用/输出

$ ./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.

内存泄漏/错误检查

在你的动态分配内存的任何代码中,你有2个责任关于任何分配的内存块:(1)总是保留一个指向内存块起始地址的指针,所以,(2)它可以在释放时释放它不再需要了。您必须使用内存错误检查程序,以确保您没有在已分配的内存块之外/之外写入,并确认已释放已分配的所有内存。对于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个错误。如果你没有两者兼顾,那就回去找出原因。

这比预期的要长得多,但这是值得了解的事情。如果您有任何其他问题,请告诉我。