使用malloc动态二维字符数组 [英] Dynamic 2D char array using malloc

查看:157
本文介绍了使用malloc动态二维字符数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下的code,它可以动态分配一个sentece:

I have the following code that can dynamically allocate a single 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);

我希望能够分配和存储多个行的'\\ n'结尾进一步与(格式)的工作,我不知道我有多少行会被分配要不了多久,他们将。线的输入与EOF结束。它并不必须是用于fgets。
例如:

I would like to be able to allocate and store multiple lines ending in '\n' to further work with (format), I don't know how many lines I will be allocating or how long they will be. The input of lines ends with EOF. It doesn't have to be with fgets. For example:


  • SENTENCE1 \\ n

  • SENTENCE2 \\ n
    ....等

任何想法?

推荐答案

这是我怎么处理动态分配和再​​分配存储一个未知的数字字符串中的经典问题。这是值得理解这一过程进行了详细,因为它会作为几乎任何地方,你正在阅读的数目不详的价值的其他情形的基础上(无论是结构,花车,人物等)。

This is the classic question of how do I handle dynamic allocation and reallocation to store an unknown number of strings. It is worth understanding this process in detail as it will serve as the basis for just about any any other circumstance where you are reading an unknown number of values (whether they are structs, floats, characters, etc...).

有许多不同类型的数据结构,你可以聘请,列表,树等,但基本方式(如你所说的二维字符数组的)是通过创建处理数组的指针到指针到类型的(用的键入的是字符在这种情况下),然后分配空间,填充数据,并作为数据被读出分配的起始地址的存储器的每个指针的新的块。短手的指针到指针到类型的仅仅是双指针的(例如的char **数组; ,这在技术上是一个的指针到指针到字符指针到字符* 的,如果你喜欢)

There are a number of different types of data structures you can employ, lists, trees, etc., but the basic way (as you call it "2D char array") is handled by creating an array of pointer-to-pointer-to-type (with type being char in this case) and then allocating space for, filling with data, and assigning the starting address for the new block of memory to each pointer as your data is read. The short-hand for pointer-to-pointer-to-type is simply double-pointer (e.g. char **array;, which is technically a pointer-to-pointer-to-char or pointer-to-char* if you like)

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

The general, and efficient, approach to allocating memory for an unknown number of lines is to first allocate a reasonably anticipated number of pointers (1 for each anticipated line). This is much more efficient than calling realloc and reallocating the entire collection for every line you read. Here, you simply keep a counter of the number of lines read, and when you reach your original allocation limit, you simmply reallocate twice the number of pointers you currenly have. Note, you are free to add any incremental amount you choose. You can simply add a fixed amount each time, or you can use some scaled multiple of the original -- it's up to you. The realloc to twice current is just one of the standard schemes.

当最初分配的指针,作为你的重新分配过程中,你可以通过设置每个指针 NULL 受益。这是很容易实现的原始分配。只需使用释放calloc 而不是的malloc 。在再分配,它要求你设置分配到 NULL 所有的新指针。

When allocating your pointers originally, and as part of your reallocation, you can benefit by setting each pointer to NULL. This is easily accomplished for the original allocation. Simply use calloc instead of malloc. On reallocation, it requires that you set all new pointers allocated to NULL.

为什么呢?它不是强制性的,但这样做,您可以将指针数组遍历不知道的行数。这是如何工作的?例如,假设你初始化100指针 NULL 键,当您去分配了多条线路的每一个指针。要遍历集合你可以简单地做:

Why? It isn't mandatory, but doing so allows you to iterate over your array of pointers without knowing the number of lines. How does that work? Example, suppose you initialized 100 pointers to NULL and have assigned a number of lines to each pointer as you go. To iterate over the collection you can simply do:

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

只有指针已分配的东西都会有一个值。所以循环只会interate了具有价值的指针,遇到第一个 NULL 指针时停止。 (第一 NULL 只是作为您的定点的价值,告诉你什么时候停止)。这也提供了一个指针传递到您的收藏任何功能,无需通过还行数的能力/值遏制。 (注:的没有理由不通过集合的大小,但有些情况下,这是一个利益)

Only pointers you have assigned something to will have a value. So the loop will only interate over the pointers that have a value, stopping when the first NULL pointer is encountered. (the first NULL simply serves as your sentinel value telling you when to stop). This also provides the ability to pass a pointer to your collection to any function without also passing the number of lines/values contained. (note: there is no reason not to pass the size of the collection, but there are circumstances where this is a benefit)

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

The example below uses the traditional approach of iterating over a fixed number of lines to print the lines, and then free the memory allocated, but there is no reason you couldn't simply iterate over the valid pointers in both cases to accomplish the same thing.

您的线路分配存储时也同样适用。如果你使用释放calloc 而不是的malloc ,你初始化所有值 0 NUL )。那么您的所有字符串存储是保证凭借初始化为空终止的。这同样适用于对分配数字数组为好。通过初始化所有值 0 您prevent意外的可能性从未遂未初始化值(未定义行为)读取。虽然一般不会当你按顺序填写一个问题/从一个数组,使用随机存储和检索程序读取时,这可能是一个真正的问题。

The same holds true when allocating storage for your lines as well. If you use calloc instead of malloc, you initialize all values to 0 (nul). The storage for all your strings is then guaranteed to be nul-terminated by virtue of the initialization. The same applies to allocating for numeric arrays as well. By initializing all values to 0 you prevent any possibility of an accidental attempted read from an uninitialized value (undefined behavior). While generally not a problem when you sequentially fill/read from an array, when randomized storage and retrieval routines are used, this can be a real problem.

当分配的内存,您必须验证每次调用成功(如的malloc 释放calloc 的realloc ,并为分配你像其他函数调用的strdup )。这只是一个简单的检查,但它是在做每一次或风险的习惯让试图读取和写入从/到未分配的内存。在下面的例子中,简单的功能被用作释放calloc的包装的realloc 提供必要的检查。虽然没有要求使用类似的辅助函数,它们有助于保持你的code的主体从重复内存检查等免费的,可以令code更难看了。

When allocating memory, you must validate that each call succeeded (e.g. for malloc, calloc, realloc, and for other function calls that allocate for you like strdup). It is just a simple check, but it is something to get in the habit of doing every time or risk attempted reads and writes from/to unallocated memory. In the example below, simple functions are used as a wrapper for calloc and realloc that provide the necessary checks. While there is no requirement to use similar helper-functions, they help keep the main body of your code free from repetitive memory checks, etc. that can make the code more difficult to read.

的realloc 最后需要说明的。 总是的使用临时变量来保存的realloc 的回报。为什么?在成功的realloc 返回一个指向内存中的新分配的块。如果失败,它将返回 NULL 。如果你没有使用临时指针和请求失败,你已经失去了获得(丢失的地址)所有的previously存储的值。一旦你验证的realloc 成功了,只是临时的指针分配给原来的。

A final note on realloc. Always use a temporary variable to hold the return of realloc. Why? On success realloc returns a pointer to the newly allocated block of memory. On failure it returns NULL. If you fail to use a temporary pointer and the request fails, you have lost access to (lost the address of) all of your previously stored values. Once you validate realloc succeeded, simply assign your temporary pointer to the original.

下面的例子将读取给出的第一个参数的程序(或标准输入默认情况下)的文件名的所有行。它采用与fgets 阅读每一行。每一行进行测试,以确保在该行的所有字符被成功读取。如果线路太长,以适应所提供的空间,一个简单的发出警告,其余读入存储以下行(您可以的realloc 并连接在这里以及)。所有的线都存储在阵列。有 MAXL (64)指针最初分配,每行可容纳 MAXC (256)字符。你可以改变要么满足您的需要或设置 MAXL 1 来强制线路的重新分配,开始在那里。该行只是简单的打印到终端,然后将所有内存被释放在程序退出之前。

The example below will read all lines from the filename given as the first argument to the program (or stdin by default). It uses fgets to read each line. Each line is tested to insure that all characters in the line were successfully read. If the line was too long to fit into the space provided, a simple warning is given, and the remainder is read into the following line of storage (you can realloc and concatenate here as well). All lines are stored in array. There are MAXL (64) pointers originally allocated and each line can hold MAXC (256) characters. You can change either to meet your needs or set MAXL to 1 to force the reallocation of lines to begin there. The lines are simply printed to the terminal, and then all memory is freed before the program exits.

#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.

内存泄漏/错误检查

在任何code你写,可动态分配的内存,您有任何关于分配的任何内存块2 responsibilites:(1)总是preserves指针的起始地址的内存块的话,(2 ),它可以当不再需要它被释放。这是你必须使用一个内存错误检查程序,以确保你没有写超出/你分配的内存块之外,以确认您已释放所有已分配的内存。对于Linux 的valgrind 是正常的选择。有这么多的微妙的方式滥用的内存块,可能会导致真正的问题,没有任何理由不这样做。有每一个类似的平台内存检查。他们都简单易用。只是通过它运行程序。

In any code your write that dynamically allocates memory, you have 2 responsibilites regarding any block of memory allocated: (1) always preserves a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory and to confirm that you have freed all the memory you have allocated. For Linux valgrind is the normal choice. There are so many subtle ways to misuse a block of memory that can cause real problems, there is no excuse not to do it. There are similar memory checkers for every platform. They are all simple to use. Just run your program through it.

$ 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上下文错误的。如果你不兼得,回去找出原因。

Simply look for All heap blocks were freed -- no leaks are possible and ERROR SUMMARY: 0 errors from 0 contexts. If you don't have both, go back and figure out why.

这结束了​​比预期的时间要长得多,但是这是值得理解。让我知道是否有其他任何你有问题。

This ended up much longer than anticipated, but this is something worth understanding. Let me know if there is anything else you have questions on.

这篇关于使用malloc动态二维字符数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆