Terminal Special Characters VEOF, VINTR, VKILL, VSUSP

 

Terminal Special Characters
Understanding the c_cc array โ€” VEOF, VINTR, VKILL, VSUSP and all special characters | Chapter 62
โŒจ๏ธ Special Keys
๐Ÿ“‹ c_cc Array
๐ŸŽฏ Interview Q&A

What Are Terminal Special Characters?

When you press Ctrl+C at the terminal and a program gets interrupted, or when you press Ctrl+D and the shell sees “end of file”, those keys are called terminal special characters.

The terminal driver intercepts these characters before your program ever sees them. Instead of passing the character to the program, the driver performs a specific action โ€” like sending a signal, discarding a line, or marking end-of-file.

These special characters are stored in the c_cc array inside the struct termios. Each slot in the array is indexed by a constant starting with the letter V (e.g., VEOF, VINTR, VKILL). You can read, change, or disable each one using tcgetattr()/tcsetattr() or the stty command.

Important: When the terminal driver processes a special character, it is normally discarded โ€” it does NOT get passed to your program. (Exceptions: CR, EOL, EOL2, NL are passed through.)

Key Terms
c_cc VEOF / EOF VINTR / INTR VKILL / KILL VSUSP / SUSP VQUIT / QUIT VERASE / ERASE VSTART / VSTOP VMIN / VTIME _PC_VDISABLE ICANON ISIG

1. The c_cc Array โ€” How Special Characters Are Stored

The c_cc field of struct termios is an array of bytes. Each byte holds the ASCII value of one special character. You index into the array using the V-prefixed constants (VEOF, VINTR, VERASE, etc.).

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

int main(void) {
    struct termios tp;

    if (tcgetattr(STDIN_FILENO, &tp) == -1) {
        perror("tcgetattr");
        return 1;
    }

    /* Print the current special character values */
    printf("EOF character  : ^%c (ASCII %d)\n",
           tp.c_cc[VEOF] + '@', tp.c_cc[VEOF]);
    printf("INTR character : ^%c (ASCII %d)\n",
           tp.c_cc[VINTR] + '@', tp.c_cc[VINTR]);
    printf("ERASE character: ^%c (ASCII %d)\n",
           tp.c_cc[VERASE] - 1 + 'A', tp.c_cc[VERASE]);
    printf("KILL character : ^%c (ASCII %d)\n",
           tp.c_cc[VKILL] + '@', tp.c_cc[VKILL]);
    printf("SUSP character : ^%c (ASCII %d)\n",
           tp.c_cc[VSUSP] + '@', tp.c_cc[VSUSP]);

    return 0;
}

c_cc array โ€” conceptual layout
c_cc[VEOF]
^D
c_cc[VINTR]
^C
c_cc[VQUIT]
^\
c_cc[VERASE]
^?
c_cc[VKILL]
^U
c_cc[VSUSP]
^Z
c_cc[VSTART]
^Q
c_cc[VSTOP]
^S
c_cc[VMIN]
1
c_cc[VTIME]
0
… more

2. Complete Table of Terminal Special Characters (Linux)

This covers all characters recognized by the Linux terminal driver.

Character c_cc Index Default Key What it does Relevant Flag(s)
CR none ^M (Enter) Carriage return โ€” can be mapped to NL via ICRNL ICANON, IGNCR, ICRNL, OPOST, OCRNL
NL none ^J Newline โ€” terminates a line in canonical mode ICANON, INLCR, ECHONL, OPOST, ONLCR
EOF VEOF ^D End-of-file โ€” causes read() to return 0 (EOF) ICANON
EOL VEOL undefined Alternate end-of-line terminator (in addition to NL) ICANON
EOL2 VEOL2 undefined Second alternate end-of-line terminator ICANON, IEXTEN
ERASE VERASE ^? (Backspace) Erase the last character typed on the current line ICANON
INTR VINTR ^C Sends SIGINT to the foreground process group ISIG
KILL VKILL ^U Erase the entire current input line ICANON
LNEXT VLNEXT ^V Literal-next โ€” treat the next character as a literal, not special ICANON, IEXTEN
QUIT VQUIT ^\ Sends SIGQUIT to the foreground process group (also generates core dump) ISIG
REPRINT VREPRINT ^R Reprint the current input line (redisplays what you have typed) ICANON, IEXTEN, ECHO
START VSTART ^Q Resume (start) output that was stopped with STOP character IXON, IXOFF
STOP VSTOP ^S Pause (stop) terminal output (XON/XOFF flow control) IXON, IXOFF
SUSP VSUSP ^Z Sends SIGTSTP โ€” suspends the foreground process (job control) ISIG
WERASE VWERASE ^W Erase the last word (not just character) typed on the current line ICANON, IEXTEN
DISCARD VDISCARD ^O Toggle discarding of output (not fully implemented on Linux) โ€”

3. Signal-Generating Characters โ€” INTR, QUIT, SUSP

Three special characters send signals to the foreground process group. They are controlled by the ISIG flag. If ISIG is cleared in c_lflag, these characters no longer generate signals โ€” they are passed to the reading process as data.

How signal characters work
^C
INTR
โ†’
Terminal driver sends SIGINT to entire foreground process group.
Default action: terminate the process.
^\
QUIT
โ†’
Terminal driver sends SIGQUIT to foreground process group.
Default action: terminate + generate core dump.
^Z
SUSP
โ†’
Terminal driver sends SIGTSTP to foreground process group.
Default action: suspend (stop) the process. Resume with fg or SIGCONT.
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/* Disable signal generation from INTR, QUIT, SUSP keys */
int main(void) {
    struct termios tp;

    if (tcgetattr(STDIN_FILENO, &tp) == -1) {
        perror("tcgetattr");
        return 1;
    }

    /* Clear ISIG flag โ€” ^C, ^\, ^Z now pass as raw bytes instead of signals */
    tp.c_lflag &= ~ISIG;

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1) {
        perror("tcsetattr");
        return 1;
    }

    printf("Signals disabled. ^C is now just a byte.\n");
    printf("Press ^C (won't kill): ");
    fflush(stdout);

    int ch = getchar();
    printf("Got character with ASCII value: %d\n", ch);

    /* Restore */
    tp.c_lflag |= ISIG;
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp);

    return 0;
}

4. Disabling a Special Character โ€” _PC_VDISABLE

You can disable any special character by setting its c_cc slot to the value returned by:

fpathconf(fd, _PC_VDISABLE)

On most Linux/UNIX systems, this returns 0. Setting a c_cc slot to 0 (or more precisely to _POSIX_VDISABLE) means that character is completely disabled โ€” pressing the corresponding key has no special meaning at all; it passes through to the reading process as raw data.

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

int main(void) {
    struct termios tp;
    long vdisable;

    /* Get the disable value for this terminal */
    vdisable = fpathconf(STDIN_FILENO, _PC_VDISABLE);
    if (vdisable == -1) {
        perror("fpathconf");
        return 1;
    }
    printf("VDISABLE value: %ld\n", vdisable);

    if (tcgetattr(STDIN_FILENO, &tp) == -1) {
        perror("tcgetattr");
        return 1;
    }

    /* Disable the EOF character (^D will no longer mean EOF) */
    tp.c_cc[VEOF] = (cc_t) vdisable;

    /* Disable the INTR character (^C won't send SIGINT) */
    tp.c_cc[VINTR] = (cc_t) vdisable;

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1) {
        perror("tcsetattr");
        return 1;
    }

    printf("EOF and INTR characters disabled.\n");

    /* Don't forget to restore before exit! */
    /* ... restore code ... */

    return 0;
}

5. Changing a Special Character Programmatically

You can reassign any special character to a different key by writing a new value into c_cc:

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

int main(void) {
    struct termios tp;

    if (tcgetattr(STDIN_FILENO, &tp) == -1) {
        perror("tcgetattr");
        return 1;
    }

    /* Change EOF from ^D (ASCII 4) to ^E (ASCII 5) */
    tp.c_cc[VEOF] = 5;  /* Ctrl+E = ASCII 5 */

    /* Change INTR from ^C (ASCII 3) to ^L (ASCII 12) */
    tp.c_cc[VINTR] = 12; /* Ctrl+L = ASCII 12 */

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1) {
        perror("tcsetattr");
        return 1;
    }

    printf("EOF is now ^E, INTR is now ^L\n");

    /* ... restore before exit ... */

    return 0;
}

6. CR and NL โ€” Why They Have No c_cc Subscripts

The table shows that CR (carriage return, ^M) and NL (newline, ^J) do NOT have c_cc subscripts. This is because their values are fixed by the ASCII standard and cannot be changed.

However, their behavior can still be controlled through flags:

Flag Effect
ICRNL (c_iflag) Map CR to NL on input (default ON โ€” this is why Enter works as a newline)
IGNCR (c_iflag) Ignore CR on input
INLCR (c_iflag) Map NL to CR on input
ONLCR (c_oflag) Map NL to CR+NL on output (default ON โ€” ensures cursor moves to column 0)
OCRNL (c_oflag) Map CR to NL on output

7. VMIN and VTIME โ€” Noncanonical Mode Parameters

Two special slots in c_cc, VMIN and VTIME, are not character values. Instead, they control how reads behave in noncanonical mode.

In the stty -a output you saw: min = 1; time = 0;

Parameter Meaning
VMIN Minimum number of characters that must be available before read() returns
VTIME Timeout in tenths of a second (0 = no timeout, wait indefinitely for VMIN chars)

These are covered in detail in the noncanonical mode section (Chapter 62.6.2). The key point here is that they live in c_cc but serve a completely different purpose from the other entries.

๐ŸŽฏ Interview Questions & Answers

Q1. What is the c_cc array in struct termios? How many entries does it have?

c_cc is an array of bytes inside struct termios that stores the values of all terminal special characters. Each slot is indexed by a V-prefixed constant (VEOF, VINTR, VERASE, etc.). On Linux, the array has NCCS entries (typically 19 or more). Two special entries, VMIN and VTIME, are used for noncanonical mode control, not as character values.

Q2. What is the difference between INTR (^C), QUIT (^\), and SUSP (^Z)?

All three send signals to the foreground process group, but different ones. INTR sends SIGINT (default: terminate). QUIT sends SIGQUIT (default: terminate + core dump). SUSP sends SIGTSTP (default: suspend the process, which can be resumed later with fg or SIGCONT). All three require the ISIG flag to be set; clearing ISIG disables signal generation for all three.

Q3. What is the difference between ERASE (^?) and KILL (^U)?

ERASE erases only the last character typed in the current input line. KILL erases the entire current input line. Both require ICANON to be enabled. A separate WERASE (^W) erases the last word typed.

Q4. What do START (^Q) and STOP (^S) do?

These implement XON/XOFF software flow control. STOP (^S) pauses terminal output. START (^Q) resumes it. They are controlled by the IXON and IXOFF flags. This is why you might accidentally freeze your terminal by pressing Ctrl+S โ€” press Ctrl+Q to unfreeze.

Q5. How do you disable a terminal special character from C code?

Set the relevant c_cc slot to the value returned by fpathconf(fd, _PC_VDISABLE), which is typically 0 (also defined as _POSIX_VDISABLE). After calling tcsetattr(), pressing that key will no longer trigger the special behavior and will instead pass the character to the reading process as data.

Q6. Why do CR and NL not have c_cc subscripts unlike other special characters?

CR and NL have fixed ASCII values (13 and 10) that cannot be changed. Because you cannot reassign these characters, there is no c_cc slot for them. Their behavior is controlled indirectly through flags like ICRNL (map CR to NL on input) and ONLCR (map NL to CR+NL on output).

Q7. What is LNEXT (^V) used for?

LNEXT (literal-next) tells the terminal driver to treat the very next character as a literal data character, ignoring any special meaning it may have. For example, if you type ^V followed by ^C, the ^C is passed through as the raw ASCII byte 3 (not as SIGINT). This is how stty lets you type special characters as arguments: stty intr ^V ^L.

Q8. What flag must be set for INTR, QUIT, and SUSP to work?

The ISIG flag in c_lflag must be set. When ISIG is enabled, pressing INTR/QUIT/SUSP sends the corresponding signal. When ISIG is cleared (tp.c_lflag &= ~ISIG), these keys are treated as ordinary data characters โ€” no signals are generated. This is useful in raw-mode applications like text editors (vim, nano) that handle ^C themselves.

You’ve Covered the Core of Chapter 62!

You now understand how to read/write terminal attributes in C, use stty from the shell, and master the c_cc special character array.

โ† Previous: stty Command Back to Home

Leave a Reply

Your email address will not be published. Required fields are marked *