Существует как минимум два способа построения таблицы при чтении строк.Каждый использует свойство realloc()
, что если его первый аргумент является нулевым указателем, он будет вести себя как malloc()
и выделит запрошенное пространство (поэтому код может запускаться самостоятельно, используя только realloc()
).Этот код может выглядеть следующим образом:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { LEN = 1024*8 };
static void error(const char *fmt, ...);
static char *xstrdup(const char *str);
int main(void)
{
char line[LEN];
char **tab = NULL;
int tabsize = 0;
int lineno = 0;
while (fgets(line, sizeof(line), stdin) != 0)
{
if (lineno >= tabsize)
{
size_t newsize = (tabsize + 2) * 2;
char **newtab = realloc(tab, newsize * sizeof(*newtab));
if (newtab == 0)
error("Failed to allocate %zu bytes of memory\n", newsize * sizeof(*newtab));
tab = newtab;
tabsize = newsize;
}
tab[lineno++] = xstrdup(line);
}
/* Process the lines */
for (int i = 0; i < lineno; i++)
printf("%d: %s", i+1, tab[i]);
/* Release the lines */
for (int i = 0; i < lineno; i++)
free(tab[i]);
free(tab);
return(0);
}
static void error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(1);
}
static char *xstrdup(const char *str)
{
size_t len = strlen(str) + 1;
char *copy = malloc(len);
if (copy == 0)
error("Failed to allocate %zu bytes of memory\n", len);
memmove(copy, str, len);
return(copy);
}
Альтернатива явно использует malloc()
, когда таблица пуста, и наиболее просто кодируется как:
int main(void)
{
char line[LEN];
int tabsize = 4;
int lineno = 0;
char **tab = malloc(tabsize * sizeof(*tab));
if (tab == 0)
error("Failed to allocate %zu bytes of memory\n", tabsize * sizeof(*tab));
...
Все остальное может оставаться нетронутым.
Обратите внимание, что может быть удобно иметь функции xmalloc()
и xrealloc()
, которые гарантированно никогда не вернут нулевой указатель, поскольку вместо этого они сообщают об ошибке:
static void *xmalloc(size_t nbytes)
{
void *space = malloc(nbytes);
if (space == 0)
error("Failed to allocate %zu bytes of memory\n", nbytes);
return(space);
}
static void *xrealloc(void *buffer, size_t nbytes)
{
void *space = realloc(buffer, nbytes);
if (space == 0)
error("Failed to reallocate %zu bytes of memory\n", nbytes);
return(space);
}
В длинномterm (большие программы), это сокращает общее количество раз, когда вы пишете сообщения об ошибке «недостаточно памяти».С другой стороны, если вам необходимо восстановиться после сбоев выделения памяти (сохранить работу пользователя и т. Д.), Это неуместная стратегия.
Приведенный выше код создает рваный массив;разные записи в tab
имеют разную длину.Если вам нужны однородные длины (как в исходном коде), вам нужно заменить или изменить функцию xstrdup()
, чтобы выделить максимальную длину.
Возможно, вы заметили, что код в xstrdup()
используется memmove()
вместо strcpy()
.Это связано с тем, что strlen()
уже измерил длину строки, поэтому нет необходимости в коде копирования для проверки каждого байта на предмет необходимости копирования.Я использовал memmove()
, потому что он никогда не может пойти не так, даже если строки перекрываются, хотя в этом контексте ясно, что строки никогда не могут перекрываться, и поэтому memcpy()
- что не гарантируется, что корректно будет работать, если строки перекрываются -мог использоваться, поскольку строки не могут перекрываться.
Стратегия распределения (oldsize + 2) * 2
новых записей означает, что код перераспределения памяти выполняется достаточно часто во время тестирования, без чрезмерного влияния на производительность в производственной среде.См. Практику программирования Кернигана и Пайка, чтобы обсудить, почему это хорошая идея.
Я почти всегда использую набор функций, аналогичных функции error()
, потому что она значительноупрощает сообщение об ошибках.Функции, которые я обычно использую, являются частью пакета, который также записывает и сообщает имя программы (с argv[0]
), и имеет довольно широкий диапазон альтернативных вариантов поведения.