Can output routines that print to a FILE* be used to build a string in C?

StackOverflow https://stackoverflow.com/questions/1270212

  •  13-09-2019
  •  | 
  •  

Question

I have a bad feeling that the answer to this question is "no", but I wanted to throw this out there in case anyone has any clever ideas.

I have a set of output routines that take a complex data structure and print it in a textual format. They have prototypes like:

void print_mystruct(struct mystruct *s, FILE *stream)

I wrote it this way so that I can get efficient, buffered output to the terminal, to a file, to the network, etc.

Unfortunately, there's no way that I know of, using standard C99, that I can use these same routines to build up a string in memory.

So my questions are:

  1. is there any clever way that I can efficiently use fputs(), fprintf(), etc. to output to a string?
  2. if not, is there a better paradigm that can efficiently do both buffered file output and string building? The best I can think of is to have my own structure with a vtable (instead of a FILE*). The vtable would have an output function that would either append to a string or call fwrite. But then I'd have to create printf wrappers also.

Any other clever ideas?

EDIT: I have discovered that fmemopen() is part of POSIX.1-2008 (see: fmemopen()) but is not widely supported, at least according to the GNU libc manpage.

Was it helpful?

Solution

There's no portable way of doing this. glibc systems(linux) have open_memstream/fmemopen , other systems might not have something like it.

The portable way is to write to a file and read it back into a string. , or to separate the concerns. Instead of implementing

void print_mystruct(struct mystruct *s,FILE *f);

You'd e.g. implement

char *mystruct_2_str(struct mystruct *s);

Which dynamically allocates a string(Or pass in a buffer), formats it to a string with standard string functions (snprintf etc.) and have the caller decide whether to write that to a FILE*

OTHER TIPS

If you don't care about seeking on the FILE, there's a portable (to all earlier POSIX targets) way to get the same effect as fmemopen, but it's rather costly. Make a pipe and a new detached thread, and fdopen the writing end of the pipe in the calling thread. The new thread then reads from the reading end of the pipe and puts the data into the string buffer. Have the new thread return when it gets EOF from the pipe; since it's detached, there's no cleanup to do.

Sure, there's nothing magical about the FILE struct, although I don't know of any built in library function to create one from a string buffer. Here is at least one version of the definition.

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

You could fabricate one of these structs on your own from a string buffer and pass it to your function, and also create a tear-down function that frees the memory.

The question is, which CRT library calls can you use on your version? Obviously anything referring to the file name will fail, since there isn't one. But you could probably use functions like fwrite and fread and fseek, which will be manipulating the pointers and allocating more space if necessary.

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

char* file_to_string(FILE *f, int *len) {
  if (fseek(f, 0, SEEK_END)) handle_error();
  int buflen = ftell(f);
  if (len) *len = buflen;
  char *buf = malloc(buflen + 1);
  buf[buflen] = '\0';
  rewind(f);
  size_t readlen = fread(buf, 1, buflen, f);
  if (readlen != buflen) handle_error();
  // in particular, note readlen might not equal buflen in the face of text-mode
  // conversions, but tmpfile() indicates binary-mode, so doesn't affect this
  // case
  return buf;
}

int main() {
  FILE *temp = tmpfile();

  // use FILE interface
  fputs("written to temporary file\n", temp);

  char *s = file_to_string(temp, NULL);
  // now you have contents as a string
  free(s);

  // can also rewind the FILE and process it incrementally, etc.

  return 0;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top