1

I'm a C beginner, I want to call ungetc() twice in a row although I know in regular C it is not permitted. Someone told me I can modify Fflush() to do this job, however I don't know how to do it.

Here is my code, my Fflush only allow one ungetc(), I want it to allow twice.

#define altUngetc(c, fp) ((fp)->next > (fp)->buffer && (c) != EOF ? \
 *--(fp)->next = (c) : EOF)

int altGetc(ALT_FILE *fp) {
  if (fp->next == fp->buffer + fp->bufSize)
  altFflush(fp);

  return fp->flags & FILE_ATEOF ? EOF : *fp->next++;
}

int altFflush(ALT_FILE *fp) {
  int res;

  if (fp->fd < 0 || fp->flags & FILE_ATEOF)
     return EOF;
  if (fp->flags & FILE_READ) {                               
     res = read(fp->fd, fp->buffer, BUF_SIZE);               
     if (res == 0)                                           
        fp->flags |= FILE_ATEOF;                             
     fp->bufSize = res;                                      
     fp->next = fp->buffer;                                  
  }                                                          
  else {                                                     
     res = write(fp->fd, fp->buffer, fp->next - fp->buffer); 
     fp->next = fp->buffer;                                  
  }   
   return res < 0 ? EOF : 0;
}     
yuan wang
  • 27
  • 4
  • 1
    I don't think `fflush()` will serve that purpose. If I'm understanding correctly, you intend to do `ungetc()` twice, and get your code to be guaranteed to work, right? **Edit**: It doesn't undoes `ungetc()`'s effects. That means it's still uneffective for your purposes. – 3442 Mar 13 '16 at 07:28
  • 2
    advice: as a C beginner, you should probably first learn to work with the rules instead of trying to break them. – mfro Mar 13 '16 at 07:31
  • yes, what I want is to modify original fflush() code. I think this is going to be a matter of arranging for at least two chars to be in the buffer to be "ungotten", no matter what. – yuan wang Mar 13 '16 at 07:32
  • @yuanwang: How is your code failing? It seems to me that you ought to be able to put up to `BUF_SIZE` characters read immediately after a `fflush` call back, though admittedly I haven't scrutinized it in detail. However a more reasonable approach would be to reserve as many characters as required of normally-unused extra space at the front the buffer which `ungetc` would be allowed to back up onto. Essentially `setvbuf(stream, &buffer[2], _IOFBF, sizeof buffer - 2)` except with custom buffering code crafted to cooperate. – doynax Mar 13 '16 at 08:06
  • @doynax: my original fflush() code only allow at least one char in buffer, so if I call two ungetc() consequently, it may fail. I think you are right, I need to put at least two chars in buffer, but I'm confused how to do it. – yuan wang Mar 13 '16 at 08:17
  • 1
    Note that while the C standard only guarantees one character of push-back with `ungetc()` (and it says the "use of ungetc on a binary stream where the file position indicator is zero prior to the call is an obsolescent feature"), many implementations allow far larger amounts of push-back. IIRC, an oldish AIX was the most parsimonious, only allowing 4 characters, while Linux and BSD allowed 4 KiB (possibly more; my testing stopped at 4 KiB). – Jonathan Leffler Mar 13 '16 at 09:10
  • @JonathanLeffler: Apparently, Glibc's implementation limit is a failed `malloc()`. – 3442 Mar 14 '16 at 06:50

2 Answers2

5

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.

3442
  • 8,248
  • 2
  • 19
  • 41
0

If you know how to implement a int stack, you can create your own ungetc() function. Simply replace calls of ungetc() with a myungetc() (etc) that if that stack has values, pop them instead of reading from getc(). Whenever you want to un-get, simply push values on to the stack in the reverse order you read them.

An example from a recent project of mine:

/* stack.h */
#ifndef _STACK_H_
#define _STACK_H_
typedef struct {
  int * vals;
  int currsize;
  int maxsize;
} stack;

int stack_init(stack * this, int size);
void stack_destroy(stack * this);

void push(stack * this, int val);
int pop(stack * this);
int isempty(stack * this);
int isfull(stack * this);
int size(stack * this);
int maxsize(stack * this);
#endif

/* stack.c */
#include <stdlib.h>
#include "stack.h"
#define THIS (this)
#define VALS (this->vals)
#define CURRSIZE (this->currsize)
#define MAXSIZE (this->maxsize)
int stack_init(stack * this, int size) {
  VALS = malloc(sizeof(int)*size);
  CURRSIZE = 0;
  MAXSIZE = size;
  if (!VALS) {
    return 1; /* alloc fail */
  }
  return 0; /* successful init */
}
void stack_destroy(stack * this) {
  free(VALS);
}

void push(stack * this, int val) {
  if (isfull(THIS)) {
    return;
  }
  VALS[CURRSIZE++] = val;
}
int pop(stack * this) {
  if (isempty(THIS)) {
    return 0;
  }
  return VALS[--CURRSIZE];
}
int isempty(stack * this) {
  return (CURRSIZE == 0);
}
int isfull(stack * this) {
  return (CURRSIZE == MAXSIZE);
}
int size(stack * this) {
  return CURRSIZE;
}
int maxsize(stack * this) {
  return MAXSIZE;
}
#undef THIS
#undef VALS
#undef CURRSIZE
#undef MAXSIZE

/* main.c */
#include <stdlib.h>
#include <stdio.h>
#include "stack.h"

int stgetc(FILE * stream, stack * pushed) { /* The getc() equivalent */
  if (isempty(pushed)) {
    return getc(stream);
  } else {
    return pop(pushed);
  }
}

int stpush(int val, stack * pushed) { /* The ungetc() equivalent */
  if (isfull(pushed)) {
    return 1;
  } else {
    push(pushed,val);
    return 0;
  }
}

int main(int argc, char ** argv) {
  /* startup code, etc. */
  stack pushbuf; /* where the pushback will be stored */
  stack_init(&pushbuf, 32) /* 32 = maximum number of ungetc calls/characters we can push */
  FILE * in = fopen("/some/file","r");
  /* file read */
  int readchar;
  while ((readchar = stgetc(in,pushbuf)) != EOF) {
    /* do stuff */
    stpush(readchar,pushbuf); /* oops, read too much! */
  }
  fclose(&in); /* close file */
  stack_destroy(&pushbuf); /* clean up our buffer */
  /* ... */
}

(I apologize for the wall of text but a shorter example isn't possible) Considering you seem to be working with a file, it should be possible to fseek() backwards, although this will work for both files and stdin.

Kitten
  • 437
  • 3
  • 13