簡體   English   中英

使用 Calloc 使用 C 初始化二維數組的元素

[英]Using Calloc to initialize elements of 2D array using C

我想知道如何將字符串存儲到字符串數組中。

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

在將單詞插入該元素之前,是否需要動態分配行數組的每個元素?

您可以自由使用calloc (或mallocrealloc )或strdup如果有)。 strdup所做的就是自動分配長度 + 1 個字符的過程,然后將給定的字符串復制到新的內存塊,然后分配給您的指針。 如果您沒有可用的strdup它會做與您自己做的完全相同的事情。

但是,請注意strdup分配,因此您必須像直接調用分配函數之一一樣驗證分配。 進一步注意,在失敗時,它會像任何分配函數一樣設置errno並返回NULL

在查看示例之前,您必須解決其他幾個錯誤來源。 您聲明buffline具有固定大小。 因此,當您向buff中添加字符或填充line指針時,您必須跟蹤索引並檢查可用於保護數組邊界的最大值。 如果您有一個包含1024-character行或包含2025字的行的文件,則寫入超過每個調用Undefined Behavior 的數組的末尾。

同樣重要的是您選擇的變量名稱。 line不是一行,它是一個數組或指向由您提供給strtok的分隔符分隔的標記words指針 包含“行”的唯一變量是buff 如果您要調用任何行,則應將buff更改為line並將line更改為word (或token )。 現在,您的文件可能確實包含輸入文件中由':'分隔的其他行(未提供),但沒有更多,我們將在示例中將line更改為word並將line_indexword_index 雖然您使用的word作為字指針是很好,讓我們縮短至wp避免與重命名沖突word指針數組每個字。 buff很好,您知道您正在緩沖其中的字符。

您的最后一個問題是在將buff傳遞給strtok之前,您還沒有終止buff 所有字符串函數都需要一個以字符結尾的字符串作為它們的參數。 未能提供 on 調用Undefined Behavior

初步的

不要在代碼中使用幻數 反而:

#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分配/復制

如上所述, strdup所做的就是為上面wp指向的word分配存儲空間(包括nul結尾字符的存儲空間),將該單詞復制到新分配的內存塊中,然后返回指向首地址的指針在該塊中,允許您將起始地址分配給您的指針。 這很方便,但是由於它分配了,您必須驗證,例如

                /* 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指向的字(或令牌)的length + 1 , (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 */
}

將它完全放在一個示例中,讓您可以根據是否將命令行定義-DUSESTRDUP作為編譯器字符串的一部分來選擇是使用strdup還是使用calloc ,您可以執行以下操作(注意:我使用fp而不是fileFILE*指針):

#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添加-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)

始終確認您已釋放所有分配的內存並且沒有內存錯誤。

仔細檢查一下,如果您還有其他問題,請告訴我。

為字符串分配新空間的最快、最易讀和最便攜的方法是使用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不必要地緩慢且非標准,因此它不可移植並且可能無法編譯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM