Продолжая комментарий, немедленная ошибка связана с char *name[14]
объявлением массива из 14 указателей на char, которые являются неинициализированными. Это означает, что каждый указатель никуда не указывает (например, адрес, который указатель удерживает, поскольку его значение указывает на некоторый неопределенный адрес памяти). Прежде чем вы сможете что-либо сохранить, вы должны убедиться, что у вас есть допустимая ячейка памяти для хранения того значения, с которым вы имеете дело. Для строки, такой как name[x]
, это означает, что вам нужно length + 1
символов (+1
для обеспечения памяти для завершающего нулем символа, '\0'
, эквивалентно старому 0
)
Ваша ReadFile()
функция ужасно неадекватна, а ваше чтение непрочно. Каждый раз, когда вы читаете строки данных, вы должны использовать строчно-ориентированную функцию ввода, такую как fgets()
(или POSIX getline()
). Таким образом, вы будете читать (использовать) строку ввода каждый раз, и любое отклонение форматирования входного файла, которое вызывает сопоставление-сбой, повлияет только на эту строку и не повлияет на чтение файла с этой точки. вперед.
Никогда не жестко кодируйте имена файлов и не используйте MagicNumbers в своем коде. Вам не нужно перекомпилировать вашу программу просто для чтения из другого входного файла. Вы можете указать имя файла для использования по умолчанию, но в противном случае взять имя файла для чтения в качестве аргумента вашей программы (для этого используется int argc, char **argv
) или запросить пользователя и принять имя файла в качестве входных данных. Чтобы избежать жестко запрограммированных имен файлов и магических чисел в вашем коде:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define COINS 14 /* if you need a constant, #define one (or more) */
#define MAXC 1024
а также
int main (int argc, char **argv)
{
...
FILE *fp = fopen (argc > 1 ? argv[1] : "coins.txt", "r");
...
Кроме того, вы обычно хотите открыть и проверить, открыт ли файл для чтения в вызывающей функции, и передать указатель FILE*
на открытый поток в качестве аргумента функции. Если открыть файл не удается, нет необходимости выполнять вызов функции для начала.
Соединение всех частей вместе и использование буфера из MAXC
символов для хранения каждой строки, прочитанной из вашего файла, перед преобразованием (и проверкой) преобразования. (хорошая работа по подтверждению возврата от fscanf()
).
Чтобы решить вашу проблему, вы должны прочитать значения name
во временный массив, а затем, чтобы выделить хранилище, просто возьмите strlen()
строки во временном массиве и символы malloc (len + 1)
для name[i]
(проверка каждого распределения), а затем скопируйте из временного массива до name[i]
. (вам нужно будет освободить память, когда вы закончите со значениями обратно в вызывающей стороне (main()
здесь)
Вам также понадобятся отдельные счетчики для line
и index
. Поскольку вы сообщаете строку, в которой произошел какой-либо сбой (снова хорошая работа!), Вам нужно будет увеличивать счетчик line
на каждой итерации, но вы хотите увеличивать индекс для name
и cash
только при успешном преобразовании, например
size_t ReadFile (FILE *fp, int *cash, char **name)
{
char buf[MAXC]; /* temporary array to hold line */
size_t index = 0, line = 0; /* separate counters for index and line */
while (line < COINS && fgets (buf, MAXC, fp)) /* protect array, read line */
{
char coinname[MAXC]; /* temporary array for name */
int r, value; /* declare variables in scope required */
size_t len; /* length of name */
r = sscanf (buf, "%1023s %d", coinname, &value); /* convert values */
if (r != 2) { /* validate conversion */
fprintf (stderr, "Error, line %zu in wrong format!\n\n", line);
line++; /* don't forget to increment line */
continue; /* before continuing to read next line */
}
len = strlen (coinname); /* get length of name */
name[index] = malloc (len + 1); /* allocate len + 1 chars */
if (name[index] == NULL) { /* validate allocation */
perror ("malloc-name[index]");
return index;
}
memcpy (name[index], coinname, len + 1); /* copy to name[index] */
cash[index] = value; /* assign to cash[index] */
index++; /* increment index */
line++; /* increment line */
}
return index; /* return index */
}
(примечание: изменение типа возвращаемого значения с void
на size_t
, чтобы разрешить возврат количества имен и значений, считанных из файла. Всегда указывайте значимый тип возвращаемого значения для любой функции, которая может быть успешной или неудачной. , например, практически все, кроме простой печати или бесплатных функций)
У вас есть другой вариант хранения для name
. Вместо выделения памяти с помощью malloc()
(или calloc()
, или realloc()
) вы можете объявить name
как 2D-массив символов с объемом памяти для каждой строки, достаточным для хранения самых длинных символов имени (+1). Это, в зависимости от разницы в длине между именами, может потребовать немного больше памяти, чем точное выделение для хранения каждого name
. Это немного упрощает работу, избавляя от необходимости выделять ресурсы. Вам решать.
Теперь вы можете присвоить результат вашей функции переменной в main()
, чтобы вы знали, сколько пар name
и cash
было успешно прочитано, например
int main (int argc, char **argv)
{
int cash[COINS];
char *name[COINS] = {NULL};
size_t ncoins = 0;
/* use filename provided as 1st argument (coins.txt by default) */
FILE *fp = fopen (argc > 1 ? argv[1] : "coins.txt", "r");
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
ncoins = ReadFile (fp, cash, name);
fclose (fp);
for (size_t i = 0; i < ncoins; i++) {
printf ("%-12s %3d\n", name[i], cash[i]);
free (name[i]); /* free mem when done */
}
}
Пример входного файла
$ cat dat/name_coins.txt
penny 20
nickle 3
dime 8
quarter 15
half-dollar 5
dollar 4
Пример использования / вывода
$ ./bin/name_coins dat/name_coins.txt
penny 20
nickle 3
dime 8
quarter 15
half-dollar 5
dollar 4
Использование памяти / проверка ошибок
В любом коде, который вы пишете, который динамически выделяет память, у вас есть 2 обязанности относительно любого выделенного блока памяти: (1) всегда сохранять указатель на начальный адрес для блока память так, (2) ее можно освободить, когда она больше не нужна.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или записи за пределами / за пределами выделенного блока, пытаетесь прочитать или основать условный переход на неинициализированном значении и, наконец, для подтверждения что вы освободили всю выделенную память.
Для Linux valgrind
- нормальный выбор. Подобные программы проверки памяти существуют для каждой платформы. Все они просты в использовании, просто запустите свою программу через них.
$ valgrind ./bin/name_coins dat/name_coins.txt
==5870== Memcheck, a memory error detector
==5870== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5870== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5870== Command: ./bin/name_coins dat/name_coins.txt
==5870==
penny 20
nickle 3
dime 8
quarter 15
half-dollar 5
dollar 4
==5870==
==5870== HEAP SUMMARY:
==5870== in use at exit: 0 bytes in 0 blocks
==5870== total heap usage: 9 allocs, 9 frees, 5,717 bytes allocated
==5870==
==5870== All heap blocks were freed -- no leaks are possible
==5870==
==5870== For counts of detected and suppressed errors, rerun with: -v
==5870== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Просмотрите все и дайте мне знать, если у вас возникнут дополнительные вопросы.
person
David C. Rankin
schedule
22.05.2021