Search code examples
c++cstdincinstdio

What's the equivalent of cin.ignore() in C?


I understand that C++ stream functions are built on top of C's stdio library.

What do I have to do in C to get the same result as cin.ignore(n)?
For example, should I use stdio function fseek(stdin, n, 0) or is there some other method that cin.ignore is using?


Solution

  • No, there is not. But let's look what happens behind the curtain called cin.ignore(). Let's take llvm libcxx sources, I find them faster to look through then gcc's.

    The extern istream cin; is in iostream, but it is initialized on application startup in iostream.cpp using statically allocated buffer and __stdoutbuf object constructed from the good' old' FILE * stdin:

    _ALIGNAS_TYPE (istream) char cin [sizeof(istream)];
    ios_base::Init::Init()  {
        istream* cin_ptr  = ::new(cin)  istream(::new(__cin)  __stdinbuf <char>(stdin) );
        ...
    

    The istream::ignore() function can be found in istraem. It's pretty simple, first we check if the user wants to clean all chars from the stream or just some of them (if (__n == numeric_limits<streamsize>::max())). Then the function calls this->rdbuf()->sbumpc() in a loop predefined amount of counts (or endless, in case __n is equal to numeric_limits<steramsize::max()). We can find sbumpc() to be a member of std::basic_streambuf, from cppreference:

    int_type sbumpc();
    Reads one character and advances the input sequence by one character.
    
    If the input sequence read position is not available, returns uflow(). Otherwise returns Traits::to_int_type(*gptr()).
    

    So we can simply deduce that this->rdbuf() returns handle to __stdinbuf<char>(stdin). In the cin::ignore function the call to __stdinbuf<char>(stdin)::sbumpc() is made that many times, as many characters we want to ignore. So let's go to sbumpc()! First let's take a look at streambuf:

    int_type sbumpc() {
        if (__ninp_ == __einp_)
            return uflow();
        return traits_type::to_int_type(*__ninp_++);
    }
    

    So if (__ninp_ == __einp_) is doing some internal buffering in streambuf object, not to call uflow() if there are already buffered characters in our buffer. __ninp__ pointer get's incremented after each read, that must be it. uflow() is overloaded by __stdinbuf : public basic_streambuf< .... >, from __std_stream:

    template <class _CharT>
    typename __stdinbuf<_CharT>::int_type
    __stdinbuf<_CharT>::uflow()
    {
        return __getchar(true);
    }
    

    Puff, let's go to __getchar and find out what the true parameter is. It's right below in __std_stream.
    It's a long function, with the main functionality, which takes care of some buffering. But we can spot the hearth of this function right away:

    template <class _CharT>
    typename __stdinbuf<_CharT>::int_type
    __stdinbuf<_CharT>::__getchar(bool __consume) {
        ....
            int __c = getc(__file_);
            if (__c == EOF)
                return traits_type::eof();
        ...
    }
    

    Let's go from the beginning:

    • cin is an istraem object and is initialized from __stdinbuf<char>(stdin)
    • istream::ignore() calls basic_streambuf::sbumpc() predefined number of times, probably on an object initalized using stdin
    • basic_streambuf::sbumpc() takes care of some bufering and calls basic_streambuf::uflow() if the buffer is empty.
    • basic_streambuf::uflow() is overloaded by __stdinbuf::uflos() and calls __stdinbuf::__getchar()
    • __sinbuf::__getchar() calls getc(__file__) so probably getc(stdin) to read one character from the stream

    To sumarize:

    void stdin_ignore(size_t n, int delim)
    {
        while (n--) {
            const int c = getc(stdin);
            if (c == EOF)
               break;
            if (delim != EOF && delim == c) {
               break;
        }
    }