簡體   English   中英

如何將內存分配應用於計算列表中單詞數量的C程序? (例如,malloc,calloc,免費)

[英]How to apply Memory Allocation to a C Program which counts the amount of words in a list? (eg. malloc, calloc, free)

考慮@David C. Rankin在之前的答案中提供的代碼:

如何只計算列表中以Capital開頭的單詞?

如何優化此代碼以包含更大文本文件的內存分配? 使用下面的代碼,它將完成小.txt文件。

但是,為此代碼設置內存分配的最佳方法是什么,以便C(編程語言)不會耗盡內存。 是否最好使用鏈接列表?

/**
 * C program to count occurrences of all words in a file.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#define MAX_WORD     50     /* max word size */
#define MAX_WORDS   512     /* max number of words */

#ifndef PATH_MAX
#define PATH_MAX   2048     /* max path (defined for Linux in limits.h) */
#endif

typedef struct {            /* use a struct to hold */
    char word[MAX_WORD];    /* lowercase word, and */
    int cap, count;         /* if it appeast capitalized, and its count */
} words_t;

char *strlwr (char *str)    /* no need for unsigned char */
{
    char *p = str;

    while (*p) {
        *p = tolower(*p);
        p++;
    }

    return str;
}

int main (void) {

    FILE *fptr;
    char path[PATH_MAX], word[MAX_WORD];
    size_t i, len, index = 0;

    /* Array of struct of distinct words, initialized all zero */
    words_t words[MAX_WORDS] = {{ .word = "" }};

    /* Input file path */
    printf ("Enter file path: ");
    if (scanf ("%s", path) != 1) {  /* validate every input */
        fputs ("error: invalid file path or cancellation.\n", stderr);
        return 1;
    }

    fptr = fopen (path, "r");   /* open file */
    if (fptr == NULL) {         /* validate file open */
        fputs ( "Unable to open file.\n"
                "Please check you have read privileges.\n", stderr);
        exit (EXIT_FAILURE);
    }

    while (index < MAX_WORDS &&                 /* protect array bounds  */
            fscanf (fptr, "%s", word) == 1) {   /* while valid word read */
        int iscap = 0, isunique = 1;    /* is captial, is unique flags */

        if (isupper (*word))            /* is the word uppercase */
            iscap = 1;

        /* remove all trailing punctuation characters */
        len = strlen (word);                    /* get length */
        while (len && ispunct(word[len - 1]))   /* only if len > 0 */
            word[--len] = 0;

        strlwr (word);                  /* convert word to lowercase */

        /* check if word exits in list of all distinct words */
        for (i = 0; i < index; i++) {
            if (strcmp(words[i].word, word) == 0) {
                isunique = 0;               /* set unique flag zero */
                if (iscap)                  /* if capital flag set */
                    words[i].cap = iscap;   /* set capital flag in struct */
                words[i].count++;           /* increment word count */
                break;                      /* bail - done */
            }
        }
        if (isunique) { /* if unique, add to array, increment index */
            memcpy (words[index].word, word, len + 1);  /* have len */
            if (iscap)                      /* if cap flag set */
                words[index].cap = iscap;   /* set capital flag in struct */
            words[index++].count++;         /* increment count & index */
        }
    }
    fclose (fptr);  /* close file */

    /*
     * Print occurrences of all words in file.
     */
    puts ("\nOccurrences of all distinct words with Cap in file:");
    for (i = 0; i < index; i++) {
        if (words[i].cap) {
            strcpy (word, words[i].word);
            *word = toupper (*word);
            /*
             * %-15s prints string in 15 character width.
             * - is used to print string left align inside
             * 15 character width space.
             */
            printf("%-15s %d\n", word, words[i].count);
        }
    }

    return 0;
}

示例使用/輸出

使用您發布的輸入

$ ./bin/unique_words_with_cap
Enter file path: dat/girljumped.txt

Occurrences of all distinct words with Cap in file:
Any             7
One             4
Some            10
The             6
A               13

但是,為此代碼設置內存分配的最佳方法是什么,以便C(編程語言)不會耗盡內存。

請注意,大多數計算機,甚至便宜的筆記本電腦,都有相當多的RAM。 實際上,您可以期望能夠分配至少1 GB的內存。 這對文本文件處理來說很重要!

一個大的人類書面文本文件是聖經。 根據經驗,該文本大約需要16兆字節(兩倍)。 對於大多數計算機來說,今天的內存非常少(我的AMD2970WX在其CPU緩存中的容量超過了它)。

是否最好使用鏈接列表?

實際考慮的是比內存消耗更多的算法時間復雜度 例如,在鏈表中搜索內容具有線性時間。 通過一百萬字的列表確實需要一些時間(即使計算機很快)。

您可能想要了解更多信息:

  • 靈活的數組成員 (在word_t使用它)。
  • 字符串復制例程,如strdupasprintf 即使你沒有它們,重新編程它們也是一件相當容易的事。

但是你仍然希望避免內存泄漏 ,更重要的是,還要避免未定義的行為

閱讀如何調試小程序 valgrindclang靜態分析器gdb調試器地址清理 等工具對學習和使用非常有用。

最后,請仔細閱讀,並完整地閱讀Norvig 在10年內自學編程 該文本是令人深思的,其附錄至少令人驚訝地接近你的問題。

PS。 我讓你猜測並估計你在一生中能夠閱讀的文本總量(以字節為單位)。 這個尺寸非常小,可能適用於今天的任何智能手機。 在今天的設備上,文字非常便宜。 照片和視頻不是。

NB。 “什么是最好的方式”問題的類型過於廣泛,在這里偏離主題,意見問題,並與P與NP問題相關。 賴斯的定理停止問題 這些問題通常沒有明確的答案,而且應該是無法解決的:通常很難證明在十幾年內無法想到更好的答案(即使對於某些此類問題,您今天可以獲得證據:例如,今天證明排序需要至少O(n log n)時間。)。

由於您已經使用結構的固定大小的數組來保存信息,從使用固定大小的陣列,其中存儲對您會自動預留堆棧上改變有一個答案,以動態地分配存儲在這里可以realloc根據需要,只需最初需要聲明一個指向類型的指針而不是類型數組,然后為每個結構分配存儲。

之前,使用512個元素的固定大小數組,您將擁有:

#define MAX_WORDS   512     /* max number of words */
...
    /* Array of struct of distinct words, initialized all zero */
    words_t words[MAX_WORDS] = {{ .word = "" }};

在動態分配時,只需聲明指向類型的指針並提供一些合理數量的元素的初始分配,例如

#define MAX_WORDS     8     /* initial number of struct to allocate */
...
    /* pointer to allocated block of max_words struct initialized zero */
    words_t *words = calloc (max_words, sizeof *words);

注意:您可以使用malloc, calloc or realloc分配,但只有calloc分配並且還將所有字節設置為零。在您的情況下,因為您希望.cap.count成員初始化為零, calloc是一個明智的選擇)

值得暫停以了解您是使用固定大小的數組還是已分配的內存塊,您通過指向第一個元素的指針訪問數據。 唯一真正的區別是編譯器使用固定數組為堆棧中的陣列保留存儲空間,並且您負責通過分配為其保留存儲空間。

對元素的訪問將完全相同,因為在訪問時,數組將轉換為指向第一個元素的指針。 請參閱: C11標准 - 6.3.2.1其他操作數 - 左值,數組和函數指示符(p3)通過指向第一個元素的指針訪問內存。 在動態分配時,您將第一個元素的地址分配給指針,而不是為陣列保留存儲器的編譯器。 無論是為您保留存儲的數組,還是聲明指針並為其分配已分配的內存塊 - 您訪問元素的方式都是相同的。 (暫停完成)

分配時,由您來驗證分配是否成功。 所以你會按照你的分配:

    if (!words) {   /* valdiate every allocation */
        perror ("calloc-words");
        exit (EXIT_FAILURE);
    }

您已經跟蹤index告訴您已經填充了多少個結構,您只需size_t max_words = MAX_WORDS;添加一個變量來跟蹤可用的結構數量size_t max_words = MAX_WORDS;將第二個變量設置為初始分配大小MAX_WORDS ) 。 所以,你的測試“我需要realloc嗎?” 只是在填充==可用時 ,或者在你的情況下if (index == max_words)

既然你現在有能力realloc ,你讀的循環不再具有保護您的數組邊界,你可以簡單地讀出每一個字的文件,例如,在

    while (fscanf (fptr, "%s", word) == 1) {  /* while valid word read */
        int iscap = 0, isunique = 1;    /* is captial, is unique flags */
        ...

現在剩下的就是在填充另一個元素之前index == max_words測試。 您可以在forif塊之前放置test和realloc for進行處理isunique ,這很好,或者您實際上可以將它放在if (isunique)塊中,因為從技術上講,除非您添加唯一的單詞,否則不需要realloc 它唯一的區別是一個轉折情況,其中index == max_words ,你在你的for循環之前調用realloc ,其中最后一個單詞不是唯一的,你可以調用realloc調用技術上不需要的東西(想一想) )。

為了防止一個realloc太多,在新元素填充之前立即放置測試和realloc ,例如

        if (isunique) { /* if unique, add to array, increment index */
            if (index == max_words) {       /* is realloc needed? */
                /* always use a temporary pointer with realloc */
                void *tmp = realloc (words, 2 * max_words * sizeof *words);
                if (!tmp) {
                    perror ("realloc-words");
                    break;  /* don't exit, original data still valid */
                }
                words = tmp;    /* assign reallocated block to words */
                /* (optional) set all new memory to zero */
                memset (words + max_words, 0, max_words * sizeof *words);
                max_words *= 2; /* update max_words to reflect new limit */
            }
            memcpy (words[index].word, word, len + 1);  /* have len */
            if (iscap)                      /* if cap flag set */
                words[index].cap = iscap;   /* set capital flag in struct */
            words[index++].count++;         /* increment count & index */
        }

現在讓我們仔細看看重新分配本身,例如

            if (index == max_words) {       /* is realloc needed? */
                /* always use a temporary pointer with realloc */
                void *tmp = realloc (words, 2 * max_words * sizeof *words);
                if (!tmp) { /* validate every allocation */
                    perror ("realloc-words");
                    break;  /* don't exit, original data still valid */
                }
                words = tmp;    /* assign reallocated block to words */
                /* (optional) set all new memory to zero */
                memset (words + max_words, 0, max_words * sizeof *words);
                max_words *= 2; /* update max_words to reflect new limit */
            }

realloc調用本身是void *tmp = realloc (words, 2 * max_words * sizeof *words); 為什么不只是words = realloc (words, 2 * max_words * sizeof *words); 答:您永遠不會重新 realloc指針本身,並始終使用臨時指針。 為什么? realloc分配新存儲,將現有數據復制到新存儲,然后在舊內存塊上調用free() 當(不是If) realloc失敗時,它返回NULL並且不觸及舊的內存塊。 如果你盲目地將NULL指定給你現有的指針words ,你剛剛用NULL覆蓋舊內存塊的地址,造成內存泄漏,因為你不再有對舊內存塊的引用而且它不能被釋放。 所以吸取了教訓, 總是用一個臨時指針重新realloc

如果realloc成功,那么呢? 密切注意線條:

                words = tmp;    /* assign reallocated block to words */
                /* (optional) set all new memory to zero */
                memset (words + max_words, 0, max_words * sizeof *words);
                max_words *= 2; /* update max_words to reflect new limit */

第一個簡單地為創建的新內存塊分配地址,並通過realloc將其填充到您的words指針中。 (`單詞現在指向一塊內存塊,其元素數量是以前的兩倍)。

第二行--remon, reallocmalloc不會將新內存初始化為零,如果你想初始化內存為零,(對於你的.cap.count成員真的很有用,你必須自己用memset做到這一點)那么什么需要設置為零?原始塊中沒有的所有內存。那是什么?好吧,它從words + max_words開始。我必須編寫多少個零?你必須填滿所有內存上面的words + max_words到塊的末尾。由於你的大小翻了一倍,你只需要將原始大小從零words + max_words開始words + max_words ,即max_words * sizeof *words的內存字節。(記得我們使用了2 * max_words * sizeof *words作為新大小,我們還沒有更新max_words ,所以它仍然保持原始大小)

最后,現在是時候更新max_words 這里只是讓它匹配你在realloc添加到你的分配中的任何內容。 我只是一倍當前分配每一次的大小realloc被調用,所以更新max_words簡單地乘以新的分配大小,你2max_words *= 2; 您可以根據需要添加少量或大量內存。 您可以按3/2.縮放3/2. ,你可以添加固定數量的元素(比如10 ),這完全取決於你,但是避免每次調用realloc來添加1個元素。 你可以這樣做,但分配和再分配都比較昂貴的操作,所以最好每次加合理大小的塊你realloc ,並加倍是內存增長和次數之間的合理平衡realloc被調用。

完全放在一起,你可以這樣做:

/**
 * C program to count occurrences of all words in a file.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#define MAX_WORD     50     /* max word size */
#define MAX_WORDS     8     /* initial number of struct to allocate */

#ifndef PATH_MAX
#define PATH_MAX   2048     /* max path (defined for Linux in limits.h) */
#endif

typedef struct {            /* use a struct to hold */
    char word[MAX_WORD];    /* lowercase word, and */
    int cap, count;         /* if it appeast capitalized, and its count */
} words_t;

char *strlwr (char *str)    /* no need for unsigned char */
{
    char *p = str;

    while (*p) {
        *p = tolower(*p);
        p++;
    }

    return str;
}

int main (void) {

    FILE *fptr;
    char path[PATH_MAX], word[MAX_WORD];
    size_t i, len, index = 0, max_words = MAX_WORDS;

    /* pointer to allocated block of max_words struct initialized zero */
    words_t *words = calloc (max_words, sizeof *words);
    if (!words) {   /* valdiate every allocation */
        perror ("calloc-words");
        exit (EXIT_FAILURE);
    }

    /* Input file path */
    printf ("Enter file path: ");
    if (scanf ("%s", path) != 1) {  /* validate every input */
        fputs ("error: invalid file path or cancellation.\n", stderr);
        return 1;
    }

    fptr = fopen (path, "r");   /* open file */
    if (fptr == NULL) {         /* validate file open */
        fputs ( "Unable to open file.\n"
                "Please check you have read privileges.\n", stderr);
        exit (EXIT_FAILURE);
    }

    while (fscanf (fptr, "%s", word) == 1) {  /* while valid word read */
        int iscap = 0, isunique = 1;    /* is captial, is unique flags */

        if (isupper (*word))            /* is the word uppercase */
            iscap = 1;

        /* remove all trailing punctuation characters */
        len = strlen (word);                    /* get length */
        while (len && ispunct(word[len - 1]))   /* only if len > 0 */
            word[--len] = 0;

        strlwr (word);                  /* convert word to lowercase */

        /* check if word exits in list of all distinct words */
        for (i = 0; i < index; i++) {
            if (strcmp(words[i].word, word) == 0) {
                isunique = 0;               /* set unique flag zero */
                if (iscap)                  /* if capital flag set */
                    words[i].cap = iscap;   /* set capital flag in struct */
                words[i].count++;           /* increment word count */
                break;                      /* bail - done */
            }
        }
        if (isunique) { /* if unique, add to array, increment index */
            if (index == max_words) {       /* is realloc needed? */
                /* always use a temporary pointer with realloc */
                void *tmp = realloc (words, 2 * max_words * sizeof *words);
                if (!tmp) { /* validate every allocation */
                    perror ("realloc-words");
                    break;  /* don't exit, original data still valid */
                }
                words = tmp;    /* assign reallocated block to words */
                /* (optional) set all new memory to zero */
                memset (words + max_words, 0, max_words * sizeof *words);
                max_words *= 2; /* update max_words to reflect new limit */
            }
            memcpy (words[index].word, word, len + 1);  /* have len */
            if (iscap)                      /* if cap flag set */
                words[index].cap = iscap;   /* set capital flag in struct */
            words[index++].count++;         /* increment count & index */
        }
    }
    fclose (fptr);  /* close file */

    /*
     * Print occurrences of all words in file.
     */
    puts ("\nOccurrences of all distinct words with Cap in file:");
    for (i = 0; i < index; i++) {
        if (words[i].cap) {
            strcpy (word, words[i].word);
            *word = toupper (*word);
            /*
             * %-15s prints string in 15 character width.
             * - is used to print string left align inside
             * 15 character width space.
             */
            printf("%-15s %d\n", word, words[i].count);
        }
    }
    free (words);

    return 0;
}

示例使用/輸出

您可以獲得樣本數據的位置:

$ ./bin/unique_words_with_cap_dyn
Enter file path: dat/girljumped.txt

Occurrences of all distinct words with Cap in file:
Any             7
One             4
Some            10
The             6
A               13

內存使用/錯誤檢查

在您編寫的任何動態分配內存的代碼中,您對分配的任何內存塊都有2個職責 :(1) 始終保留指向內存塊起始地址的指針,因此,(2)當它為no時可以釋放它需要更久。

您必須使用內存錯誤檢查程序,以確保您不會嘗試訪問內存或寫入超出/超出已分配塊的范圍,嘗試讀取或基於未初始化值的條件跳轉,最后,確認你釋放了你分配的所有內存。

對於Linux, valgrind是正常的選擇。 每個平台都有類似的記憶檢查器。 它們都很簡單易用,只需通過它運行程序即可。

$ valgrind ./bin/unique_words_with_cap_dyn
==7962== Memcheck, a memory error detector
==7962== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==7962== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==7962== Command: ./bin/unique_words_with_cap_dyn
==7962==
Enter file path: dat/girljumped.txt

Occurrences of all distinct words with Cap in file:
Any             7
One             4
Some            10
The             6
A               13
==7962==
==7962== HEAP SUMMARY:
==7962==     in use at exit: 0 bytes in 0 blocks
==7962==   total heap usage: 4 allocs, 4 frees, 3,912 bytes allocated
==7962==
==7962== All heap blocks were freed -- no leaks are possible
==7962==
==7962== For counts of detected and suppressed errors, rerun with: -v

上面你可以看到有4分配和4釋放(原始分配8realloc8, 16 & 32 ),你可以看到有0錯誤。

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

仔細看看,如果您有任何疑問,請告訴我。

暫無
暫無

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

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