Использование Calloc для инициализации элементов 2D-массива с использованием C - PullRequest
0 голосов
/ 01 ноября 2018

Мне интересно, как хранить строки в массиве строк.

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 голосов
/ 01 ноября 2018

Самый быстрый, самый читаемый и самый удобный способ выделить новое место для строки - это использовать 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 неоправданно медленный, а также нестандартный, поэтому он непереносим и может не компилироваться.
0 голосов
/ 01 ноября 2018

Вы можете использовать либо calloc (или malloc или realloc), либо strdup, если он у вас есть. Все, что делает strdup, - это автоматизирует процесс выделения длины + 1 символов и затем копирует данную строку в новый блок памяти, который вы затем назначаете указателю. Он делает то же самое, что и вы, если бы у вас не было strdup.

Однако note , strdup выделяет, поэтому вы должны подтвердить распределение так же, как если бы вы его вызвали функций распределения напрямую. Также обратите внимание, что при сбое он устанавливает errno и возвращает NULL, как и любая из функций распределения.

Перед просмотром примеров у вас есть несколько других источников ошибок, которые вы должны устранить. Вы объявляете buff и line с фиксированным размером. Поэтому, когда вы добавляете символы в buff или заполняете указатели в line, вы должны отслеживать индекс и проверять максимальный доступный уровень для защиты границ вашего массива. Если у вас есть файл с 1024-character строками или строками с 2025 словами, вы пишете после конца каждого массива, вызывая неопределенное поведение .

Не менее важным является выбор имен переменных. line - это не строка, это массив или указатели от до токенов или words, разделенных разделителями, которые вы предоставляете для strtok. Единственная переменная, которая содержит «строку» - это buff. Если вы собираетесь звонить по какому-либо номеру, вы должны изменить buff на line и изменить line на word (или token). Теперь возможно, что ваш файл содержит строки из чего-то еще, отделенного ':' во входном файле (не предоставлен), но без большего мы изменим line на word и line_index на word_index в пример. Несмотря на то, что вы использовали word в качестве указателя слов, давайте сократим его до wp, чтобы избежать конфликта с переименованным массивом word указателей на каждое слово. buff хорошо, вы знаете, что в нем буферизуются символы.

Ваша последняя проблема в том, что вы не завершили nul buff перед передачей 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 для выделения / копирования

Как уже упоминалось выше, все, что делает 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 */

Использование strlen + calloc + memcpy То же самое

Если strdup не было доступно, или вы просто хотели выделить и скопировать вручную, то вы бы просто сделали то же самое. (1) Получить длину слова (или токена), на которую указывает wp, (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 (my 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*):

#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

(который создаст strtokstrdupcalloc.exe в текущем каталоге в Windows)

Для компиляции с использованием strdup, просто добавьте -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)

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

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

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