Является ли 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, ...);
описание
на