As wisely mentioned in the comments, you should probably first learn to work with the rules instead of trying to break them
. However, we're here to answer the question, and that means to break the rules! Take into account that neither fflush()
, setbuf()
, or setvbuf()
would work here for different reasons.
First of all, at least four custom functions are required. One to create a "proxy buffer" relating to a file (called just after fopen()
), one to destroy it (called just before fclose()
, one to do the actual ungetting (replacement for ungetc()
, and one to to retrieve a char
from the file (replacement for fgetc()
. Unfortunately, this means that performing fscanf()
, fflush()
, etc... on the stream will yield you bad and ugly results. You would have to rewrite all of stdio
!
First of all, let's call all of our new stuff xtdio
("extended stdio
"), so, first of all comes xtdio.h
...
#ifndef __XTDIO_H__
#define __XTDIO_H__
#include <stdio.h>
typedef struct
{
FILE *file;
char *buffer;
size_t buffer_size;
size_t buffer_usage;
size_t buffer_tail_offset;
} XFILE;
/* I know this is not the best of API design, but I need to be
* compatible with stdio's API.
*/
XFILE *xwrap(FILE *file, size_t max_ungets);
void xunwrap(XFILE *xfile);
int xgetc(XFILE *xfile);
int xungetc(int ch, XFILE *xfile);
#endif
Then, in the interesting side of the fence, comes xtdio.c
...
#include <stdlib.h>
#include <stdio.h>
#include "xtdio.h"
/* Create a XFILE wrapper, along with its respective buffer
* of 'max_ungets' size, around 'file'.
*/
XFILE *xwrap(FILE *file, size_t max_ungets)
{
XFILE *xfile = malloc(sizeof(XFILE));
if(xfile == NULL)
return NULL;
xfile->file = file;
xfile->buffer = malloc(max_ungets);
if(xfile->buffer == NULL) {
free(xfile);
return NULL;
}
xfile->buffer_size = max_ungets;
xfile->buffer_usage = 0;
xfile->buffer_tail_offset = 0;
return xfile;
}
/* Undo what 'xwrap()' did.
*/
void xunwrap(XFILE *xfile)
{
free(xfile->buffer);
free(xfile);
}
/* Check if there's something in the XFILE's
* buffer, and return it. Otherwise, fallback
* onto 'fgetc()'.
*/
int xgetc(XFILE *xfile)
{
if(xfile->buffer_usage == 0)
return fgetc(xfile->file);
if(xfile->buffer_tail_offset == 0)
xfile->buffer_tail_offset = xfile->buffer_size - 1;
else
xfile->buffer_tail_offset--;
xfile->buffer_usage--;
return xfile->buffer[xfile->buffer_tail_offset];
}
/* Here's the interesting part! If there's room in the
* buffer, it puts 'ch' in its front. Otherwise, returns
* an error.
*/
int xungetc(int ch, XFILE *xfile)
{
if(xfile->buffer_usage == xfile->buffer_size)
return EOF; //TODO: Set errno or something
xfile->buffer[xfile->buffer_tail_offset++] = (char)ch;
xfile->buffer_tail_offset %= xfile->buffer_size;
xfile->buffer_usage++;
return ch;
}
The smallish xtdio
library will allow you to perform as many ungets as you pass to xwrap()
's parameter. Each XFILE
has a buffer with the ungotten characters. When you xgetc()
, it first checks if there's something on the buffer and retrieves it. Otherwise, it fallbacks to fgetc()
. Example usage case...
#include <stdio.h>
#include <string.h>
#include "xtdio.h"
int main()
{
const char *my_string = "I just ungot this same long string in standard and compliant C! No more one-char limits on ungetc()!\n";
const size_t my_string_len = strlen(my_string);
XFILE *xtdin = xwrap(stdin, my_string_len);
if(xtdin == NULL) {
perror("xwrap");
return 1;
}
for(size_t i = my_string_len; i != 0; i--)
xungetc(my_string[i - 1], xtdin);
int ch;
while((ch = xgetc(xtdin)) != EOF)
putchar(ch);
xunwrap(xtdin);
return 0;
}
xtdio
can be further improved, by adding things such as xrewrap()
to extend/shrink the buffer's size.
There's an even better solution, and it is to refactor your code, and follow the conventions, so that you don't have to ungetc()
twice. xtdio
is just a proof of concept, but is not good code, and shall never be used in practice. This way, you won't have to deal with rewriting stdio
.