Skip to content

Instantly share code, notes, and snippets.

@jfriedly
Last active March 2, 2023 07:21
Show Gist options
  • Save jfriedly/9797693 to your computer and use it in GitHub Desktop.
Save jfriedly/9797693 to your computer and use it in GitHub Desktop.
The difference between ``putc`` and ``fputc`` in C: notes from a conversation with Shevek

C: putc vs fputc

From the putc man page, "putc() is equivalent to fputc() except that it may be implemented as a macro which evaluates stream more than once." I wasn't sure what this meant so I asked Shevek. He launched into a lambda calculus explanation that eventually boiled down to: putc may evaluate it's arguments more than once, but fputc evaluates them exactly once. This is because putc is implemented as a macro and the C preprocessor just does string substitution, leading to examples like the ones below.

Ignoring what putc and fputc actually do, let's look at a simplified version where they both simply do one operation twice and another operation once:

#define putc(v, s) s, s, v
void fputc(v, s) { s, s, v }

putc('a', streams[idx++])
    -> streams[idx++], streams[idx++], 'a'
    -> idx += 2

fputc('a', streams[idx++])
    -> v = 'a'
    -> s = streams[idx++]
    -> s, s, v
    -> idx += 1

The outcome of this is that idx gets incremented twice by putc, but only once by fputc. That happens because the putc being a macro means that it cannot evaluate it's arguments before it is "called", it must simply evaluate them at each place where they're used at runtime. fputc, on the other hand, evaluates it's arguments once and binds those values to variables inside it's scope.

A more realistic example is one that could actually be a definition of putc:

#define putc(v, s) do {
    s.buf[s.ptr++] = v;
    if (s.ptr == BUFSIZ) { fflush(s); }
} while(0)

FILE **streams[100];
void writeall(char c) {
    int idx = 0;
    while (idx < 100)
        putc(c, streams[idx++]);
}

struct FILE {
    int ptr = 0;
    char buf[BUFSIZ];
}

The while loop was supposed to write a character to each of a hundred streams. But instead, it wrote a character to every third stream (and when ptr reaches BUFSIZE, it skips one), because the macro evaluates to:

while (idx < 100) {
    streams[idx++].buf[streams[idx++].ptr++] = v;
    if (streams[idx++].ptr == BUFSIZE) { fflush(streams[idx++]); }
}

If we had used fputc instead of putc, we would have written to each of the 100 streams.

The lambda calculus part comes in because it's the theory that describes this effect. Suppose we have a function f(x) = x + x. Now suppose that x = a + b. Because addition is associative, we can evaluate these in any order: we can evaluate x first and then add it to itself, or we can substitute a + b into f(a + b) = a + b + a + b. We can think of this as a tree:

.
        answer
       /      \
      x   +    x
     / \      / \
    a + b +  a + b

The two ways of evaluating it are just evaluating it from the root down or from the leaves up. Now in lambda calculus, there are no side effects, so either way works fine. But if there are side effects of an operation, such as incrementing an index, then the order matters. putc is like evaluating from the leaves up, but fputc is like evaluating from the root down.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment