There's a lot of nonsense-UB in stdio due to buffer comparisons along the lines of "f->rpos < f->rend". The intent of these comparisons is to simultaneously check that the buffer is initialized for the proper mode (read or write) and that there's data left in it (for reading) or space left (to write) or buffered data to be written out (for write), etc. Unfortunately, when the buffer is uninitialized for the mode being checked, the comparison becomes NULL. This is correct because in the places where we're testing for <, > is not a possibility; to be true, > would require OOB pointer arithmetic (thus UB) already to have been performed. Unfortunately there are still places where UB is left: subtraction, such as this line in ftell: return pos - (f->rend - f->rpos) + (f->wpos - f->wbase); Adding branches to test for all the null buffer possibilities here would uglify and pessimize the code (compilers are too stupid to optimize them out). So another possible idea is abolishing use of null pointers here, and using a different sentinel, e.g. f->buf (start of buffer). Then instead of disabling write buffer via: f->wpos = f->wbase = f->wend = 0; we could do something like: f->wpos = f->wbase = f->wend = f->buf; Unfortunately there are several places that explicitly check predicates like !f->rpos, and in some places they're treated differently than end-of-buffer, such as ungetc: if (!f->rpos) __toread(f); if (!f->rpos || f->rpos <= f->buf - UNGET) { Here, empty buffer after __toread is expected, but null pointer indicates an error entering read mode. This could possibly be handled by using f->buf-UNGET (which would naturally inhibit ungetc) as the sentinel for read mode off. The other major user of null sentinels is code in fseek.c and fflush.c that checks for write failure; after f->write(), f->wpos==0 is interpreted as an error. f->wpos==f->wend can't in general be used as an error conditional (it's always true for unbuffered output) but it could be used in these specific cases since the code path is only reachable when there's buffered data to be flushed. Also related: vsnprintf does some wacky UB stuff in order to support direct writes into a buffer of unknown size, and strto* use similar hacks to read from buffers of unknown size. So the right long-term solution might be to eliminate the whole system of end pointers and instead compare pos-start against a size. And on top of that I guess we need to stop using null so pos-start is defined. So what to do? I'm not aware of any immediate need to fix these issues, but I want to future-proof the code and make it friendly to analysis tools that catch the undefined behavior. I think a good place to start might be coming up with and documenting a clear model for how stdio's buffer internals are supposed to work, what operations are allowed, what invariants hold, etc. based on the above analysis of current UB issues and what the code is doing. Rich