Вы можете использовать либо 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)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.