Как применить Распределение Памяти к Программе на Си, которая считает количество слов в списке? (например, Malloc, Calloc, бесплатно) - PullRequest
0 голосов
/ 15 мая 2019

Учитывая код, предоставленный @David C. Rankin в этом предыдущем ответе:

Как считать только слова, начинающиеся с заглавной буквы в списке?

Как вы оптимизируете этот код, чтобы включить распределение памяти для гораздо больших текстовых файлов? С этим кодом ниже он будет завершен для небольших файлов .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

Ответы [ 2 ]

0 голосов
/ 15 мая 2019

Поскольку у вас уже есть ответ с использованием массива структуры фиксированного размера для хранения информации, переход от использования массива фиксированного размера, где хранилище автоматически резервируется для вас в стеке, к динамически распределенному хранилищу, где вы можете 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 Standard - 6.3.2.1 Другие операнды - L Значения, массивы и обозначения функций (p3) В любом случае доступ к памяти осуществляется через указатель на первый элемент. При динамическом размещении вы назначаете адрес первого элемента указателю, а не компилятору, резервирующему хранилище для массива. Будь то массив с зарезервированным для вас хранилищем, или вы объявляете указатель и назначаете ему выделенный блок памяти - способ доступа к элементам будет идентичным. (пауза сделана)

Когда вы распределяете, вы можете подтвердить , что распределение прошло успешно. Таким образом, вы должны следовать своему распределению:

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

Вы уже отслеживаете index, сообщая, сколько структур у вас есть заполнено , вам просто нужно добавить еще одну переменную, чтобы отследить, сколько структур доступно (size_t max_words = MAX_WORDS; дает вам 2-ую переменную, установленную на начальный размер выделения 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 тест до того, как заполнить другой элемент. Вы можете поместить тест и realloc перед блоками for и if для обработки isunique, что нормально, или вы можете фактически поместить его в блок if (isunique), поскольку технически, если вы не добавляете уникальный словом, realloc не потребуется. Единственное отличие, которое он делает, это угловой случай, когда index == max_words и вы звоните realloc перед циклом for, где последнее слово не является уникальным, вы можете сделать один вызов 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.(`words теперь указывает на блок памяти с вдвое большим количеством элементов, чем раньше).

Вторая строка - напомним, realloc и malloc не инициализируют новую память нулем, если вы хотите инициализировать ноль памяти (что для ваших .cap и .count членовдействительно полезно, вы должны сделать это самостоятельно с помощью memset. Итак, что нужно установить на ноль? Вся память, которой не было в вашем исходном блоке. Где это? Ну, это начинается с 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 доновый размер размещения, вы упрощаетеу умножить на 2 с max_words *= 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) он может быть освобожден , когда он больше не нужен.

Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированноми, наконец, подтвердить, что вы освобождаете всю выделенную память.

Для 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 освобождений (исходное выделение 8, realloc в8, 16 & 32) и вы видите, что были 0 ошибки.

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Просмотрите все идайте мне знать, если у вас есть какие-либо вопросы.

0 голосов
/ 15 мая 2019

Однако, каков наилучший способ установить выделение памяти для этого кода, чтобы C (язык программирования) не исчерпал память.

Обратите внимание, что большинство компьютеров, даже дешевых ноутбуков, имеют достаточно много оперативной памяти. На практике вы можете рассчитывать на выделение как минимум гигабайта памяти. Это много для обработки текстовых файлов!

Большой текстовый файл, написанный человеком, - это Библия. Как правило, этот текст занимает около 16 мегабайт (в два раза). Для большинства компьютеров сегодня это довольно небольшой объем памяти (у моего AMD2970WX больше, чем в кэш-памяти процессора ).

Лучше ли использовать связанные списки?

Практическое соображение более алгоритмическое сложность времени , чем потребление памяти. Например, поиск чего-либо в связанном списке имеет линейное время. А просмотр списка из миллиона слов займет некоторое время (даже если компьютеры работают быстро).

Возможно, вы захотите узнать больше о:

  • члены гибкого массива (используйте это вместо этого в word_t).
  • процедуры дублирования строк, такие как strdup или asprintf . Даже если у вас их нет, перепрограммирование их - довольно простая задача.

Но вы все равно хотите избежать утечек памяти , а также, что еще более важно, неопределенное поведение .

Чтение Как отлаживать небольшие программы . Такие инструменты, как valgrind , статический анализатор clang , отладчик gdb , очиститель адресов и т. Д., Очень полезны для учиться и использовать.

Наконец, внимательно и полностью прочитайте Norvig Научитесь программировать за 10 лет . Этот текст заставляет задуматься, и его приложение, по крайней мере, удивительно близко к вашим вопросам.

PS. Я оставляю вас угадывать и оценивать общий объем текста в байтах, который вы способны читать в течение всей своей жизни. Этот размер удивительно мал и, вероятно, подходит для любого смартфона сегодня. На современных устройствах текст действительно дешев. Фото и видео нет.

NB. Типы вопросов «как лучше» слишком широки, здесь не по теме, вопрос мнения и относятся к вопросу P против NP . Теорема Райса и проблема остановки . На эти вопросы обычно нет четкого ответа, и предполагается, что они неразрешимы: зачастую трудно доказать, что лучшего ответа невозможно было бы найти за дюжину лет (даже если для некоторых таких вопросов вы сегодня можно получить подтверждение: например, сегодня доказано, что сортировка требует не менее O (n log n) времени.).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...