skip to Main Content

i am making a counting program, and i want to add a feature that quits the program, but i don’t really know how to do it. i have seen examples on how to do it, but all of them are for windows. please help

os is linux debian

i tried adding "i = getchar();" and then added an if: "if (i==1){break;}" but nothing happened. i was expecting it to get a value of 1 and then exit.

code:

#include <stdio.h>
//#include <threads.h>
#include <time.h>
#include <unistd.h> 

int main() {
    start:
    struct timespec duration;
    struct timespec remaining; 
    int time, num;  

    duration.tv_sec = 1;    
    duration.tv_nsec = 0;   

    time = 0;
    num = 0;
    
    while(time <= 32767)
    {
      int result = nanosleep(&duration, &remaining);
      num++; //adds 1 to the number
      if (result == 0) 
      {
          printf("Seconds Passed: ");
          printf("%dn", num); //prints the number
          time++; //adds 1 to the time
      }
      else if (result == -1)
      {
         printf("Error with nanosleepn");
         goto start;
      }
    }
    getchar();
    return 0;
}

2

Answers


  1. You want to do two things at the same time:

    • run a loop with sleep printing seconds
    • wait for q

    There are generally two popular ways to implement multitasking:

    • multithreading/multiprocessing
    • event loop

    Multithreading using standard C11 threads might look like the following:

    #include <signal.h>
    #include <stdatomic.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <threads.h>
    #include <unistd.h>
    
    int printer(void *arg) {
      // prints seconds until shoudlexit is set
      atomic_bool *shouldexit = arg;
      for (unsigned time = 0; time <= 32767; time++) {
        printf("Seconds Passed: %dn", time);
        sleep(1);
        if (atomic_load(shouldexit)) {
          break;
        }
      }
      return 0;
    }
    
    void sigusr1_handler(int ignore) {}
    
    int main() {
      atomic_bool shouldexit = ATOMIC_VAR_INIT(false);
      thrd_t thr;
      thrd_create(&thr, printer, &shouldexit);
      // wait for q or end of stream
      for (int c; (c = getchar()) != EOF;) {
        if (c == 'q') {
          break;
        }
      }
      printf("Exiting...n");
      atomic_store(&shouldexit, true);
      // send yourself a signal to break sleep mid sleep.
      signal(SIGUSR1, sigusr1_handler);
      raise(SIGUSR1);
      thrd_join(thr, 0);
    }
    

    While an event loop might look like this:

    #include <poll.h>
    #include <stdio.h>
    #include <unistd.h>
    int main() {
      struct pollfd fds[] = {{.fd = STDIN_FILENO, .events = POLLIN}};
      int time = 0;
      while (1) {
        int r = poll(fds, sizeof(fds) / sizeof(*fds), 1000);
        if (r == 0) {
          printf("Seconds Passed: %dn", time);
          time++;
        } else {
          int c = getchar();
          if (c == EOF || c == 'q') {
            printf("Exiting...n");
            break;
          }
        }
      }
    }
    

    You might be interested in: man pthread_create man poll man signal man 7 signal man sleep man pipe https://en.cppreference.com/w/c/thread .

    You might also be interested in How do you do non-blocking console I/O on Linux in C? to read q without having to press enter.

    The way you used goto in your code smells for me. Do not use goto (that way). You might be interested in https://en.wikipedia.org/wiki/Goto#Criticism . The kernel coding style https://www.kernel.org/doc/html/v4.18/process/coding-style.html#centralized-exiting-of-functions shows the only acceptable way to use goto. In the case of your presented code, you could have just used a normal loop.

    Login or Signup to reply.
  2. Here’s another approach.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    
    int main() {
      int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
      fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
    
      while (1) {
        // Do some work here
        printf("Doing some work...n");
        sleep(1);  // Simulate work
    
        // Check for input
        char c;
        ssize_t result = read(STDIN_FILENO, &c, 1);
    
        if (result > 0) {
          printf("Received input: %c", c);
        } else if (result == -1 && errno != EAGAIN) {
          perror("read");
          exit(1);
        }
        // If result is -1 and errno is EAGAIN, it means no data was available
      }
    
      return 0;
    }
    

    This uses the underlying file descriptor for standard in and sets it to non-blocking. This prevents the read from waiting for input.

    Note that this uses Unix-style calls, which are POSIX compliant, but not standard C library. Thus, this cannot easily be run as-is on Windows.

    Also, this solution (as with KamilCuk’s solution) requires you to press return for your "q" to be read. If you want single-keystroke recognition, then you need to put the terminal driver into a special mode. I advise against doing that, partly due to the added complexity. Also, if your program exits prematurely without cleaning up, it will leave the terminal driver in the special mode, which you will not find usable. (There are things you can to do protect against even that, but it adds even more complexity.)

    One more thing. KamilCuk’s solution reacts immediately to the keystroke. Mine waits till the loop gets to the read(). This might be an advantage or a disadvantage, depending on your needs.

    So three solutions. The right one depends on the details of your overall design. Mine is arguably the less complex, but also the least flexible, depending on what else is going on in your program.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search