В C, как я могу создать ошибку, если входная строка слишком велика?

Я хочу прочитать список слов из файла, в котором по одному слову в строке. Каждое слово должно содержать до 4 символов. Как я могу выдать ошибку, если одна из строк длиннее этой?

Я пытался читать слова, используя fgets

char buf[5];
fgets(buf, 5, stdin);

и со сканф

char buf[5];
scanf("%4s", &buf);

но в обоих случаях он разбивает длинные строки на более мелкие. Например, qwerasdf читается как два слова, qwer и asdf. Есть ли способ обнаружить, что он пытался прочитать длинную строку с более чем 4 символами и вместо этого выдал ошибку?

Единственная альтернатива, о которой я могу думать, - это читать ввод посимвольно и позаботиться обо всем самостоятельно. Но есть ли более простое решение с использованием функций из стандартной библиотеки?


person hugomg    schedule 14.04.2020    source источник
comment
Проверьте последний символ вашего ввода fgets.   -  person Jongware    schedule 14.04.2020


Ответы (3)


Вы можете проверить длину прочитанной строки, а поскольку fgets также читает символ новой строки, вы можете явно проверить наличие '\n' в качестве последнего входного символа.

char buf[6];
while (fgets(buf, sizeof(buf), stdin)) {
    if (strlen(buf) > 5
        || (strlen(buf) == 5 && buf[strlen(buf) - 1] != '\n')) {
        fprintf(stderr, "line too long\n");
        exit(EXIT_FAILURE);
    }
}    

Буфер должен состоять как минимум из шести символов: 4 входных символа + 1 символ новой строки + строка, завершающая нулевой байт.

person Stephan Schlecht    schedule 14.04.2020
comment
Это может произойти с перенаправлением файлов. - person Nate Eldredge; 14.04.2020

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

Когда вы читаете строку из файла, fgets() (или POSIX getline()) считывает и включает '\n' как часть заполняемого ими буфера (если есть место). Если вы ожидаете до 4 символов, то размер буфера 5 слишком мал для размещения всех ваших символов, символа nul-terminating и '\n'. В вашем случае попытка прочитать 4-символьную строку ("cats") с 5-символьным буфером с fgets() приведет к тому, что buf удержит:

    +---+---+---+---+---+
    | c | a | t | s | \0|    -->   '\n' remains unread
    +---+---+---+---+---+

Вы также можете изящно справиться с этим (но лучше не экономить на размере буфера). Чтобы изящно решить проблему, вам нужно проверить:

  • если '\n' является последним символом в буфере, прочитать всю строку, обрезать '\n', перезаписав его завершающим нулем символом;
  • otherwise, read next char;
    • if next char is '\n', then OK, you read all chars and there wasn't room for the '\n' which you just read and checked -- continue reading the next line;
    • иначе, если следующий символ равен EOF, то вы читаете все символы в последней строке в файле с концом файла, отличным от POSIX (без '\n' после последней строки данных), прерываете цикл чтения, который вы нашли EOF;
  • иначе дополнительный символ останется непрочитанным в строке, прочитайте и отбросьте символы, пока не будет найден следующий '\n' или EOF

Собрав эту логику вместе, вы могли бы сделать:

#include <stdio.h>
#include <string.h>

int main (void) {

    char buf[5];

    while (fgets (buf, 5, stdin)) {                 /* read each line */
        if (strchr (buf, '\n'))                     /* if '\n' found - line read */
            buf[strcspn (buf, "\n")] = 0;           /* nul-termiante at '\n' */
        else {  /* otherwise */
            int c = getchar();                      /* read next chars */
            if (c == '\n')                          /* if '\n', OK read next line */
                continue;
            else if (c == EOF)                      /* if EOF, OK, non-POSIX eof */
                break;
            fputs ("error: line too long - discarding remainder.\n", stderr);
            for (; c != '\n' && c != EOF; c = getchar()) {}
        }
    }
}

Просмотрите все и дайте мне знать, если у вас есть дополнительные вопросы.

person David C. Rankin    schedule 14.04.2020

Здесь я сделал эту функцию для чтения файла char за char и возвращает только одну строку за вызов

так что теперь вы можете читать свой файл построчно, тип Line имеет массив символов value, где мы храним строку, и int hasNextLine 1 или 0 (bool), которые сообщают вам, есть ли в файле другая строка или нет, это удобно когда вы перебираете файл построчно.

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

typedef struct {
  char *value;
  int hasNextLine;
} Line;

Line * getLine(FILE *file) {
  Line *line = (Line *)malloc(sizeof(Line));
  if(line == NULL) {
    return NULL;
  }
  line->value = NULL;
  line->hasNextLine = 1;
  int n = 0, c;
  while(1) {
    c = getc(file);
    char *tmpStr = (char *)realloc(line->value, n + 2);
    if(tmpStr == NULL) {
      line->hasNextLine = -1;
      return line;
    }
    line->value = tmpStr;
    if(c == EOF) {
      line->hasNextLine = 0;
      line->value[n] = '\0';
      return line;
    }
    if(c == '\n') {
      line->value[n] = '\0';
      return line;
    }
    line->value[n] = c;
    n++;
  }
  return line;
}

Использование:

// example reading one line

int main() {
  FILE *f = fopen("your_file.txt", "r");

  if(f == NULL) {
    printf("File not found!");
    return 1;
  }

  Line *l = getLine(f);

  if(l != NULL) {
    printf("%s\n", l->hasNextLine != -1 ? l->value :
      "Error: while getting the line");
    free(l->value);
    free(l);
  }

  fclose(f);
  return 0;
}
// example reading the whole file

int main() {
  FILE *f = fopen("your_file.txt", "r");

  if(f == NULL) {
    printf("File not found!");
    return 1;
  }

  Line *l;
  int hasNextLine;

  while(1) {
    l = getLine(f);
    if(l != NULL) {
      printf("%s\n", l->hasNextLine != -1 ? l->value :
        "Error: while getting the line");
      free(l->value);
      hasNextLine = l->hasNextLine;
      free(l);
    }
    if(hasNextLine <= 0) {
      break;
    }
  }

  fclose(f);
  return 0;
}

вы можете сделать пользовательскую функцию для пользовательского ввода

char * sgetLine(char *msg) {
  printf("%s", msg);
  Line *l = getLine(stdin);
  char *strLine = NULL;
  if(l == NULL) {
    return NULL;
  }else {
    if(l->hasNextLine == -1) {
      free(l->value);
      free(l);
      return NULL;
    }
    strLine = l->value;
    free(l);
    return strLine;
  }
}

так что теперь вы можете использовать один вызов функции, чтобы распечатать вопрос и получить ответ (массив символов)

int main() {
  char *l = sgetLine("What is your name? ");
  if(l != NULL) {
    printf("%s\n", l);
  }
  free(l);
  return 0;
}
person SaymoinSam    schedule 14.04.2020