Прерывание потока программы в C

У меня работает цикл, который увеличивает значение переменной на каждой итерации, и я хочу иметь возможность нажимать клавишу на клавиатуре, чтобы остановить цикл и сообщить окончательное значение переменной. Дело в том, что я не могу понять, как сделать это в C. Я чувствую себя глупо, потому что мне кажется, что я упускаю из виду какой-то очень простой и легкий способ сделать это, но все, что я пытаюсь сделать, останавливает цикл, пока не нажму клавишу клавиатура, которая по сути является полной противоположностью того, что я хочу.

По сути, я хочу сделать что-то вроде этого:


   while (key is not pressed)
increment value
print final value

Имеет ли это смысл? В любом случае, какие-либо советы о том, как сделать это в C?

11 ответов

Если вы пытаетесь читать по одному символу за раз (не нажимая Enter) с терминала в Linux, вам нужно будет установить терминал для небуферизованного ввода.

Смотрите этот пример:

Не буферизованный getc(3) под GNU/Linux

Я думаю, что нет тривиального решения, взгляните на этот связанный вопрос

обнаружить событие клавиатуры в C

Если вы работаете в 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);
}

Вы можете передавать сигналы между программами, так что на самом деле вы можете разветвить программу, которая выполняет "работу", и тогда вторая программа будет лениво контролировать клавиатуру - и, как только будет нажата клавиша, она отправит сигнал первый, который напечатал бы результат.

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