【问题标题】:Using Calloc to initialize elements of 2D array using C使用 Calloc 使用 C 初始化二维数组的元素
【发布时间】:2019-04-05 05:53:24
【问题描述】:

我想知道如何将字符串存储到字符串数组中。

char buff[1024]; // to get chars from fgetc
char *line[2024]; // to store strings found in buff
int ch;
int i = 0;
while ((ch = fgetc(file)) != EOF) {
    buff[i] = ch;
    if (ch == '\n') { // find the end of the current line
       int line_index = 0;
       char *word;
       word = strtok(buff, ":"); // split the buffer into strings
       line[line_index] = word;
       line_index++;
       while (word != NULL) {
           word = strtok(NULL, ":");
           line[line_index] = word;
           line_index++;
       }
   }

在将单词插入元素之前,是否需要动态分配线数组的每个元素?

【问题讨论】:

  • 我会选择strdup。是的,你需要,在每行读取的单词上都指向 buff。
  • 将:line[line_index] = word; 更改为 line[line_index] = strdup(word);(在两个地方)。否则,第二行将丢弃位于第一行的单词值(依此类推......)。
  • 您可能还会发现while (1) { char *cp = fgets(buff,sizeof(buff),file); if (cp == NULL) break; cp = strchr(buff,'\n'); if (cp != NULL) *cp = 0; // do strtok stuff ... } 比使用fgetc 更容易。 YMMV
  • 谢谢你的帮助。现在,如果我要对整行执行此操作,当我在文件中找到新行时,如何重用我的 char *line[2024] 数组?
  • 您分配了line_index 字。要重用这些相同的指针,请确保您 free 内存 - 或者您的下一次分配和分配 strdup 将覆盖每个填充的 line[line_index] 中保存的地址,从而导致内存泄漏。所以for (int i = 0; i < line_index; i++) { free (line[i]); line[i] = NULL; } 在再次循环下一行之前。

标签: c dynamic-memory-allocation calloc


【解决方案1】:

您可以随意使用calloc(或malloc 或realloc)或strdup(如果有)。 strdup 所做的只是自动分配 length + 1 个字符的过程,然后将给定的字符串复制到新的内存块,然后将其分配给指针。如果您没有可用的strdup,它会做与您自己做的完全相同的事情。

但是,注意,strdup 分配,因此您必须验证分配,就像您直接调用了其中一个分配函数。进一步注意,失败时,它设置errno 并返回NULL,就像任何分配函数一样。

在查看示例之前,您必须解决其他几个错误来源。您将buff 和line 都声明为固定大小。因此,当您向buff 添加字符或在line 中填充指针时,您必须跟踪索引并检查可用于保护数组边界的最大值。如果您有一个包含1024-character 行或包含2025 字的行的文件,则您在每个调用未定义行为的数组的末尾写入。

同样重要的是变量名的选择。 line 不是一行,它是一个 数组或指针,指向 tokens 或 words,由您提供给 strtok 的分隔符分隔。唯一包含“行”的变量是buff。如果您要拨打任何线路,您应该将buff 更改为line 并将line 更改为word(或token)。现在,您的文件可能确实包含与输入文件(未提供)中由 ':' 分隔的其他内容的行,但没有更多内容,我们将在这个例子。虽然您使用word 作为字指针很好,但让我们将其缩短为wp 以避免与重命名的word 指向每个字的指针数组冲突。 buff 很好,你知道你在里面缓冲字符。

您的最后一个问题是在将buff 传递给strtok 之前,您还没有nul 终止 buff。所有字符串函数都需要一个 nul-terminated 字符串作为它们的参数。未能提供 on 调用未定义行为。

初步

不要在代码中使用幻数。而是:

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

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

    char buff[MAXC] = "",           /* line buffer */
        *word[MAXC * 2] = {NULL};   /* array of pointers to char */
    int ch,
        i = 0;
    ...
    while ((ch = fgetc(fp)) != EOF) {   /* read each char until EOF */
        int word_index = 0;
        buff[i++] = ch;
        if (i == MAXC - 1 || ch == '\n') {      /* protect array bounds */
            char *wp;       /* word pointer */
            buff[i++] = 0;  /* nul-termiante buf */
            for (wp = strtok (buff, " :\n");    /* initial call to strtok */
                word_index < MAXC && wp;        /* check bounds & wp */
                wp = strtok (NULL, " :\n")) {   /* increment - next token */

(注意:您实际上应该检查 if (i == MAXC - 1 || (i &gt; 1 &amp;&amp; ch == '\n')) 以避免尝试标记空行 - 这留给您。还要注意 for 循环提供了一种方便的方法来涵盖两者在单个表达式中调用strtok)

使用strdup 分配/复制

如上所述,strdup 所做的只是为上面wp 指向的单词分配存储空间(包括 nul-terminating 字符的存储空间),将该单词复制到新的分配的内存块,然后返回指向该块中第一个地址的指针,允许您将起始地址分配给指针。挺方便的,但是既然是分配,就必须validate,例如

                /* NOTE: strdup allocates - you must validate */
                if (!(word[word_index] = strdup (wp))) {
                    perror ("strdup(wp)");
                    exit (EXIT_FAILURE);
                }
                word_index++;   /* increment after allocation validated */

使用strlen + calloc + memcpy 做同样的事情

如果strdup 不可用,或者您只是想手动分配和复制,那么您只需执行完全相同的操作。 (1)获取wp所指向的字(或token)的长度,(2)分配length + 1字节; (3) 将wp 指向的字符串复制到新分配的内存块中。 (将新块的起始地址分配给您的指针发生在分配点)。

关于复制到新内存块的效率。由于您已经在wp 指向的字符串中向前扫描以找到长度,因此无需使用strcpy 再次扫描该字符串。你有长度,所以只需使用memcpy 来避免第二次扫描字符串结尾(这很简单,但显示了对代码中发生的事情的理解)。使用calloc 你会这样做:

                /* using strlen + calloc + memcpy */
                size_t len = strlen (wp);     /* get wp length */
                /* allocate/validate */
                if (!(word[word_index] = calloc (1, len + 1))) {
                    perror ("calloc(1,len+1)");
                    exit (EXIT_FAILURE);
                }   /* you alread scanned for '\0', use memcpy */
                memcpy (word[word_index++], wp, len + 1);

现在,如果我要对整行执行此操作,当我在 该文件如何重用我的 char *line[2024] 数组?

好吧,它现在叫做word,但正如评论中提到的,你已经跟踪了用你的line_index(我的word_index)变量填充的指针数量,所以在你可以分配一个新块之前内存并为您的指针分配一个新地址(从而覆盖指针持有的旧地址),您必须 free 指针当前持有的地址处的内存块(否则您将失去释放该地址的能力内存导致内存泄漏)。在释放内存后将指针设置为NULL 很好(但可选)。

(这样做可以确保只有有效的指针保留在您的指针数组中,从而允许您迭代数组,例如while (line[x] != NULL) { /* do something */ x++; } - 在传递或返回指向该数组的指针时很有用)

要重用空闲内存,重置指针以供重用并重置字符索引i = 0,您可以在输出行中的单词时执行以下操作,例如

            }
            for (int n = 0; n < word_index; n++) {  /* loop over each word */
                printf ("word[%2d]: %s\n", n, word[n]); /* output */
                free (word[n]);     /* free memory */
                word[n] = NULL;     /* set pointer NULL (optional) */
            }
            putchar ('\n');     /* tidy up with newline */
            i = 0;              /* reset i zero */
        }
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */
}

把它放在一个示例中,让您选择是使用 strdup 还是使用 calloc,具体取决于您是否将命令行定义 -DUSESTRDUP 作为编译器字符串的一部分传递,您可以执行以下操作(注意:我使用fp 而不是file 作为FILE* 指针):

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

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

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

    char buff[MAXC] = "",           /* line buffer */
        *word[MAXC * 2] = {NULL};   /* array of pointers to char */
    int ch,
        i = 0;
    /* 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 ((ch = fgetc(fp)) != EOF) {   /* read each char until EOF */
        int word_index = 0;
        buff[i++] = ch;
        if (i == MAXC - 1 || ch == '\n') {      /* protect array bounds */
            char *wp;       /* word pointer */
            buff[i++] = 0;  /* nul-termiante buf */
            for (wp = strtok (buff, " :\n");    /* initial call to strtok */
                word_index < MAXC && wp;        /* check bounds & wp */
                wp = strtok (NULL, " :\n")) {   /* increment - next token */
#ifdef USESTRDUP
                /* NOTE: strdup allocates - you must validate */
                if (!(word[word_index] = strdup (wp))) {
                    perror ("strdup(wp)");
                    exit (EXIT_FAILURE);
                }
                word_index++;   /* increment after allocation validated */
#else
                /* using strlen + calloc + memcpy */
                size_t len = strlen (wp);     /* get wp length */
                /* allocate/validate */
                if (!(word[word_index] = calloc (1, len + 1))) {
                    perror ("calloc(1,len+1)");
                    exit (EXIT_FAILURE);
                }   /* you alread scanned for '\0', use memcpy */
                memcpy (word[word_index++], wp, len + 1);
#endif 
            }
            for (int n = 0; n < word_index; n++) {  /* loop over each word */
                printf ("word[%2d]: %s\n", n, word[n]); /* output */
                free (word[n]);     /* free memory */
                word[n] = NULL;     /* set pointer NULL (optional) */
            }
            putchar ('\n');     /* tidy up with newline */
            i = 0;              /* reset i zero */
        }
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */
}

编译

默认情况下,代码将使用calloc 进行分配,一个简单的 gcc 编译字符串为:

gcc -Wall -Wextra -pedantic -std=c11 -O3 -o strtokstrdupcalloc strtokstrdupcalloc.c

对于 VS (cl.exe),你会使用

cl /nologo /W3 /wd4996 /Ox /Festrtokstrdupcalloc /Tc strtokstrdupcalloc.c

(会在windows当前目录下创建strtokstrdupcalloc.exe)

要使用strdup 进行编译,只需将-DUSESTRDUP 添加到任一命令行即可。

输入文件示例

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

使用/输出示例

$ ./bin/strtokstrdupcalloc dat/captnjack.txt
word[ 0]: This
word[ 1]: is
word[ 2]: a
word[ 3]: tale

word[ 0]: Of
word[ 1]: Captain
word[ 2]: Jack
word[ 3]: Sparrow

word[ 0]: A
word[ 1]: Pirate
word[ 2]: So
word[ 3]: Brave

word[ 0]: On
word[ 1]: the
word[ 2]: Seven
word[ 3]: Seas.

(无论你如何分配,输出都是一样的)

内存使用/错误检查

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

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

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

$ valgrind ./bin/strtokstrdupcalloc dat/captnjack.txt
==4946== Memcheck, a memory error detector
==4946== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4946== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==4946== Command: ./bin/strtokstrdupcalloc dat/captnjack.txt
==4946==
word[ 0]: This
word[ 1]: is
word[ 2]: a
word[ 3]: tale

word[ 0]: Of
word[ 1]: Captain
word[ 2]: Jack
word[ 3]: Sparrow

word[ 0]: A
word[ 1]: Pirate
word[ 2]: So
word[ 3]: Brave

word[ 0]: On
word[ 1]: the
word[ 2]: Seven
word[ 3]: Seas.

==4946==
==4946== HEAP SUMMARY:
==4946==     in use at exit: 0 bytes in 0 blocks
==4946==   total heap usage: 17 allocs, 17 frees, 628 bytes allocated
==4946==
==4946== All heap blocks were freed -- no leaks are possible
==4946==
==4946== For counts of detected and suppressed errors, rerun with: -v
==4946== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

  • strdup 不是标准的,所以应该避免。
【解决方案2】:

为字符串分配新空间的最快、最易读和最便携的方法是使用malloc+memcpy:

size_t size = strlen(old_str) + 1;
...
char* new_str = malloc (size);
if(new_str == NULL)
{ /* error handling */ }

memcpy(new_str, old_str, size);

当您事先知道长度时,没有反对使用此方法的论据。

关于劣质方法的说明:

  • 当您已经知道长度时,strcpy 是不必要的慢。
  • strncpy -"-。而且很危险,因为很容易错过空终止。
  • calloc 是不必要的慢,因为它会将所有内存归零。
  • strdup 速度太慢而且不标准,所以它是不可移植的,可能无法编译。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多