Чтение заблокировано getc

Для следующего кода:

#include <unistd.h>
#include <stdio.h>

int main()
{
    char buf[6];

    printf("%c\n", (char)getc(stdin));
    int ret = read(STDIN_FILENO, buf, 5);
    printf("%d\n", ret);
    printf("%c\n", (char)getc(stdin));
}

При использовании «abcdef» в качестве входных данных система выводит

a
0
b

Я думаю, это означает, что в данном случае read ничего не может получить. Однако при закомментировании первого вызова getc система выводит

5
f

Я думаю, это означает, что read можно что-то получить.

Мой вопрос: как избежать блокировки read от getc? Кстати: вы можете воспроизвести поведение из https://godbolt.org/z/EaPTMWPz9

Что ж, это одна из причин, по которой не следует смешивать методы ввода/вывода из/в один и тот же поток. Есть ли для этого практическая причина?

Eugene Sh. 26.07.2024 19:14

Единственный способ предотвратить блокировку getc при чтении — либо предоставить данные в канале, либо закрыть канал. (Или вызвать ошибку чтения, или пометить fd как неблокирующий). Но я не думаю, что это действительно вопрос. Поведение, которое вы видите, ожидаемо и разумно; Что именно ты пытаешься сделать? Есть ли что-то в наблюдаемом поведении, что сбивает с толку?

William Pursell 26.07.2024 19:26

Я понял, что они не означают «заблокировано», как «блокировка ввода-вывода», меня это тоже смутило. Они просто спрашивают, почему read не получает ожидаемых данных.

Useless 26.07.2024 19:30

Не проблема, но read возвращает ssize_t, а не int. (При переключении вам также придется переключить спецификатор формата на %zd.)

ikegami 26.07.2024 20:43
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
87
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

getc ничего не блокирует. getc — буферизованный ввод. Сначала он считывает всю строку из STDIN_FILENO в буфер, связанный с stdin, а затем просто возвращает один символ из буфера каждый раз, когда вы его вызываете, пока он не станет пустым.

read — это функция POSIX, а не стандартная часть языка C, которая работает с базовыми дескрипторами файлов. Это непереносная деталь платформы. В этом случае он подключен через STDIN_FILENO ко входу (например, каналу, если вы запустили echo "abcdef" | ./a.out), в котором ничего не осталось, потому что getc уже прочитал все во внутренний буфер stdin.

Или

  • используйте буферизованный стандарт C, например fread(..., stdin) с fgetc(stdin), и позвольте ему управлять буферизацией за вас, ИЛИ
  • используйте POSIX или другой необработанный ввод-вывод, специфичный для платформы, и управляйте собственными буферами.

Только не смешивайте их для одного и того же потока, все происходит именно так, как вы наблюдали.


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

Что вы подразумеваете под словами «В данном случае он подключен к трубе…»? Вы имеете в виду конкретно, что godbolt осуществляет ввод через канал? Или вообще stdin реализован через канал?

Streve Ford 26.07.2024 19:55

По какой-то причине я предполагал, что OP работает echo "abcdef" | ./a.out или что-то подобное, но не могу вспомнить почему. Уточню.

Useless 26.07.2024 20:14

Согласно POSIX 2.5.1 Взаимодействие файловых дескрипторов и стандартных потоков ввода-вывода:

В этом разделе описывается взаимодействие файловых дескрипторов и стандартных потоков ввода-вывода. Функциональность, описанная в этом разделе, является расширением стандарта ISO C...

...

Результат вызовов функций, включающих любой один дескриптор («активный дескриптор»), определен в другом месте этого тома POSIX.1-2017, но если используются два или более дескрипторов, и любой из них является потоком, приложение должно убедитесь, что их действия скоординированы, как описано ниже. Если этого не сделать, результат не определен.

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

Ни одно из этих правил не предполагает чтение из одного и того же источника через дескриптор файла и поток FILE *.

Таким образом, неопределенное поведение вызывается, когда вы пытаетесь прочитать из того же базового источника через [p]read() в файловом дескрипторе, а также прочитать из того же источника через поток FILE *, например getc(stdin).

Я понимаю концепцию, лежащую в основе: «Это неопределенное поведение, поэтому не тратьте время на размышления, почему вы видите то, что видите — с такой же легкостью это может привести к тому, что единорог выпрыгнет из вашего экрана». Но объяснения в других ответах дают полезную информацию о том, как работают эти слои. Я думаю, что лучше всего сочетать эти два варианта: рассказать, что на самом деле происходит, а затем сказать, что не следует рассчитывать на такое поведение, поскольку оно не определено.

Streve Ford 26.07.2024 20:21

@StreveFord Не было смысла повторять другой ответ(ы). Понимание неопределенного поведения может быть интересным, но практически невозможно применить это понимание каким-либо надежным способом. А если оно ненадежно, то это хуже, чем бесполезно – это опасно. Тот факт, что единорог не выпрыгивает из вашего экрана, не означает, что вы имеете какой-либо контроль над тем, что происходит. If может работать нормально, и без изменений произойдет сбой при следующем запуске. Это ужасно низкий стандарт для написания кода.

Andrew Henle 26.07.2024 20:49
Ответ принят как подходящий

Это всего лишь вопрос о буферизации. Первый getc считывает из файла намного больше одного байта. Обратите внимание, что слово «файл» здесь неоднозначно, поскольку существует несколько уровней абстракции, но я не хочу беспокоиться о деталях.

По сути, чтение из файла происходит медленно, и система ускоряет процесс, выполняя медленные операции как можно реже. Когда вы вызываете getc в первый раз, система считывает кучу данных из базового потока, что-то вроде 4096 или 8192 байт, или какой-то разумный размер для платформы. Вызов read после getc возвращает 0, поскольку система уже прочитала все данные. Следующий getc возвращает данные из буфера, записанного первым getc.

Если вы вызовете read до вызова getc, данные не будут буферизованы и все еще доступны для чтения.

По сути, вызов read пытается обойти буферизацию, которую выполняет getc.

Мне нравится ваш ответ, потому что он объясняет, почему выполняется буферизованный ввод-вывод. Но вы конкретно не говорите, что вы не должны смешивать стандартный буферизованный ввод-вывод библиотеки c с вводом-выводом POSIX (чтение/запись). Ты должен это сказать. Кроме того, было бы полезно упомянуть, что, хотя стандартный ввод-вывод библиотеки C можно найти на многих платформах, отличных от Unix (например, Windows), POSIX не так распространен в операционных системах, отличных от Unix (например, Windows).

Streve Ford 26.07.2024 20:14

@StreveFord обычно ответы стараются не дублировать. Если в одном ответе уже говорилось об отказе от смешивания ввода-вывода, обычно не требуется дублировать эту информацию в каждом ответе. В противном случае все ответы на SO были бы просто копиями всех предыдущих ответов плюс какой-то новой мыслью.

David C. Rankin 27.07.2024 10:16

Другие вопросы по теме