tags:

views:

192

answers:

6

C# has a neat feature of being able to write to a memory stream using the MemoryStream object.

I'm looking for similar functionality from C using a FILE* pointer.

I want to be able to sprintf() but with the added functionality of having C remember "where I was" in the buffer I'm writing to.

A: 

Why not use mmap? You can map a file to memory and use a FILE* pointer.

Update: It does not work. Sorry.

stribika
No, you can't. mmap only works in page units, and has to be reset if the file size changes. Additionally, modifying the file via normal IO channels while it's mmaped results in undefined behavior.
bdonlan
A: 

MemoryStream is not a feature of C#, the language. It's simply a class in the BCL. You could write it yourself in C#.

And so it is in C - you'd write some functions that work similarly in use to fopen, fprintf, fwrite, fclose, etc., but give them names like mopen, mwrite, etc. (presumably) and write them so they operate on a memory buffer.

Daniel Earwicker
+5  A: 

If you are using the GNU C Library, you can use fmemopen(). There may be other non-portable extensions for other environments, but there's no portable way using FILE*s.

You could also, however, wrap snprintf, if you don't insist on actually using FILE*s. For example, glib (note: not the same as the GNU C Library, and portable) has a g_string_append_printf that does what you want.

bdonlan
glibc also has `open_memstream`, which you should use if you don't know the size of the data you'll be writing in advance. See http://linux.die.net/man/3/fmemopen
Adam Rosenfield
+1  A: 

There's also an ugly hack which works with plain ISO-C: You can use fopen() to open a null file (/dev/null on *nix, NUL on Windows) and set the array as the file's buffer via

setvbuf(file, buffer, _IOFBF, buffer_size)

This should work fine as long as fflush() isn't called anywhere in the code. Also, the programmer has to explicitly take care of the string delimiter.

I don't really see a need to do this, though: As snprintf() returns the number of characters written, it's trivial to keep track of a buffer's position.

One can even write a function to automagically resize the buffer on overflow: bufprintf.c

The function's prototype is

int bufprintf(char **buffer, size_t *size, size_t *offset,
    const char *format, ...);

An example program could look like this:

#include <stdio.h>

extern int bufprintf(char **buffer, size_t *size, size_t *offset,
    const char *format, ...);

int main(void)
{
    size_t size = 0; // must be set!
    size_t offset;
    char * buffer;

    for(int i = 0; i < 100; ++i)
        bufprintf(&buffer, &size, &offset, "we rock %i\n", i);

    puts(buffer);
    printf("size:\t%u\noffset:\t%u\n", (unsigned)size, (unsigned)offset);
}
Christoph
this is one sweet hack.
bobobobo
this is the "right" answer to the question, but i'm afraid to mark it so because it is called/labelled a "hack". can anyone validate this practice?
bobobobo
From what I can tell, it's perfectly legal, but I wouldn't write new code to do that. It's an easy and nice way to port existing code that is hardcoded to writing to a file, though.
arke
Note that setvbuf is part of C99, which isn't implemented on all compilers fully yet. Seems to be implemented on glibc and msvc though.
bdonlan
A: 

How about just manually managing your pointers. I don't have a compiler in front me but hopefully the code below gets the idea across:

char huge_buffer[REALLY_BIG_SIZE];
char *write_pos = huge_buffer;

//...

void fprintf_to_mem(char **mem_ptr, const char *fmt, ...)
{
  va_list args;
  int num_written;

  va_start(args, mem_ptr);
  num_written = vsprintf(*write_pos, fmt, args);
  *write_pos += num_written;

  va_end(args);
}

//...

fprintf_to_mem(&write_pos, "Testing %d %d %d", 1, 2, 3);
fprintf_to_mem(&write_pos, "Hello world!\r\n");

I would expect that to output "Testing 1 2 3Hello world!\r\n" to huge_buffer.

Gabe
A good idea, but you don't keep track of the buffer size so you're susceptible to buffer overflows (and you waste a lot of memory otherwise); as a result you also don't handle dynamically resizing the array when it fills up. It's also not multithread-safe, since it uses global variables.
Adam Rosenfield
The code above simply illustrates the concept, I didn't include all of the error checking etc. The variables don't necessarily have to be global, again I was just indicating their types and that they would be defined outside of the function. They could exist in whatever scope he wanted (function scope/file scope etc). Thanks for pointing that stuff out, though, I had assumed that he would take all of that into account.
Gabe
I was also assuming he had some existing buffer he was filling. I guess that's not really a fair assumption. If he wanted something more like the STL std::vector, then that would obviously require a lot more work!
Gabe
+2  A: 

sprintf returns the number of characters that were printed into the string. You can use that value to increment the pointer of your buffer.

buffer += sprintf(buffer, "%d", i);

Make sure that you keep around a copy of the original pointer, as that is what you will be using when passing the buffer somewhere else.

arke