Я изучаю fgets из справочной страницы. Я сделал несколько тестов на fgets, чтобы убедиться, что понимаю его. Один из тестов, который я сделал, приводит к поведению, противоположному тому, что указано на странице руководства. На странице руководства говорится:
char *fgets(char s[restrict .size], int size, FILE *restrict stream);
fgets() считывает не более одного символа меньше size из потока и сохраняет их в буфер, на который указывает s. Чтение останавливается после EOF или новая строка. Если читается новая строка, она сохраняется в буфере. А завершающий нулевой байт ('\0') сохраняется после последнего символа в буфер.
Но он не «считывает не более одного символа меньшего размера из потока». Как показано в следующей программе:
#include<stdio.h>
#include<stdlib.h>
int main(){
FILE *fp;
fp=fopen("sample", "r");
char *s=calloc(50, sizeof(char));
while(fgets(s,2,fp)!=NULL) printf("%s",s);
}
Образец файла:
thiis is line no. 1
joke joke 2 joke joke
arch linux btw 3
4th line
5th line
Вывод скомпилированного бинарника:
thiis is line no. 1
joke joke 2 joke joke
arch linux btw 3
4th line
5th line
Ожидаемый результат согласно справочной странице:
t
j
a
4
5
Страница руководства неверна, или я что-то упустил?
Измените printf("%s",s);
на printf("%s--",s);
, и вы увидите, что fgets()
делает то, на что претендует.
Вы запрашиваете чтение 1 символа в цикле. Он читает один символ, следующий символ, следующий символ. Он не отбрасывает символы, если буфер меньше строки.
как мне отметить это решенным?
@heman: Вы можете ответить на свой вопрос . Или вы можете просто удалить его, если считаете, что вопрос не будет полезен другим читателям. Поскольку ваш вопрос основан на недопонимании с вашей стороны, возможно, будет лучше просто удалить его. Если за ваш вопрос не проголосовали, он, вероятно, будет автоматически удален через определенный период времени.
Или просто сделайте printf("|%s|",s);
Чтобы вывести только 1-й символ while (fgets(s,50,fp)) { printf ("%c\n", *s); }
(это выведет 1-й символ из каждого чтения до 49
символов с fgets()
, за которым следует новая строка) Вы можете заменить printf()
на putchar(*s); putchar('\n');
Нет особого смысла использовать calloc()
для выделения 50 байт — вместо этого вы можете использовать char s[50];
. Или, действительно, вы могли бы использовать char s[2];
, так как вы говорите fgets()
, что размер массива составляет 2 байта. Если вы используете calloc()
, вам, вероятно, также следует использовать free(s)
перед окончанием функции, и вы обязательно должны проверить, что calloc()
было успешным, прежде чем пытаться использовать выделенное пространство. Конечно, в этой программе это не является большой проблемой — выделение памяти вряд ли приведет к сбою, а утечка памяти очищается при выходе из программы. Но лучше всего использовать хорошие привычки с самого начала.
@JonathanLeffler Хорошая информация. Разве это не освобождает память автоматически в конце основной функции?
Да; вся динамически выделяемая память освобождается при завершении программы. Вот почему я сказал «в этой программе это не большая проблема». Однако в других случаях невысвобождение памяти означает утечку памяти, и программе может не хватить памяти из-за утечки слишком большого объема памяти. И вполне вероятно, что программа резко завершится. Итак, это хорошая привычка — знать, где будет освобожден любой выделенный ресурс — память, файловые потоки, любой ресурс.
В приведенной цитате написано ясно
Если читается новая строка, она сохраняется в буфере.
Где вы видите, что этот вызов fgets(s,2,fp)
читает символ новой строки, например, при чтении этой строки?
thiis is line no. 1
Строка содержит только один символ новой строки в конце.
Этот вызов считывает только один символ за другим, то есть символ за символом, к которому добавляется завершающий нулевой символ '\0'
.
Итак, прочитанные строки выглядят так
{ 't', '\0' }
{ 'h', '\0' },
{ 'i', '\0' }
// ...
{ '1', '\0' }
{ '\n', '\0' }
Если у вас есть вызов fgets
вот так
fgets(s,n,fp)
то из входного потока считывается не более n-1
символов. Один символ зарезервирован для завершающего нулевого символа '\0'
для построения строки.
Из стандарта C (7.21.7.2 Функция fgets)
2 Функция fgets считывает не более чем на единицу меньше числа символы, указанные параметром n, из потока, на который указывает stream, в массив, на который указывает s. Никакие дополнительные символы не читаются после символ новой строки (который сохраняется) или после конца файла. ноль символ записывается сразу после последнего символа, прочитанного в массив
Страница руководства неверна или я что-то упустил?
Я не скажу, что справочная страница неверна, но она могла бы быть более понятной.
Есть 3 вещи, которые могут помешать fgets
читать из стрима.
Буфер заполнен (т. е. осталось место только для завершающего символа)
Символ новой строки был прочитан из потока
Конец файла произошел
На цитируемой справочной странице ясно упоминаются только два из этих условий.
Чтение останавливается после EOF или новой строки.
То есть № 2 и № 3 упоминаются очень явно, в то время как № 1 (отчасти) получен из
считывает не более одного символа меньше size из потока
Вот еще описание от https://man7.org/linux/man-pages/man3/fgets.3p.html
... читать байты из потока в массив, на который указывает s, до тех пор, пока не будет прочитано n-1 байт, или новая строка не будет прочитана и передана в s, или не встретится условие конца файла.
где четко указаны 3 случая.
Но да... вы что-то упускаете. Как только буфер заполняется, оставшаяся часть текущей строки не читается и отбрасывается. Остальные останутся в потоке и будут доступны для следующего чтения. Так что ничего не потеряно. Вам просто нужно больше вызовов fgets
, чтобы прочитать все данные.
Как предлагается в ряде комментариев (например, Fe2O3 и Lundin), вы можете увидеть это, если измените оператор печати, чтобы он включал какой-то разделитель. Например (из Лундина):
printf("|%s|",s);
Это прояснит, что именно вы получили от отдельных звонков fgets
.
Выход правильный. Он читает не более 1 символа или новой строки \n. Он не отбрасывает другие символы, он читает их при следующих вызовах в цикле.