У вас есть большое количество ошибок в вашем коде, и с вашим подходом в целом. Нет необходимости делать два прохода по файлу, чтобы определить количество строк перед выделением, а затем перечитать файл в попытке проанализировать данные. Кроме того, нет необходимости маркировать каждую строку для разделения значений, разделенных запятыми, sscanf()
для анализа двух строк, и здесь достаточно одного int
после чтения каждой строки с fgets
.
Несмотря на то, что вы можете свободно передавать любой набор параметров и возвращать все, что захотите, поскольку вы выделяете для массива struct и считывает значения в массив, имеет смысл возвращать указатель на выделенный массив из вашего функция (или NULL
при сбое) и просто обновите параметр, переданный в качестве указателя, чтобы сделать общее число прочитанных строк доступным обратно в вызывающей стороне.
Кроме того, обычно вы хотите открыть и проверить файл в вызывающей программе и передать параметр FILE*
, передающий поток открытых файлов вашей функции. Имея это в виду, вы можете изменить свою функцию следующим образом:
/* read saleslines into array of saleslines_t, allocating for
* salesid, and smmcampaignid within each struct. Return pointer
* to allocated array on success with lines updated to hold the
* number of elements, or NULL otherwise.
*/
saleslines_t *read_saleslines (FILE *fp, size_t *lines)
{
В вашей функции вам просто нужен буфер для хранения каждой прочитанной строки, счетчик для отслеживания количества элементов, выделенных в вашем массиве, и указатель на ваш массив для возврата. Например, вы можете сделать что-то вроде следующего, чтобы обработать все три:
char buf[MAXC]; /* buffer to hold line */
size_t maxlines = MINL; /* maxlines allocated */
saleslines_t *sales = NULL; /* pointer to array of struct */
( примечание: , поскольку вы отслеживаете количество строк, прочитанных через указатель lines
, переданных в качестве параметра, имеет смысл инициализировать значение по этому адресу в ноль)
Теперь начинается работа вашей функции, вы хотите прочитать каждую строку в buf
и проанализировать необходимую информацию из каждой строки. Поскольку salesid
и smmcampaignid
являются указателями на символ в вашей структуре, вам необходимо выделить блок памяти для каждой строки, анализируемой в строке, скопировать строку в новый блок памяти и затем назначить Начальный адрес Бока к каждому из ваших указателей. Чтобы «динамически» обрабатывать размещение элементов для вашей структуры, вы просто проверяете, равно ли количество заполненных строк (*lines
) по сравнению с выделенным числом (maxlines
), (или если *lines
равно нулю, указывая на необходимость начального allocation) и realloc
в обоих случаях либо realloc
(или заново выделять) хранилище для вашего массива структуры.
Когда вы realloc
, вы всегда realloc
используете временный указатель, поэтому, если realloc
завершится с ошибкой и вернет NULL
, вы не перезапишите свой указатель на выделенный в данный момент блок с помощью NULL
тем самым создавая утечку памяти.
Объединение всего этого в начале вашей функции может показаться утомительным, но на самом деле все просто, например,
while (fgets (buf, MAXC, fp)) { /* read each line in file */
char id[MAXC], cid[MAXC]; /* temp arrays to hold strings */
int bottles; /* temp int for numberofbottles */
if (*lines == maxlines || !*lines) { /* check if realloc req'd */
/* always realloc with a temp pointer */
void *tmp = realloc (sales, 2 * maxlines * sizeof *sales);
if (!tmp) { /* if realloc fails, original pointer still valid */
perror ("realloc-sales"); /* throw error */
return sales; /* return current pointer */
} /* (don't exit or return NULL) */
sales = tmp; /* assign reallocated block to sales */
/* (optional) zero newly allocated memory */
memset (sales + *lines, 0, maxlines * sizeof *sales);
maxlines *= 2; /* update maxlines allocated */
}
Теперь вы готовы анализировать требуемую информацию из вашей строки с помощью sscanf
, а затем, после успешного анализа информации, вы можете выделить для каждого из ваших salesid
и smmcampaignid
указателей скопировать проанализированную информацию в новые блоки памяти, присваивающие начальный адрес каждому указателю соответственно, например,
/* parse needed data from line (sscanf is fine here) */
if (sscanf (buf, "%1023[^,],%1023[^,],%d", id, cid, &bottles) == 3) {
size_t idlen = strlen (id), /* get lengths of strings */
cidlen = strlen (cid);
sales[*lines].salesid = malloc (idlen + 1); /* allocate string */
if (!sales[*lines].salesid) { /* validate! */
perror ("malloc-sales[*lines].salesid");
break;
}
sales[*lines].smmcampaignid = malloc (cidlen + 1); /* ditto */
if (!sales[*lines].smmcampaignid) {
perror ("malloc-sales[*lines].smmcampaignid");
break;
}
memcpy (sales[*lines].salesid, id, idlen + 1); /* copy strings */
memcpy (sales[*lines].smmcampaignid, cid, cidlen + 1);
sales[(*lines)++].numberofbottles = bottles; /* assign int */
} /* (note lines counter updated in last assignment) */
( примечание: вы можете использовать strdup
, чтобы получить длину каждой проанализированной строки и выделить достаточно памяти для хранения строки и назначить ее указателю за один раз, например, sales[*lines].salesid = strdup (id);
, но ... strdup
не требуется включать в C99 или более позднюю версию, поэтому получить длину, выделить length + 1
байт, а затем memcpy
вашу строку так же просто, чтобы обеспечить переносимость. strdup
выделяет память, вы должны проверить возвращенный указатель - что-то упускают из виду 99% тех, кто его использует.)
Вот и все, когда fgets()
терпит неудачу, вы достигли EOF
, теперь просто:
return sales; /* return dynamically allocated array of struct */
}
В целом, в коротком рабочем примере, в котором имя файла читается как первый аргумент вашей программы (или читает из stdin
по умолчанию, если аргумент не указан), вы можете сделать:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MINL 2
typedef struct saleslines{
char *salesid;
char *smmcampaignid;
int numberofbottles;
} saleslines_t;
/* read saleslines into array of saleslines_t, allocating for
* salesid, and smmcampaignid within each struct. Return pointer
* to allocated array on success with lines updated to hold the
* number of elements, or NULL otherwise.
*/
saleslines_t *read_saleslines (FILE *fp, size_t *lines)
{
char buf[MAXC]; /* buffer to hold line */
size_t maxlines = MINL; /* maxlines allocated */
saleslines_t *sales = NULL; /* pointer to array of struct */
*lines = 0; /* zero lines */
while (fgets (buf, MAXC, fp)) { /* read each line in file */
char id[MAXC], cid[MAXC]; /* temp arrays to hold strings */
int bottles; /* temp int for numberofbottles */
if (*lines == maxlines || !*lines) { /* check if realloc req'd */
/* always realloc with a temp pointer */
void *tmp = realloc (sales, 2 * maxlines * sizeof *sales);
if (!tmp) { /* if realloc fails, original pointer still valid */
perror ("realloc-sales"); /* throw error */
return sales; /* return current pointer */
} /* (don't exit or return NULL) */
sales = tmp; /* assign reallocated block to sales */
/* (optional) zero newly allocated memory */
memset (sales + *lines, 0, maxlines * sizeof *sales);
maxlines *= 2; /* update maxlines allocated */
}
/* parse needed data from line (sscanf is fine here) */
if (sscanf (buf, "%1023[^,],%1023[^,],%d", id, cid, &bottles) == 3) {
size_t idlen = strlen (id), /* get lengths of strings */
cidlen = strlen (cid);
sales[*lines].salesid = malloc (idlen + 1); /* allocate string */
if (!sales[*lines].salesid) { /* validate! */
perror ("malloc-sales[*lines].salesid");
break;
}
sales[*lines].smmcampaignid = malloc (cidlen + 1); /* ditto */
if (!sales[*lines].smmcampaignid) {
perror ("malloc-sales[*lines].smmcampaignid");
break;
}
memcpy (sales[*lines].salesid, id, idlen + 1); /* copy strings */
memcpy (sales[*lines].smmcampaignid, cid, cidlen + 1);
sales[(*lines)++].numberofbottles = bottles; /* assign int */
} /* (note lines counter updated in last assignment) */
}
return sales; /* return dynamically allocated array of struct */
}
int main (int argc, char **argv) {
saleslines_t *sales = NULL; /* pointer to saleslines_t */
size_t nlines;
/* 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;
}
sales = read_saleslines (fp, &nlines); /* read saleslines */
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < nlines; i++) { /* loop over each */
printf ("sales[%2zu]: %s %s %2d\n", i, sales[i].salesid,
sales[i].smmcampaignid, sales[i].numberofbottles);
free (sales[i].salesid); /* free salesid */
free (sales[i].smmcampaignid); /* free smmcampaignid */
}
free (sales); /* free sales */
return 0;
}
Пример использования / Вывод
$ ./bin/saleslines dat/saleslines.txt
sales[ 0]: SO02773202 5087001 0
sales[ 1]: SO02773203 5087001 0
sales[ 2]: SO02773204 5087001 0
sales[ 3]: SO02773205 5087001 0
sales[ 4]: SO02773206 5087001 14
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так что, (2) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, , чтобы подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/saleslines dat/saleslines.txt
==19819== Memcheck, a memory error detector
==19819== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==19819== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==19819== Command: ./bin/saleslines dat/saleslines.txt
==19819==
sales[ 0]: SO02773202 5087001 0
sales[ 1]: SO02773203 5087001 0
sales[ 2]: SO02773204 5087001 0
sales[ 3]: SO02773205 5087001 0
sales[ 4]: SO02773206 5087001 14
==19819==
==19819== HEAP SUMMARY:
==19819== in use at exit: 0 bytes in 0 blocks
==19819== total heap usage: 13 allocs, 13 frees, 935 bytes allocated
==19819==
==19819== All heap blocks were freed -- no leaks are possible
==19819==
==19819== For counts of detected and suppressed errors, rerun with: -v
==19819== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Нет ничего сложного в динамическом выделении чего-либо. Просто возьмите его достаточно маленькими кусочками, чтобы разбить все "I's"
и пересечь все "T's"
для каждого указателя, требующего выделения. Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.