使用Calloc使用C初始化2D数组的元素

时间:2018-11-01 01:13:45

标签: c dynamic-memory-allocation calloc

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

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++;
       }
   }

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

2 个答案:

答案 0 :(得分:2)

您可以自由使用calloc(或mallocrealloc)或strdup(如果有)。 strdup所做的全部工作就是自动化分配 length + 1 个字符,然后将给定的字符串复制到新的内存块中,然后将其分配给指针的过程。如果没有strdup,它会执行与您自己完全相同的操作。

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

在查看示例之前,您必须解决其他几种错误源。您同时声明buffline的大小。因此,在向buff添加字符或在line中填充指针时,必须跟踪索引并检查可用的最大值以保护数组边界。如果您有一个包含1024-character行或2025个单词的行的文件,则会在每个数组的末尾写入 Undefined Behavior

选择变量名同样重要。 line不是一行,它是指向令牌words数组或指针,由您提供给{{1}的定界符分隔}。包含“行”的唯一变量是strtok。如果要呼叫任何线路,则应将buff更改为buff,并将line更改为line(或word)。现在,您的文件可能确实包含输入文件中未用token分隔的行(未提供),但是如果没有更多内容,我们将':'更改为line和{{在示例中从1}}到word。虽然您可以将line_index用作单词指针,但我们将其缩短为word_index,以避免与重命名的指向每个单词的word指针数组冲突。 wp很好,您知道您正在其中缓冲字符。

您的最后一个问题是,在将word传递到buff之前,您还没有未终止 buff。所有字符串函数都需要一个以 nul结尾的字符串作为其参数。无法提供调用会调用未定义行为

初步

请勿在代码中使用魔术数字。相反:

buff

注意:,您实际上应该检查strtok以避免尝试对空行进行标记化-这留给您。另外请注意,#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 > 1 && ch == '\n'))的两次调用)

使用for分配/复制

如上所述,所有strtok所做的工作就是为上述strdup所指向的单词分配存储空间(包括存储为 nul-终止字符的存储空间),然后复制字指向新分配的内存块,然后返回指向该块中第一个地址的指针,使您可以将起始地址分配给指针。这很方便,但是由于它分配,因此您必须验证,例如

strdup

使用wp做同样的事情

如果 /* NOTE: strdup allocates - you must validate */ if (!(word[word_index] = strdup (wp))) { perror ("strdup(wp)"); exit (EXIT_FAILURE); } word_index++; /* increment after allocation validated */ 不可用,或者您只是想手动分配和复制,那么您将做完全相同的事情。 (1)获取strlen + calloc + memcpy所指向的单词(或令牌)的长度,(2)分配strdup个字节; (3)将wp指向的字符串复制到新分配的内存块中。 (在分配时将新块的起始地址分配给指针)。

复制到新的内存块的效率高。由于您已经向前扫描了length + 1所指向的字符串以查找长度,因此无需使用wp再次扫描该字符串。您有长度,因此只需使用wp即可避免对字符串结尾进行第二次扫描(这很简单,但是可以理解代码中发生的事情)。使用strcpy可以做到:

memcpy
  

现在,如果我要整行地做,当我在   该文件如何重用char * line [2024]数组?

好吧,它现在被称为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); (我的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*进行分配,并且简单的gcc编译字符串为:

#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 */
}

对于VS(calloc),您将使用

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

(它将在Windows的当前目录中创建cl.exe

要使用cl /nologo /W3 /wd4996 /Ox /Festrtokstrdupcalloc /Tc strtokstrdupcalloc.c 进行编译,只需将strtokstrdupcalloc.exe添加到任一命令行中即可。

示例输入文件

strdup

使用/输出示例

-DUSESTRDUP

(无论分配如何,输出都是相同的)

内存使用/错误检查

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

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,$ 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.

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

仔细检查一下,如果还有其他问题,请告诉我。

答案 1 :(得分:0)

为字符串分配新空间的最快,最易读和最便携的方法是使用def foo(outside_queue, evt): outside_queue.put(os.getpid()) evt.set() class Object(object): def run(self, *args): outside_queue = args[0] items = dict() for i in range(5): evt = multiprocessing.Event() queue = multiprocessing.Queue() proc = multiprocessing.Process(target=foo, args=(queue, evt)) items[proc] = queue proc.start() evt.wait(None) ... + 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-“-。而且很危险,因为很容易错过null终止。
  • strncpy的速度很慢,因为它会将所有内存归零。
  • calloc不必要地缓慢并且也不是标准的,因此它是不可移植的并且可能无法编译。