繁体   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