动态分配大小未知的字符串

时间:2016-01-17 00:49:52

标签: c arrays string pointers

我必须从输入中获取具有已知数量的名称的names作为一个字符串,每个字符串用空格分隔,我必须为每个字符串获取名称的字符串数组动态分配内存,

    char** names;
    char ch;
    names = malloc(N*sizeof(char*); /*N is defined*/

    for(i=0; i<N; i++) {

现在我必须为每个字符串分配而不使用定义的数字:

    i=0, j=0;
    while ((ch=getchar) != '\n') {
         while (ch != ' ') {
              names[i][j++] = ch;
         }
         if (ch == ' ') {
              names[i][j] = '\0';
              i++}}
    if (ch == '\n')
         names[i][j] = '\0';

5 个答案:

答案 0 :(得分:5)

这是如何处理动态分配和重新分配以存储未知数量的字符串的经典问题。 (在保存到数组之前,将每个字符串分成单独的标记)是值得理解的,因为它将作为几乎任何其他情况下读取未知数量值的基础(无论是它们是结构,花车,人物等......)。

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

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

&#34; 一个合理预期的指针数量?&#34;这不是确切的数字。你只想对你所期望的令牌数进行有根据的猜测,并将其用作分配指针的初始数。如果你只想到100,你不会想要分配10,000个指针。那将是非常浪费的。重新分配将解决任何不足,因此只需粗略猜测即可。如果你真的不知道,那么分配一些合理的数字,比如说64128等等。你可以在代码的开头简单地将限制声明为常量,因此很容易调整。 e.g:

#declare MAXPTR 128

使用匿名 enum

完成同样的事情
enum { MAXPTR = 128 };

最初分配指针时,作为重新分配的一部分,您可以通过将每个指针设置为NULL来获益。这对于原始分配很容易实现。只需使用calloc代替malloc即可。在重新分配时,它要求您设置分配给NULL的所有新指针。它提供的好处是第一个NULL充当 sentinel ,表示您的有效指针停止的点。只要您确保至少有一个NULL被保留为哨兵,您就可以在不知道精确指针数量的情况下进行迭代。 e.g:

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

使用分配的内存后,您需要确保释放内存。在一段简单的代码中,内存在退出时被释放,养成了跟踪你分配的内存并在不再需要时释放内存的习惯。

对于此特定任务,您需要将一行未知数量的字符读入内存,然后将该字符串标记(分离)为标记。 getline将读取并分配足以容纳任何大小字符串的内存。您可以使用任何其他输入函数执行相同的操作,您只需自己编写重复检查和重新分配的代码。如果getline可用(它在每个现代编译器中),请使用它。然后,只需将输入分隔为令牌strtokstrsep。然后,您将要复制每个标记以将每个标记保留在其自己的内存块中,并将该位置分配给您的标记数组。以下是一个简短的例子。

示例中包含几个用于打开文件,分配和重新分配的辅助函数。他们所做的只是简单的错误检查,这有助于保持代码的主体清洁和可读。查看示例,如果您有任何问题,请告诉我。

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

#define MAXL  64    /* initial number of pointers  */

/* simple helper/error check functions */
FILE *xfopen (const char *fn, const char *mode);
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 *line = NULL;
    size_t i, idx = 0, maxl = MAXL, n = 0;
    ssize_t nchr = 0;
    FILE *fp = argc > 1 ? xfopen (argv[1], "r") : stdin;

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

    while ((nchr = getline (&line, &n, fp)) != -1)
    {
        while (nchr > 0 && (line[nchr-1] == '\r' || line[nchr-1] == '\n'))
            line[--nchr] = 0; /* strip carriage return or newline   */

        char *p = line;  /* pointer to use with strtok */
        for (p = strtok (line, " \n"); p; p = strtok (NULL, " \n")) {

            array[idx++] = strdup (p);  /* allocate & copy  */

            /* check limit reached  - reallocate */
            if (idx == maxl) array = xrealloc_dp (array, &maxl);
        }
    }
    free (line);  /* free memory allocated by getline */
    if (fp != stdin) fclose (fp);

    for (i = 0; i < idx; i++)  /* print all tokens */
        printf (" array[%2zu] : %s\n", i, array[i]);

    for (i = 0; i < idx; i++)  /* free all memory  */
        free (array[i]);
    free (array);

    return 0;
}

/* fopen with error checking */
FILE *xfopen (const char *fn, const char *mode)
{
    FILE *fp = fopen (fn, mode);

    if (!fp) {
        fprintf (stderr, "xfopen() error: file open failed '%s'.\n", fn);
        // return NULL;
        exit (EXIT_FAILURE);
    }

    return fp;
}

/* 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, "%s() error: virtual memory exhausted.\n", __func__);
        exit (EXIT_FAILURE);
    }
    p = tmp;
    memset (p + *n, 0, *n * sizeof tmp); /* set new pointers NULL */
    *n *= 2;

    return p;
}

输入文件

$ cat dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.

<强>输出

$ ./bin/getline_strtok <dat/captnjack.txt
 array[ 0] : This
 array[ 1] : is
 array[ 2] : a
 array[ 3] : tale
 array[ 4] : Of
 array[ 5] : Captain
 array[ 6] : Jack
 array[ 7] : Sparrow
 array[ 8] : A
 array[ 9] : Pirate
 array[10] : So
 array[11] : Brave
 array[12] : On
 array[13] : the
 array[14] : Seven
 array[15] : Seas.

内存/错误检查

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

$ valgrind ./bin/getline_strtok <dat/captnjack.txt
==26284== Memcheck, a memory error detector
==26284== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==26284== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==26284== Command: ./bin/getline_strtok
==26284==
 array[ 0] : This
 array[ 1] : is
<snip>
 array[14] : Seven
 array[15] : Seas.
==26284==
==26284== HEAP SUMMARY:
==26284==     in use at exit: 0 bytes in 0 blocks
==26284==   total heap usage: 18 allocs, 18 frees, 708 bytes allocated
==26284==
==26284== All heap blocks were freed -- no leaks are possible
==26284==
==26284== For counts of detected and suppressed errors, rerun with: -v
==26284== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

每次要确认的是&#34; 释放所有堆块 - 不可能泄漏&#34;和&#34; 错误摘要:来自0个上下文的0个错误&#34;。

答案 1 :(得分:3)

如何逐渐增加缓冲区,例如,当缓冲区变满时缓冲区的大小加倍?

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

char *read_string(void) {
    size_t allocated_size = 2;
    size_t read_size = 0;
    char *buf = malloc(allocated_size); /* allocate initial buffer */
    if (buf == NULL) return NULL;
    for(;;) {
        /* read next character */
        int input = getchar();
        if (input == EOF || isspace(input)) break;
        /* if there isn't enough buffer */
        if (read_size >= allocated_size - 1) {
            /* allocate new buffer */
            char *new_buf = malloc(allocated_size *= 2);
            if (new_buf == NULL) {
                /* failed to allocate */
                free(buf);
                return NULL;
            }
            /* copy data read to new buffer */
            memcpy(new_buf, buf, read_size);
            /* free old buffer */
            free(buf);
            /* assign new buffer */
            buf = new_buf;
        }
        buf[read_size++] = input;
    }
    buf[read_size] = '\0';
    return buf;
}

int main(void) {
    int N = 5;
    int i;

    char** names;
    names = malloc(N*sizeof(char*));
    if(names == NULL) return 1;
    for(i=0; i<N; i++) {
        names[i] = read_string();

    }
    for(i = 0; i < N; i++) {
        puts(names[i] ? names[i] : "NULL");
        free(names[i]);
    }
    free(names);
    return 0;
}

注意:他们说you shouldn't cast the result of malloc() in C

答案 2 :(得分:3)

对于已知数量的字符串,您已正确分配char **

char** names;
names = (char**) malloc(N*sizeof(char*));

注意,因为在C中不需要强制转换,所以你可以这样写:

names = malloc(N*sizeof(char*));

要在读取文件时分配内存,请使用以下方法:

  1. 使用已知起始大小的[m][c]alloc分配缓冲区(calloc更干净)
  2. 读入缓冲区,直到空间不足为止。
  3. 使用realloc将缓冲区的大小增加一些增量(加倍)
  4. 重复步骤1到3,直到读取文件
  5. 此外,当使用未知长度的缓冲区,并且您希望其内容预先设置或归零时,请考虑在malloc()上使用 calloc() 。这是一个更清洁的选择。

答案 3 :(得分:0)

当你说,

char** names;
char ch;
names = malloc(N*sizeof(char*));

您创建了一个名称变量,它是双指针,能够多次存储字符串的地址N次。

例如:如果你有32个字符串,则N为32。   所以,32 * sizeof(char *)   和sizeof char *是4个字节   因此,将分配128个字节

之后你就这样做了,

names[i][j++] = ch;

以上表达方式使用错误。 因为,您正在尝试将char数据分配给地址变量。

您需要为内存地址变量名称创建子内存。

或者您需要从主字符串中分配每个子字符串的地址。

答案 4 :(得分:0)

使用readline()getline()获取指向包含数据的内存分配的指针。

然后使用类似sscanf()strtok()的内容将各个名称字符串提取到数组成员中。