Прерывание потока программы в C
У меня работает цикл, который увеличивает значение переменной на каждой итерации, и я хочу иметь возможность нажимать клавишу на клавиатуре, чтобы остановить цикл и сообщить окончательное значение переменной. Дело в том, что я не могу понять, как сделать это в C. Я чувствую себя глупо, потому что мне кажется, что я упускаю из виду какой-то очень простой и легкий способ сделать это, но все, что я пытаюсь сделать, останавливает цикл, пока не нажму клавишу клавиатура, которая по сути является полной противоположностью того, что я хочу.
По сути, я хочу сделать что-то вроде этого:
while (key is not pressed)
increment value
print final value
Имеет ли это смысл? В любом случае, какие-либо советы о том, как сделать это в C?
11 ответов
Если вы пытаетесь читать по одному символу за раз (не нажимая Enter) с терминала в Linux, вам нужно будет установить терминал для небуферизованного ввода.
Смотрите этот пример:
Я думаю, что нет тривиального решения, взгляните на этот связанный вопрос
Если вы работаете в Windows и используете MSVC, вам могут понадобиться getch() и kbhit(), что-то вроде этого
#include <conio.h>
while( looping ) {
// do regular loop stuff
// check if a key is hit, w/o blocking, using kbhit()
if( kbhit() ) {
// only runs when user has hit a key
// so display stuff here,
// and wait for permission to resume with getch()
getch();
}
}
Если вам разрешено указывать, что нажимается, и вы работаете в POSIX.1-совместимой системе, вы можете настроить обработчик сигнала для перехвата SIGINT (отправляется с помощью Ctrl+C). Попросите ваш обработчик изменить значение переменной так, чтобы вы выпали из цикла while.
Если вы выбираете этот подход, будьте осторожны. Если неправильная реализация приводит к бесконечному циклу и перехвату SIGINT, вы не сможете завершить свою программу с помощью Ctrl+C. В этом случае вам нужно будет использовать kill(1) для завершения вашей программы.
Это единственное место, где стандарт C оставляет программистов в погоне за сушкой. Наиболее переносимым решением этой проблемы является выполнение ввода-вывода с использованием curses
библиотека, которая обрабатывает так называемый "сырой" ввод с клавиатуры (что вам нужно) и многое другое. Кривая обучения немного крутая, но есть хорошие учебники, особенно в документации программистов BSD.
Это зависит от вашей платформы. Язык C не определяет такие вещи, как это.
Окна? линукс? (gnome app? kde app? терминал?) что-то еще?
Некоторые люди упоминали о блокировке. В средах *nix вы можете попробовать установить стандартный дескриптор входного файла в неблокирующий режим. Вот пример:
/* testbrk
Test breaking an infinite loop with a keystroke.
Hit the <Enter> key to break this loop.
*/
/* Include block */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/*---testbrk main loop---*/
int main ( int argc , char ** argv )
{
/* Variables: file flags , return value , buffer */
int f ;
ssize_t r ;
char b ;
/*
Switch standard input to non-blocking mode:
-Get existing flags from the file descriptor
-Bitwise OR to activate non-blocking bit
-Apply new flags
*/
if ( f = fcntl( STDIN_FILENO , F_GETFL , 0 ) == -1 )
{
perror ( "fcntl F_GETFL" ) ;
exit ( EXIT_FAILURE ) ;
}
f |= O_NONBLOCK ;
if ( f = fcntl( STDIN_FILENO , F_SETFL , f ) == -1 )
{
perror ( "fcntl F_SETFL" ) ;
exit ( EXIT_FAILURE ) ;
}
/*
Infinite loop
-try reading from standard input directly via its file
descriptor , NOT the standard input stream
-if the number of bytes returned is > zero then break
-if bytes returned is -1 then check errno for EAGAIN,
meaning that read would block but the file is nonblocking
-if zero is returned then print message immediately
*/
while ( (r = read( STDIN_FILENO , &b , sizeof( b ) )) < 1 )
{
if ( r == -1 && errno != EAGAIN )
{
perror ( "read" ) ;
exit ( EXIT_FAILURE ) ;
}
fprintf ( stderr , "\nInfinite loop" ) ;
} /* inf loop */
/* Done */
printf ( "\n\nBroken loop\n\n" ) ;
exit ( EXIT_SUCCESS ) ;
} /* testbrk */
В зависимости от системы и того, как она работает, вы можете попытаться использовать функцию выбора со STDIN в качестве дескриптора файла. Вы можете установить время в операторе выбора на ноль для опроса, чтобы увидеть, есть ли данные, или установить время ожидания.
Вы можете посмотреть на ссылку http://www.gnu.org/s/libc/manual/html_node/Waiting-for-I_002fO.html для примера использования оператора select с сокетами.
Я изменил этот пример, чтобы использовать STDIN в качестве дескриптора файла. Функция вернет 0, если нет ожидающего ввода, 1, если есть ожидающий ввод (т.е. кто-то нажал клавишу на клавиатуре ввода), или -1, если произошла ошибка какой-либо природы
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
int waitForKBInput(unsigned int seconds)
{
/* File descriptor set on which to wait */
fd_set set;
/* time structure which indicate the amount of time to
wait. 0 will perform a poll */
struct timeval timeout;
/* Initialize the file descriptor set. */
FD_ZERO (&set);
/* Use the Standard Input as the descriptor on which
to wait */
FD_SET (STDIN, &set);
/* Initialize the timeout data structure. */
timeout.tv_sec = seconds;
timeout.tv_usec = 0;
/* select returns 0 if timeout, 1 if input available, -1 if error. */
/* and is only waiting on the input selection */
return select (FD_SETSIZE,
&set, NULL, NULL,
&timeout));
}
Я знаю, что это не будет работать в системе VMS, так как я попробовал это, и они реализовали Select и STDIN по-разному, чтобы он не работал (пришлось использовать другие средства для обнаружения ввода с клавиатуры).
Для Visual C/C++ можно было бы использовать функцию kbhit, которая указала бы, есть ли ввод с клавиатуры для чтения.
Функция, которую вы вызываете для чтения символа, блокируется. Если там нет персонажа, вы хотите вернуть его.
Волшебные условия поиска здесь - неблокируемый и небуферизованный ввод.
Зачем считать в цикле, пока кто-нибудь не нажмет клавишу?
Вы, если вы действительно хотите что-то вроде, чем проверить время для примерно 10k итераций. Затем сделайте ожидание в не блокирующем итерации со сном (). Когда вы получите время, вы можете использовать его, чтобы приблизиться к тому, что является "окончательным значением"
Я попытаюсь взглянуть не на дословный вопрос, который вы задаете, а на ваше намерение - очевидно, вы хотите представить результат вычислений, вызванный какой-то реакцией пользователя.
Проверка "нажата ли клавиша?" на каждой итерации достаточно расточительно - ваш процессор может тратить все это время на выполнение более полезных задач.
Итак, лучший подход здесь - это использование сигналов, на мой взгляд, а именно, SIGINT, который срабатывает, когда вы нажимаете "Ctrl-C". Вот код, который напечатает значение переменной, когда вы нажмете Ctrl-C, и выйдет после того, как вы сделаете это три раза:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
long globalvar = 0;
int interrupts_before_exit = 3;
void ctrl_c_handler(int x) {
printf("Value of the variable: %ld\n", globalvar);
if(--interrupts_before_exit) {
printf("Press Ctrl-C %d more times to stop\n", interrupts_before_exit);
} else {
printf("Computation interrupted!\n");
exit(0);
}
}
int main(int argc, char *argv[]) {
struct sigaction act;
act.sa_handler = ctrl_c_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGINT, &act, NULL) >= 0) {
while (1) {
/* The work happens here */
globalvar++;
}
}
exit(1);
}
Вы можете передавать сигналы между программами, так что на самом деле вы можете разветвить программу, которая выполняет "работу", и тогда вторая программа будет лениво контролировать клавиатуру - и, как только будет нажата клавиша, она отправит сигнал первый, который напечатал бы результат.