TL;DR: use sigaction()
, not signal()
, to install signal handlers.
As comments and other answers have observed, your signal handler has undefined behavior as a result of its call to printf()
. In the event that the signal handler is ever triggered, that gives the whole program UB, which makes it very difficult to reason about its observed behavior.
But consider this variation instead:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_handler(int signum){
static const char msg[] = "\nInside handler function\n";
write(1, msg, sizeof(msg) - 1);
}
int main(){
signal(SIGINT,sig_handler); // Register signal handler
for(int i=1;;i++){ //Infinite loop
printf("%d : Inside main function\n",i);
sleep(1); // Delay for 1 second
}
return 0;
}
I have switched from printf
to write
inside the signal handler, thereby removing the UB. And if I compile it with
gcc -std=c11 htest.c -o htest
then the resulting executable still exhibits the behavior you describe: the first Ctrl-C is handled, but the second is not.
HOWEVER, if I instead compile with
gcc htest.c -o htest
then the resulting program intercepts every Ctrl-C I type, as I guess you were expecting. So what's going on?
The problem revolves around the fact that the details of the behavior of the signal()
function have varied historically. For more detail, see the portability notes in the signal
(2
) manual page, but here's a brief rundown:
In the original System V UNIX, the custom signal handlers installed via signal()
were one-shots: when such a handler was triggered, the disposition for the signal was reset to its default, and the signal was not blocked during execution of the handler.
The System V behavior has some issues, so BSD implemented signal()
differently in these respects: signal disposition is not automatically reset, and the signal is blocked during execution of the handler. And additionally, in BSD, certain blocking system calls are automatically restarted if interrupted by a signal that does not result in the program terminating.
These differences mean that the only portable uses for the signal()
function are for setting signal disposition to SIG_DFL
or SIG_IGN
.
Glibc supports both alternatives, and which one you get is controlled by feature test macros, which can be influenced by compiler command-line options. It defaults to BSD semantics as long as the _DEFAULT_SOURCE
macro is defined (_BSD_SOURCE
prior to glibc 2.19). Gcc defines that macro by default, but some command line options, notably the strict-conformance -std
options, cause Gcc not to define it. And that's why I could get different behavior depending on how I compiled the program.
On POSIX systems, the solution is to use the sigaction()
function instead of signal()
to register signal handlers. This function has well-defined semantics for all the areas of behavioral difference described above, including a well-defined means to choose among them. However, to get its declaration included in all cases, you will need to ensure that a different feature-test macro is defined. For example:
// Manipulation of feature-test macros should precede all header inclusions
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 1
#endif
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int signum) {
static const char msg[] = "\nInside handler function\n";
write(1, msg, sizeof(msg) - 1);
}
int main(void) {
// Register signal handler
struct sigaction sa = { .sa_handler = sig_handler /* default sa_mask and sa_flags */ };
sigaction(SIGINT, &sa, NULL);
while (int i = 1; ; i++) {
printf("%d : Inside main function\n",i);
sleep(1);
}
return 0;
}
That will reliably get you a handler that is not reset when triggered, does have SIGINT
blocked while it is being handled, and does not automatically restart system calls interrupted by the signal. That will prevent the program from being killed via a SIGINT
, though there are other ways it can be killed, such as via a SIGKILL
(which cannot be blocked or handled).