Простой реализации на C для Unix "хвост" команды


Ниже приводится как программа Unix "хвост". Он был назначен в качестве упражнения в главе 5 Керниган & Ричи на языке программирования С ++ . Потому что я только прочел большинство из главы 5, я до сих пор незнаком с определенной темы, такие как malloc(), который, возможно, был более подходящим для использования, я не знаю.

Я сделал немного программирования, но не достаточно, чтобы считать себя очень опытными, поэтому любой совет приветствуется. : )

#include <stdio.h>
#include <stdlib.h>

#define DEFLINES 10
#define MAXBUFF  20000

int findTail(char *lines[][2], int nlines, char buff[], int maxbuff);

/* main() processes optional cli argument '-n', where n is a number of lines.
 * The default is 10.  findTail finds the last n lines from the input so that
 * they can be printed. */

main(int argc, char *argv[])
{
  int nlines; char *endptr;

  endptr = NULL;
  nlines = DEFLINES;
  if (argc > 2) {
    printf("error: too many arguments.\n");
    return EXIT_FAILURE;
  }
  else if (argc == 2) {
    if (*argv[1] == '-') {
      nlines = strtol(argv[1] + 1, &endptr, 10);
      if (*endptr != '\0') {
        printf("error: not a number of lines: %s\n", argv[1] + 1);
        return EXIT_FAILURE;
      }
    }
    else {
      printf("error: malformed argument: %s\n", argv[1]);
      return EXIT_FAILURE;
    }
  }

  int i;
  char *lines[nlines][2], buff[MAXBUFF];

  findTail(lines, nlines, buff, MAXBUFF);
  for (i=0; i < nlines; ++i) {
    if (lines[i][0] != NULL)
      printf(lines[i][0]);
    if (lines[i][1] != NULL)
      printf(lines[i][1]);
  }
}

#define TRUE     1
#define FALSE    0

void shift(char *lines[][2], int nlines);
void testForRoom(char *lines[][2], int index, char *buffp);

/* findTail stores characters from stdin in the buffer 'buff'. When it finds
 * the end of a line, it stores the pointer for the beginning of that line in
 * 'lines'. once nlines have been found, pointers to previous lines are shifted
 * off of the end of 'lines'. If there is space at the start of 'buff' not
 * pointed to by 'lines', then the end of a line that hits the end of the
 * buffer can continue its storage at the beginning of the buffer. This makes
 * the best use of a fixed-sized buffer for long input. */

int findTail(char *lines[][2], int nlines, char buff[], int maxbuff)
{
  char *buffp, *linestart;
  int i, c, wrap, nfound;

  for (i=0; i < nlines; ++i) {
    lines[i][0] = NULL; // [0] for storing line, or beginning of wrapped line
    lines[i][1] = NULL; // [1] for storing second half of a wrapped line
  }

  nfound = 0;
  wrap = FALSE;
  linestart = buffp = buff;
  while ((c=getchar()) != EOF) {
    if (buffp == linestart && wrap == FALSE) {
      if (nfound < nlines)
        ++nfound;
      shift(lines, nlines);
    }

    if (buffp - buff == maxbuff - 1) {
      *buffp = '\0';
      lines[nlines - 1][0] = linestart;
      wrap = TRUE;
      linestart = buffp = buff;
    }

    testForRoom(lines, nlines - nfound, buffp);

    *buffp++ = c;
    if (c == '\n') {
      *buffp++ = '\0';
      lines[nlines - 1][wrap] = linestart;
      wrap = FALSE;
      if (buffp - buff >= maxbuff - 1)
        buffp = buff;
      linestart = buffp;
    }

  }
  // this is in case the input ended without a newline.
  if (c == EOF && buffp != buff && buffp[-1] != '\0') {
    testForRoom(lines, nlines - nfound, buffp);
    *buffp = '\0';
    lines[nlines - 1][wrap] = linestart;
  }

}

/* shift is used upon finding a character that starts a new line. It shifts
 * line pointers in the pointer array to the left, making room for new line
 * pointer(s) and forgetting the pointer(s) for the oldest line in memory. */

void shift(char *lines[][2], int nlines)
{
  int i;
  for (i=0; i < nlines - 1; ++i) {
    lines[i][0] = lines[i + 1][0];
    lines[i][1] = lines[i + 1][1];
  }
  lines[nlines - 1][0] = NULL;
  lines[nlines - 1][1] = NULL;
}

/* testForRoom tests to see if the location for (or the location following the)
 * next character that would be placed in the buffer is pointed to by a line in
 * the lines pointer array. */

void testForRoom(char *lines[][2], int index, char *buffp) {
  if (buffp == lines[index][0]
      || buffp + 1 == lines[index][0]) {
    printf("error: not enough room in buffer.\n");
    exit(EXIT_FAILURE);
  }
}


13308
16
задан 15 февраля 2011 в 03:02 Источник Поделиться
Комментарии
3 ответа

Вы используете стандарте C99 функции, такие как объявление переменных, часть пути через блок кода и Влас, но не повинуясь какому-то С99 ограничений, направленных на то, чтобы в Main() функция имеет явный тип возвращаемого значения int.

Потому что вы используете стандарте C99, вы имеете право оставить на возврат(0); (или возврат 0;) в конце функции main(). Я думаю, что был одним из менее хорошие решения в C++, который затем повторяется в C, и не осмелился это сделать сам; но я не могу критиковать ваш код при стандарт это позволяет.

Это может быть лучше использовать перечисления вместо #определение для константы; перечисление упрощает отладку, потому что значения в таблицу символов, в то время как #определить константы, как правило, нет.

enum { DEFLINES =    10 };
enum { MAXBUFF = 20000 };

Ваш дизайн только читает из стандартного ввода. Это не плохо, хотя это слегка ограничивает.

Код включает в себя:

printf(lines[i][0]);

Это очень опасный способ, чтобы использовать функции printf() - это конечная строка формата уязвимости. Беда в том, что если мой входной сигнал содержит:

%s%n%13$s

тогда функции printf() будет читать и писать значения в стек, который вы туда не клал, что приводит к большому несчастью. Как минимум, используйте:

printf("%s", lines[i][0]);

В качестве альтернативы, используйте:

fputs(lines[i][0], stdout);

(Не используйте кладет() , потому что он добавляет новые строки в конце вашего сведения - если вы удалите строку из входных данных.)

Когда я скомпилировать код, используя мои параметры по умолчанию, я получаю два предупреждения о функции printf() - подтверждение того, что я уже отметила - плюс предупреждение о том, что findTail() не возвращает значение, даже если он объявлен, чтобы возвращать тип int. Это лучший исправлена путем внесения его в пустоту функцию.

/usr/bin/gcc -g -std=c99 -Wall -Wextra -Wmissing-prototypes \
-Wstrict-prototypes cr.c -o cr

Это довольно строгий набор опций предупреждение, и ваш код хорош для создания только этих троих. Желаю всем код я имел дело с был чист.

Когда я запускаю программу на собственный исходный код, он отлично работает. Когда я запускаю его:

$ perl -e 'for $i (1..12) { print "A" x 2047, "\n"; }' | ./cr
error: not enough room in buffer.
$

Это нормально - вы говорили, что не с помощью функции malloc() , чтобы сделать динамическое выделение памяти. Как правило, хотя, ваши сообщения об ошибках должны содержать название программы, как найти в переменной argv[0], так что если существует несколько процессов в трубопроводе, например, вы можете сказать, какой из процессов привела к ошибке. Я делаю это с помощью вызова функции err_setarg0(массива argv[0]); в начале функции main(). Этой записи имя программы для использования в последующих сообщениях об ошибках. Я затем использовать вызовы функций, таких как err_error("ошибка: недостаточно места в буфере\п"); сообщить сообщения. Минимальная реализация этих двух функций:

#include <stdio.h>
#include <stdarg.h>

static const char *err_arg0 = "unknown";

void err_setarg0(const char *arg0)
{
err_arg0 = arg0;
}

void err_error(const char *fmt, ...)
{
va_list args;
fprintf(stderr, "%s: ", err_arg0);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(1);
}

У меня довольно сложный файл с множеством вариаций на эту тему, который обеспечивает простой в использовании отчетов об ошибках, в том числе вариации отчетности о системных ошибках (через ошибки) и в том числе временные метки и идентификатор процесса и ... все выбираемые на основе программа для проектирования.

Более пристальное изучение показывает, что в любой момент времени, не более одной строки будут обернуты - это означает, что в начале строки до конца буфера и завернуть в начало буфера.

В целом, довольно хорошая программа. Молодец (и я не говорю, что легко).

Я не уверен, насколько легко будет адаптироваться для обработки динамического выделения памяти - я подозреваю, что вы будете в конечном итоге с довольно различной схема управление памятью. Это сняло бы проблемы с большим количеством очень длинных строк в конце файла, переполнен буфер. В зависимости от вашей платформы, вы могли бы использовать в POSIX вызовом getLine() функция, которая выделяет память для вас, как он читает строки. Или вы можете решить, чтобы написать эмуляция кода. Вам тогда нужен только простой кольцевой буфер символ указателей, чтобы сохранить последние n строк, и вы сбрасываете старую (с бесплатной()) перед хранением в следующей строке.

13
ответ дан 16 февраля 2011 в 03:02 Источник Поделиться

Поскольку вы используете стандарте C99, вы можете быть заинтересованы в заголовок, это включает в себя три определения макроса, похожие на те, что вы уже определили. Этот заголовок имеет по крайней мере следующие макроопределения на любой реализации, соответствующей:

#define bool _Bool
#define true 1
#define false 0

В C99, _Bool является родным логический тип данных, содержит только 0 или 1. При преобразовании любой скалярный тип _Bool, он преобразует в 0, если скалярное тип равен 0, в противном случае он будет преобразовать в 1.

Кроме того, в стандарте C99, в “неявный инт” был удален, так что вы должны дать каждой функции возвращаемый тип, даже главный. В старых C, функции без явного указания возвращаемого типа бы “по умолчанию” в инт типа (как бы аргументы, я думаю).

5
ответ дан 17 февраля 2011 в 05:02 Источник Поделиться

Интересный дизайн. Производительность, вероятно, может быть улучшена путем выделения пару больших буферов, и чтение блоков входных поочередно в два буфера, пока ВФ встречаются. В тот момент, посчитайте через два квартала до тех пор, пока правильное количество строк были найдены, а затем распечатать оттуда все до конца.

Если входной случается файл на диске, можно искать место ближе к концу и подсчитать количество строк с этого момента; если не хватает, стремятся вернуться на некоторое расстояние и подсчитать количество строк между ссылки на предыдущие точка. Это позволило бы "хвост", чтобы эффективно работать, даже если вход-это несколько гигабайт дискового файла.

3
ответ дан 15 февраля 2011 в 05:02 Источник Поделиться