A few days ago, I received an email from a reader who reported a new errata on page 123 (in the “Learning to fly the PIC24” book) in the write() function, an example of redirecting the “stdout” output stream.
The function receives a pointer to a buffer containing characters that need to be forwarded to the output device of choice and a counter. A loop is performed to print sequentially the required number of characters, but (here is the bug) I apparently omitted to increment the buffer pointer. To the reader’s greatest surprise the code example seems to work anyway! How is this possible?
To reveal the mystery it takes a bit of historical perspective and insight in the inner workings of the stdio library…
As you might know the stdio.h library was originally written (by Thompson and others at Bell Labs circa 1970) in the most generic possible way using the concept of i/o streams (stdin, stdout, stderr, …) so that the same functions could be used to write to a terminal, a printer or a disk drive. This was a fine concept, but efficiency and usability reasons dictate that the three cases must be dealt in different ways for what regards the “buffering” of the data.
A disk drive would work very inefficiently if we were to send a write command for each single byte, writing a block the size of a sector at a time is a better option.
A printer on the other side would be better served by a buffering mechanism based on entire lines of text.
A video terminal would be very unresponsive with a line buffering, each character needs to be printed immediately for the user to see…
So the standard C libraries include the concept of “buffering discipline”, and apply it differently based on the target device of each stream.
The MPLAB C30 stdio.h library applies the “one character at a time” (no buffering) discipline as a default, assuming you will be using the stdout stream to send characters to a serial port attached to a terminal of sorts. Since no buffering is used, each character output will produce a separate call to the write() function (passing a count of 1). Hence the flawed code snippet will appear to work fine as no increment is ever required.
The write() function though must be generic and must be able to adapt to each of the three possible disciplines, ready to send an entire line of characters or an entire buffer full of characters to the output device.
The standard setbuf() and setvbuf()
functions can be used to change the buffering discipline for a given stream. After opening a stream, (stdout is always open) you can specify:
_IONBF – no buffering, one character at a time (default for stdout)
_IOLBF – line buffering, one line at a time
_IOFBF – block buffering, one block at a time
Notice that in the last two cases you will have to alocate memory and provide a pointer to a buffer to be used.
Also should you use one of the last two options, the “flawed” example code will clearly not work…
Hope this helps…
P.S.: If you check the code on the PIC24 CDROM you will notice that the bug had already been detected and corrected at the time of publication. Also the PIC32 readers will not be affected, incidentally the MPLAB C32 compiler libraries use a slightly different technique to redirect the stdout stream via a function called _mon_putc() that is always required to manage a single character at a time.