【问题标题】:modify fflush() that guarantee calling ungetc() twice in a row in C修改 fflush() 以保证在 C 中连续调用 ungetc() 两次
【发布时间】:2016-06-28 07:06:42
【问题描述】:

我是 C 初学者,我想连续调用 ungetc() 两次,尽管我知道在常规 C 中这是不允许的。有人告诉我我可以修改Fflush() 来完成这项工作,但是我不知道该怎么做。

这是我的代码,我的Fflush 只允许一个ungetc(),我希望它允许两次。

#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;
}     

【问题讨论】:

  • 我认为fflush() 不会达到这个目的。如果我理解正确,您打算执行两次ungetc(),并保证您的代码可以正常工作,对吗? 编辑:它不会撤销ungetc() 的效果。这意味着它对您的目的仍然无效。
  • 建议:作为 C 初学者,您可能应该首先学会遵守规则,而不是试图打破规则。
  • 是的,我想要的是修改原来的 fflush() 代码。我认为这将是一个安排至少两个字符在缓冲区中的问题是“ungotten”,无论如何。
  • @yuanwang:你的代码怎么失败了?在我看来,您应该能够在fflush 回电后立即读取最多BUF_SIZE 字符,尽管我承认我没有详细检查它。然而,更合理的方法是在缓冲区前面保留正常未使用的额外空间所需的尽可能多的字符,ungetc 将被允许备份到该缓冲区。本质上是setvbuf(stream, &amp;buffer[2], _IOFBF, sizeof buffer - 2),除了为合作而设计的自定义缓冲代码。
  • 请注意,虽然 C 标准仅保证使用 ungetc() 回推一个字符(并且它说“在调用之前文件位置指示符为零的二进制流上使用 ungetc是一个过时的功能”),许多实现允许更大量的推回。 IIRC,一个老式的 AIX 是最简洁的,只允许 4 个字符,而 Linux 和 BSD 允许 4 KiB(可能更多;我的测试停止在 4 KiB)。

标签: c stdio fflush getc ungetc


【解决方案1】:

正如 cmets 中明智地提到的那样,you should probably first learn to work with the rules instead of trying to break them。但是,我们是来回答这个问题的,这意味着要打破规则!考虑到 fflush()setbuf()setvbuf() 都不会因为不同的原因在这里工作。

首先,至少需要四个自定义函数。一个用于创建与文件相关的“代理缓冲区”(在fopen() 之后调用),一个用于销毁它(在fclose() 之前调用,一个用于实际取消获取(替换ungetc(),一个用于从文件中检索char(替换fgetc()。不幸的是,这意味着在流上执行fscanf()fflush() 等...会产生糟糕和丑陋的结果。你必须重写所有stdio

首先,我们将所有的新东西称为xtdio(“扩展stdio”),所以,首先是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

然后,在栅栏的有趣一侧,出现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;
}

较小的xtdio 库将允许您执行与传递给xwrap() 的参数一样多的取消操作。每个XFILE 都有一个包含未获取字符的缓冲区。当您xgetc() 时,它首先检查缓冲区中是否有东西并检索它。否则,它会退回到fgetc()。示例用例...

#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 可以进一步改进,通过添加诸如 xrewrap() 之类的东西来扩展/缩小缓冲区的大小。

还有一个更好的解决方案,那就是重构你的代码,并遵循约定,这样你就不必ungetc() 两次了。 xtdio 只是一个概念证明,但不是好的代码,永远不会在实践中使用。这样,您就不必处理重写stdio

【讨论】:

    【解决方案2】:

    如果您知道如何实现int 堆栈,则可以创建自己的ungetc() 函数。只需将ungetc() 的调用替换为myungetc()(等),如果该堆栈有值,则弹出它们而不是从getc() 读取。每当您想取消获取时,只需按照您读取它们的相反顺序将值压入堆栈即可。

    我最近的一个项目中的一个例子:

    /* 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 */
      /* ... */
    }
    

    (我为文字墙道歉,但不可能提供更短的示例) 考虑到您似乎正在处理文件,应该可以向后使用 fseek(),尽管这对文件和 stdin 都有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多