Ну, очевидно, что вы думаете по правильным линиям логики c, чтобы добраться до симулированных tail
строк, но вы, похоже, спотыкаетесь о том, как подходить к обработке распределения, перераспределения и освобождения памяти. (что, вероятно, указывает на упражнение).
Хотя ничто не мешает вам выделить ваши указатели для pa
в main()
и передать этот параметр в readlines()
, это несколько неловко способ сделать это. Когда вы думаете о создании функции, которая будет выделять хранилище для объекта, пусть функция выделяет для всего объекта и возвращает указатель на объект в случае успеха, или возвращает NULL в случае ошибки. Таким образом, вызывающая функция знает, что если функция возвращает действительный указатель, она отвечает за освобождение памяти, связанной с объектом (вместо того, чтобы часть памяти была выделена в разных местах). Если функция возвращает NULL
- вызывающая сторона знает, что функция не выполнена, и ей не нужно беспокоиться о какой-либо памяти для объекта.
Это также освобождает вас от необходимости передавать параметр для объекта. Так как вы выделите полный объект в функции, просто измените тип возвращаемого значения на тип вашего объекта ((char**
здесь)) и передайте pointer-to память, содержащую количество строк в вывод. Почему указатель? Если сохранено меньше этого количества строк (либо потому, что в читаемом файле меньше строк, либо вам не хватило памяти перед сохранением всех строк), вы можете обновить значение по этому адресу фактическим количеством сохраненных строк и сделать так, чтобы номер, доступный обратно в вызывающей стороне (main()
здесь).
С этими изменениями вы можете объявить вашу функцию как:
char **readlines (int *n)
{
В вашей функции вам нужно объявить счетчик строк - буфер для хранения строки, прочитанной из файла (для которой, я полагаю, ваш MAXLEN
), и объявления и выделения указателей для вашего объекта, проверки каждого выделения. Например:
int ndx = 0; /* line counter */
char buf[MAXLEN], **pa = malloc (*n * sizeof *pa); /* allocate pointers */
if (!pa) { /* validate pointer allocation */
perror ("malloc-pa");
return pa;
}
for (int i = 0; i < *n; i++) /* initialize all pointers NULL */
pa[i] = NULL;
Обратите внимание, что все указатели были инициализированы NULL
, что позволит realloc()
обрабатывать как начальное распределение, так и любые необходимые перераспределения. Также обратите внимание, что вместо использования malloc
для указателей, вы можете использовать calloc
, который установит все байты равными нулю (и для всех известных мне компиляторов, чтобы указатели оценивались как NULL
без явного указания l oop установка их). Однако это не гарантируется стандартом - так что все oop является правильным.
Здесь fgets()
используется для чтения каждой строки, а strcspn()
используется для обрезки '\n'
и получения длина каждой строки - вы можете использовать любую функцию, которая вам нравится. После того как строка прочитана, обрезана и получена длина, память выделяется (или перераспределяется) для хранения строки, и строка копируется в новый блок памяти. Ваш индекс nlines % n
работает правильно, но вы не увеличиваете nlines
до тех пор, пока после выделения и назначения не будет, например,
( note: Отредактировано ниже, чтобы рассматривать любой сбой перераспределения строк как терминал и Free All Memory возвращают NULL
, как обсуждалось в комментариях к @ 4386427 - это необходимо из-за циклического использования индексов, и любой сбой после того, как все выделенные строки были изначально, приведет к непригодным частичным результатам (непоследовательный вывод строки))
while (fgets (buf, MAXLEN, stdin)) { /* read each line of input */
void *tmp; /* tmp to realloc with */
size_t len; /* line length */
buf[(len = strcspn (buf, "\n"))] = 0; /* trim '\n', get length */
/* always realloc to a temporary pointer, validate before assigning */
if (!(tmp = realloc (pa[ndx % *n], len + 1))) {
int rm = ndx > *n ? *n : ndx; /* detrmine no. of lines to free */
perror ("realloc-pa[ndx % *n]");
while (rm--) /* loop freeing each allocated line */
free (pa[rm]);
free (pa); /* free pointers */
return NULL;
}
pa[ndx % *n] = tmp; /* assign new block to pa[ndx%n] */
memcpy (pa[ndx % *n], buf, len + 1); /* copy line to block of memory */
ndx++; /* increment line count */
}
( примечание: в случае сбоя выделения для какой-либо выделенной строки все выделенные строки освобождаются вместе с указателями и возвращается NULL
, исключая любую утечку памяти. Продолжается перезапись каждого из них. указатель с новым адресом для каждого вновь выделенного блока с непрерывной утечкой памяти, которая больше не может быть освобождена - вы потеряли начальный адрес для исходного блока при перезаписи указателя)
Последнее, что вы делаете раньше возвращать выделенный объект, чтобы проверить, меньше ли ваш индекс, чем значение для *n'
и, если оно есть, обновите значение по этому адресу, чтобы фактическое количество сохраненных строк было доступно обратно в вызывающей стороне, например,
if (ndx < *n) /* if less than *n lines read */
*n = ndx; /* update number at that address with ndx */
return pa; /* return allocated object */
}
Это в основном все для вашей функции. Если сложить все вместе с выводом, просто записанным из main()
, у вас будет:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define NLINES 10 /* default number of lines */
#define MAXLEN 1000 /* max characters per-line */
/* create and store last *n lines from stdin in allocated object,
* returning pointer to object on success, and updating value at n,
* if less than NLINES lines read. Return NULL on failure. Caller
* is responsible for freeing allocated memory.
*/
char **readlines (int *n)
{
int ndx = 0; /* line counter */
char buf[MAXLEN], **pa = malloc (*n * sizeof *pa); /* allocate pointers */
if (!pa) { /* validate pointer allocation */
perror ("malloc-pa");
return pa;
}
for (int i = 0; i < *n; i++) /* initialize all pointers NULL */
pa[i] = NULL;
while (fgets (buf, MAXLEN, stdin)) { /* read each line of input */
void *tmp; /* tmp to realloc with */
size_t len; /* line length */
buf[(len = strcspn (buf, "\n"))] = 0; /* trim '\n', get length */
/* always realloc to a temporary pointer, validate before assigning */
if (!(tmp = realloc (pa[ndx % *n], len + 1))) {
int rm = ndx > *n ? *n : ndx; /* detrmine no. of lines to free */
perror ("realloc-pa[ndx % *n]");
while (rm--) /* loop freeing each allocated line */
free (pa[rm]);
free (pa); /* free pointers */
return NULL;
}
pa[ndx % *n] = tmp; /* assign new block to pa[ndx%n] */
memcpy (pa[ndx % *n], buf, len + 1); /* copy line to block of memory */
ndx++; /* increment line count */
}
if (ndx < *n) /* if less than *n lines read */
*n = ndx; /* update number at that address with ndx */
return pa; /* return allocated object */
}
int main (int argc, char **argv) {
char *p = NULL, **lines = NULL; /* pointers for strtol, and lines */
int n = argc > 1 ? (int)strtol (argv[1], &p, 0) : NLINES;
if (n != NLINES && (errno || p == argv[1])) { /* validate conversion */
fprintf (stderr, "error: invalid no. of lines '%s'\n", argv[1]);
return 1;
}
if (!(lines = readlines(&n))) { /* read lines validate return */
fputs ("error: readlines failed.\n", stderr);
return 1;
}
for (int i = 0; i < n; i++) { /* loop over each stored line */
puts (lines[i]); /* output line */
free (lines[i]); /* free storage for line */
}
free (lines); /* free pointers */
}
(вы можете добавить функции, которые вы хотите заменить, читать с fgets()
и вывод l oop в main()
, по желанию).
Пример использования / Вывод
Поведение по умолчанию:
$ printf "%s\n" line{1..20} | ./bin/tail
line11
line12
line13
line14
line15
line16
line17
line18
line19
line20
Вывод только 5
строк вместо по умолчанию:
$ printf "%s\n" line{1..20} | ./bin/tail 5
line16
line17
line18
line19
line20
Обрабатывать меньше количества строк по умолчанию в файле:
$ printf "%s\n" line{1..5} | ./bin/tail
line1
line2
line3
line4
line5
Проверка использования памяти / ошибок
В любом коде Вы пишете, что динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, поэтому, (2 ) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за ее пределами. границы вашего выделенного блока, попытка прочитать или основать условный j Возьмите неинициализированное значение и, наконец, подтвердите, что вы освобождаете всю выделенную память.
Для Linux valgrind
это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через нее.
$ printf "%s\n" line{1..20} | valgrind ./bin/tail 5
==25642== Memcheck, a memory error detector
==25642== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==25642== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==25642== Command: ./bin/tail 5
==25642==
line16
line17
line18
line19
line20
==25642==
==25642== HEAP SUMMARY:
==25642== in use at exit: 0 bytes in 0 blocks
==25642== total heap usage: 23 allocs, 23 frees, 5,291 bytes allocated
==25642==
==25642== All heap blocks were freed -- no leaks are possible
==25642==
==25642== For counts of detected and suppressed errors, rerun with: -v
==25642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.