6

What makes getch inherently unportable to be included as a standard C function?

It is so intuitive and elegant for console interfaces. Without it, asking for a single character is always misleading because users are allowed to enter more than one key.

Worse, you are often required to make sure to clear standard input after reading console input, which isn't even provided as a standard feature! I have to use my own!

A simple program which could be:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> 
#include <conio.h>

int main(int argc, char **argv)
{   
    while (1) {
        char yes_or_no;        

        printf("is this statement correct? 1 + 1 = 2(Y/N) $ ");
        yes_or_no = tolower(getch());

        switch (yes_or_no) {
            case 'y':
                puts("Right!");     
                goto done;

            case 'n':
                puts("\nhint> Please just say yes for the sake of this demo...");
                break;

            case 'q':
                puts("\nExitting.");
                goto done;

            case EOF:
                puts("\nEOF.");
                goto done;

            default:
                printf("\nunknown response '%c'.\n", yes_or_no);
                break;
        }
    }


done:
    return 0;
}

becomes:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> 

static inline void flush_stdin()
{
    char ch;

    do {
        ch = getchar();
    } while ((ch != '\n') && (ch != EOF));
}

int main(int argc, char **argv)
{   
    while (1) {
        char yes_or_no;        

        printf("is this statement correct? 1 + 1 = 2(Y/N) $ ");
        yes_or_no = tolower(getchar());


        switch (yes_or_no) {
            case 'y':
                puts("Right!");     
                goto done;

            case 'n':
                puts("hint> Please just say yes for the sake of this demo...");
                break;

            case EOF:
                puts("EOF.");
                goto done;

            default:
                printf("unknown response '%c'.\n", yes_or_no);
                break;
        }
        flush_stdin(); /* remove this to see the difference */        
    }


done:
    return 0;
}

Every time I want to make a simple portable console program, I feel forced into making a bunch of functions like that, and still end up not having all the things I want, like getch.

Sure, you could use curses, but curses takes over your whole console, and makes your program behave differently than what a regular user expects(for the program to just scroll, and still show the command you ran the program with in the console buffer).

I know "why" is a bad question(what should always be prefered), but it must be asked. Is there anything inherently unportable in getch that a desktop system can't support? If there isn't, I can just write my own and port it to all the platforms I want to support and I'm good.

If there is something getch does that can't be supported by a desktop system, what is it? So I have better reason for understanding why conio is avoided than "just use ncurses/conio is not standard".

Dmytro
  • 5,068
  • 4
  • 39
  • 50
  • 2
    Btw, `ch` should be of type `int`. – pzaenger Apr 12 '16 at 19:18
  • If you try avoiding mixing formatted I/O (`scanf`/`printf`) and unformatted I/O (`getchar`/`putchar`), you don't even need that. – edmz Apr 12 '16 at 19:29
  • @pzaenger i know, but i made it char to avoid comparing it to EOF making it invalid because it's going to compare the whole 4 bytes instead of just 1 byte, and adding an additional cast. It's a common mistake. -black: and that detail is not obvious and causes subtle bugs that could be avoided with more user friendly library design considerations. – Dmytro Apr 12 '16 at 19:31
  • See? Just `scanf("%c", &yes_or_no)` and you won't need to handle that anymore; it also tells you about EOF. Just a note. – edmz Apr 12 '16 at 19:41
  • @black you are right, this is a contrived example avoiding scanf because of it being heavily discouraged. It is an elegant solution if used correctly(as you demonstrate), but unfortunately still allows you to enter more characters than you ask for. – Dmytro Apr 12 '16 at 19:49

1 Answers1

5

The C standard has no notion of terminals or Windows. Anything pertaining to these things is implementation specific and cannot be part of the C programming language.

However, there are industry standards for terminal programming. One is part of POSIX (IEEE 1003.1) as the termios terminal driver interface, one is X/Open curses which specifies a library of C functions to manipulate the terminal on a higher level and the third is ISO 6429, the set of ANSI terminal escape sequences.

Incidentally, X/Open curses provides a getch() function. Microsoft Windows also supports these standards but not in a useful way.

The way you would use getch() on Windows (i.e. via conio.h) is not portable though as conio.h is a DOS specific header that cannot easily be implemented on other platforms due to the different model of how the console works in DOS vs. terminals on other platforms.

Jens
  • 69,818
  • 15
  • 125
  • 179
fuz
  • 88,405
  • 25
  • 200
  • 352
  • I completely forgot about terminios, i need to check it out. Thanks for reminding me. – Dmytro Apr 12 '16 at 19:33
  • but do you really need a notion of terminal/window to turn off eaco for one character, read one character, and return it? Does standard io have to be so high level that it has no way of detecting keypresses and MUST operate on complete string inputs exclusively? Is this the only way to make things portable? – Dmytro Apr 12 '16 at 19:40
  • @Dmitry You could open a new question about it (there would be a bit to say). But, in short, the answer is yes, the standard does not care about that; §1.2 N1570 _"This International Standard does not specify: [...] the mechanism by which input data are transformed for use by a C program;"_ – edmz Apr 12 '16 at 19:46
  • I get the standard doesn't, that's why I'm asking whether it's an arbitrary design decision/restricted by inertia/cost of change, or there are inherent technical limitations from allowing that, systems that literally cannot support this. – Dmytro Apr 12 '16 at 19:47
  • @Dmitry C's first goal is portability and simplicity. If you think about it, that's the only true way to have it running anywhere. – edmz Apr 12 '16 at 20:01
  • @black that's why I am asking what makes getch not simple and not portable? – Dmytro Apr 12 '16 at 20:28
  • @Dmitry You can't turn off echo in a file, pipe, or socket. That's something specific to the notion of a terminal. Introducing such a function without introducting the concept of a terminal is a futile endeavour. Also, not all systems have terminals. For example, Plan 9 doesn't. Mandating terminal functionality would make C impossible to use on Plan 9. – fuz Apr 12 '16 at 20:32
  • Is there no way to introduce a universal notion of a terminal that all systems having a terminal support? I can't think of a modern operating system without a notion of terminal on which writing programs including stdio.c makes sense. This implementation could just switch between the two modes seamlessly. Right now programming in pure standard C(no libc/libs) feels as though old COBOL programs had more terminal functionality than modern standalone C programs, when I read old programming books, I get feature envy. – Dmytro Apr 12 '16 at 20:34
  • @Dmitry Your mistake is thinking that C needs an operating system. My microcontroller has no terminals either. Plan 9 has no terminals, just character streams. If you want to program terminals, use the industry standard POSIX and curses to do so. They are supported wherever there are terminals. There is a curses port for Windows, too even though Windows' POSIX support is really shitty. – fuz Apr 12 '16 at 20:48
  • @FUZxxl it doesn't, but is stdio used on microcontrollers? If stdout is used on a microcontroller, what does it typically mean? I can imagine it being used but it just doesn't seem to make sense to me. What harm would there be in providing a standard way to deal with "terminals" on systems that have terminals, which are typically(perhaps not always), the same ones including stdio functionality. I understand you can use stdio to manage files on high level, but otherwise both stderr and stdin/stdout make little sense unless you redirect them. – Dmytro Apr 12 '16 at 20:53
  • @Dmitry stdio is not required on freestanding platforms, but many microcontrollers have hosted platforms as well (under some embedded OS) and many of these don't have the conept of a terminal. There is a standard way to deal with terminals, but just like all low-level APIs, it's part of POSIX instead of C. – fuz Apr 12 '16 at 21:08
  • @Dmitry Blame Microsoft for not adopting POSIX in a useful way. They even sat in the committee! – fuz Apr 12 '16 at 21:08
  • @FUZxxl so the scope of POSIX is very minimal and exclusive to devices on which running C programs make sense? Anything beyond that is up the users/other standards to manage? – Dmytro Apr 12 '16 at 21:12
  • @Dmitry The purpose of C is to standardize a programming language, independently of where it runs. Thus, the C standard describes an environment that is present *everywhere* (even on 1960 era mainframes) and has tons of undefined behaviour. POSIX specifies a portable interface to a modern operating system and is farily rich in what it describes. There is a good reason for this split and terminals definitely belong into POSIX, not C. – fuz Apr 12 '16 at 21:21
  • I don't find this reasoning persuasive, as the C and C++ standards venture into this territory in general with stdio and iostreams (and locales and execution character sets and ...). I agree that standardizing all terminal behavior in the language standards is probably inappropriate, but providing a standard for just `getchar` for hosted environments as part of standard i/o seems pretty plausible. I suspect the issue is that nobody has bothered to write the proposal for the standards committees, perhaps because the need isn't that great. – Adrian McCarthy Apr 12 '16 at 22:49