Является ли fgets () возвращающим NULL с коротким буфером совместимым?

в модульном тестировании функция, содержащая fgets(), наткнулся на неожиданный результат, когда размер буфера n < 2. Очевидно, что такой размер буфера глуп, но тест исследует угловые случаи.

упрощенный код:

#include <error.h>
#include <stdio.h>

void test_fgets(char * restrict s, int n) {
  FILE *stream = stdin;
  s[0] = 42;
  printf("< s:%p n:%d stream:%pn", s, n, stream);
  char *retval = fgets(s, n, stream);
  printf("> errno:%d feof:%d ferror:%d retval:%p s[0]:%dnn",
    errno, feof(stream), ferror(stream), retval, s[0]);
}

int main(void) {
  char s[100];
  test_fgets(s, sizeof s);  // Entered "123n" and works as expected
  test_fgets(s, 1);         // fgets() --> NULL, feof() --> 0, ferror() --> 0
  test_fgets(s, 0);         // Same as above
  return 0;
}

что удивительно, это fgets() возвращает NULL и ни feof(), ни ferror() are 1.

спецификация C, ниже, кажется молчаливой на этом редком случай.

вопросы:

  • возвращается NULL без feof(), ни ferror() поведение уступчивое?
  • может ли другой результат быть совместимым поведением?
  • имеет ли значение, если n 1 или меньше 1?

платформа: версия 4.5.3 gcc цель: i686-pc-cygwin

вот аннотация от стандарта C11, некоторый акцент мой:

7.21.7.2 в fgets функции

на fgets функция считывает не более одного меньше, чем количество символов, указанных n [...]

на fgets возвращает функция s в случае успеха. Если обнаружен конец файла и никакие символы не были прочитаны в массив, содержимое массива остается неизменным и null возвращается указатель. Если во время операции возникает ошибка чтения, содержимое массива не определено и возвращается нулевой указатель.

обзоры проводки
как использовать feof и ferror для fgets (minishell в C)
проблема создания оболочки в C (Seg-Fault и ferror)
fputs(), fgets (), ferror () вопросы и эквиваленты C++
возвращаемое значение fgets ()


[Edit] комментарии к ответам

@Shafik Yaghmour хорошо представил общую проблему: поскольку спецификация C не упоминает, что делать, когда она не читается любой данные, ни писать любой данные s когда (n <= 0), это неопределенное поведение. Поэтому любой разумный ответ должен быть приемлемым, например return NULL, не устанавливайте флаги, оставьте буфер в покое.

о том, что должно произойти, когда n==1, @Oliver Matthews ответ и комментарий @Matt McNabb указывают на отсутствие ясности спецификации C, учитывая буфер n == 1. C spec кажется в пользу буфер n == 1 должен возвращать указатель буфера с s[0] == '', но недостаточно ясно.

3 ответов


поведение отличается в более новых версиях glibc, for n == 1 возвращает s что указывает на успех, это не необоснованное чтение 7.19.7.2 функция fgets абзац 2 где сказано (это то же самое в C99 и C11, акцент мой):

char * fgets(char * restrict s,int n, FILE * restrict stream);

функция fgets читает не более одного меньше, чем количество символов, указанных n из потока, на который указывает поток, в массив, на который указывает s. Без дополнительных символы считываются после символа новой строки (который сохраняется) или после окончания файла. нулевой символ записывается сразу после последнего символа, считанного в массив.

не очень полезно, но не нарушает ничего, что сказано в стандарте, он будет читать самое большее 0 символы и null-завершить. Таким образом, результаты, которые вы видите, выглядят как ошибка, которая была исправлена в более поздних версиях glibc. Это же явно не конец файла или ошибка чтения как описано в пункте 3:

[...]Если обнаружен конец файла и никакие символы не были прочитаны в массив, содержимое массива остается неизменным и возвращается нулевой указатель. Если во время операции возникает ошибка чтения, содержимое массива не определено, а нулевой указатель возвращенный.

Что касается последнего случая, когда n == 0 похоже, это просто неопределенное поведение. Проект раздела стандарта C99 4. соответствие абзац 2 говорит (выделено мной):

если нарушается требование’ должен ‘или’ не должен", которое появляется вне ограничения, поведение не определено. неопределенное поведение в противном случае указано в этом международном Стандарт по словам "неопределенное поведение"или опущение какого-либо явного определения поведения. Нет никакой разницы в акцентах между этими тремя; все они описывают "поведение, которое не определено".

формулировка та же в C11. Невозможно читать не более -1 символов и это не конец файла или ошибка чтения. Таким образом, у нас нет явного определения поведения в этом случае. Похоже на дефект, но я не могу. найдите любые отчеты о дефектах, которые покрывают это.


tl; dr: эта версия glibc имеет ошибку для n=1, спецификация имеет (возможно) двусмысленность для n

таким образом, спецификация c99 в основном одинакова.

поведение test_fgets(s, 1) - Это неправильно. в glibc 2.19 дает правильный результат (retval!=null, s[0]==null.

поведение test_fgets(s,0) не определено, на самом деле. Он не был успешным (вы не можете прочитать не более -1 символов), но он не попадает любой из двух критериев' return null ' (EOF & 0 read; read error).

однако поведение GCC, возможно, правильно (возврат указателя на неизмененный s также будет в порядке) - feof не установлен, потому что он не попал в eof; ferror не установлен, потому что не было ошибки чтения.

Я подозреваю, что логика в gcc (не получила источник) имеет "если n

[edit:]

по размышлении, я действительно думаю, что glibc поведение n=0 это самый правильный ответ, который он может дать:

  • нет eof читать, так что feof()==0
  • нет чтения, поэтому ошибка чтения не могла произойти, поэтому ferror=0

теперь что касается возвращаемого значения - чем fgets не может прочитали -1 символ (это невозможно). Если fgets вернет переданный указатель назад, это будет выглядеть как успешный вызов. - Игнорируя этот угловой случай, fgets обязуется вернуть строку с нулевым завершением. Если нет, то на это нельзя положиться. Но fgets установит символ после после последнего символа в массиве к нулю. учитывая, что мы читаем в символах -1 (apparantly) при этом вызове, это заставит его установить 0-й символ в null?

Итак, самый разумный выбор-вернуть null (на мой взгляд).


стандарт C (проект C11 n1570) указывает fgets() этот путь (некоторый акцент мой):

7.21.7.2 в fgets функции

справка

   #include <stdio.h>
   char *fgets(char * restrict s, int n,
               FILE * restrict stream);

описание

на ';. Спецификация для gets_s предлагает такое же значение, с той же неточность.

спецификация snprintf точнее, случай n = 0 явно указано, с полезной семантикой прилагается. К сожалению, такая семантика не может быть реализована для fgets:

7.21.6.5 в snprintf функции

справка

#include <stdio.h>
int snprintf(char * restrict s, size_t n,
     const char * restrict format, ...);

описание

на