tags:

views:

143

answers:

3

So I've been digging into how the stdio portion of libc is implemented and I've come across another question. Looking at man setvbuf I see the following:

When the first I/O operation occurs on a file, malloc(3) is called, and a buffer is obtained.

This makes sense, your program should have a malloc in it for I/O unless you actually use it. My gut reaction to this is that libc will clean up its own mess here. Which I can only assume it does because valgrind reports no memory leaks (they could of course do something dirty and not allocate it via malloc directly... but we'll assume that it literally uses malloc for now).

But, you can specify your own buffer too...

int main() {
    char *p = malloc(100);
    setvbuf(stdio, p, _IOFBF, 100);
    puts("hello world");
}

Oh no, memory leak! valgrind confirms it. So it seems that whenever stdio allocates a buffer on its own, it will get deleted automatically (at the latest on program exit, but perhaps on stream close). But if you specify the buffer explicitly, then you must clean it up yourself.

There is a catch though. The man page also says this:

You must make sure that the space that buf points to still exists by the time stream is closed, which also happens at program termination. For example, the following is invalid:

Now this is getting interesting for the standard streams. How would one properly clean up a manually allocated buffer for them, since they are closed in program termination? I could imagine a "clean this up when I close flag" inside the file struct, but it get hairy because if I read this right doing something like this:

setvbuf(stdout, 0, _IOFBF, 0);
printf("hello ");
setvbuf(stdout, 0, _IOLBF, 0);
printf("world\n");

would cause 2 allocations by the standard library because of this sentence:

If the argument buf is NULL, only the mode is affected; a new buffer will be allocated on the next read or write operation.

EDIT: an addendum to my question. Since it is clear that I must free any buffers I pass to setvbuf, if I do in fact use it on stdout is there any practical way to free it? It must live to program end. The best I can think of is to fclose(stdout) then free it or use a static buffer as some people have mentioned. I ask because it does seem like an awkward design decision.

+2  A: 

Also from the man page (at least, on my system):

If buf is not NULL, it is the caller's responsibility to free(3) this buffer after closing the stream.

That is, you malloc-ed it, you free it.

Before exit, you could close the streams yourself, thus allowing you to free the buffer. Alternatively, you could flush the streams and call setvbuf again with a NULL buffer argument to switch back to a library managed buffer or unbuffered I/O.

outis
Updated answer to link to the page. It's OS X, but the man page comes from FreeBSD (http://www.freebsd.org/cgi/man.cgi?query=setvbuf).
outis
Hard to see how things could be otherwise, as one could provide a static buffer that must not be free'd with free().
anon
@Neil: indeed, I was wondering more if the stdlib had a memory leak by design since there is no way to get the buffer it allocated out from the stream.
Evan Teran
Calling "`setvbuf` again with a NULL buffer argument" isn't allowed by the standard (see citation in my answer).
Jerry Coffin
+1  A: 

You can close stdin, stdout and stderr explicitly (with fclose()).

With most operating systems, heap memory is automatically released upon program exit. So there is no practical issue with having unreleased buffers (there is a cosmetic issue, which is that those unreleased buffers pollute Valgrind's output). My advice would be to use static buffers if you feel the urge to call setvbuf() on standard input or output. Static buffers needs not be allocated or released, and are appropriate here since there are only three standard streams and you are worrying about a situation where those streams are kept open up to the program termination.

Thomas Pornin
Polluted debugging output is a problem, because you get in the habit of glossing over the output and you can easily miss a real error.
Mark Ransom
@Thomas: is there a guarantee that files will be closed before memory is freed?
outis
@outis: on operating systems where memory is automatically released, it is released "atomically", at a point where the execution thread(s) have ceased to exist. There is nothing which may run after that, in particular no libc code which could (wrongly) access the released buffer. In brief it "just works". There could still be some open files and buffered data, but within the OS kernel, outside of the reach of applicative code.
Thomas Pornin
+1  A: 

At least according to the C standard, your last scenario simply isn't allowed: "The setvbuf function may be used only after the stream pointed to by stream has been associated with an open file and before any other operation (other than an unsuccessful call to setvbuf) is performed on the stream." (C99, §7.19.5.6/2).

As to when to free the memory in the simpler cases, on way is to call atexit() to register a callback that will free the memory after exiting from main(), but before control is returned to the OS.

Jerry Coffin
interesting. So my last example falls into the category of undefined behavior.
Evan Teran