How to Fix: `fflush` doesn’t work since v14 (worked in v13)
fflush(stdout) stopped working after v14 because stdout is no longer treated like a real interactive terminal in the same way it was in v13.
If your C program prints a prompt with printf(">"), calls fflush(stdout), and still does not show the prompt before read() blocks, the regression usually comes from a runtime or terminal-layer change rather than from fflush itself. The core issue is that buffer flushing only works if the underlying file descriptor is writable and connected to the expected terminal/pty path. In v14, changes in the stdio or tty plumbing can make output appear delayed even though fflush(stdout) is being called correctly.
Reproducing the Bug
This minimal program demonstrates the problem:
#include <stdio.h>
#include <unistd.h>
int main() {
printf(">");
fflush(stdout);
char buf[10];
read(0, buf, 10);
printf("Got %s\n", buf);
}
Expected behavior: the > prompt appears immediately before the process waits for input.
Observed behavior on v14: the process blocks on input, but the prompt is not visible until later, making it look like fflush(stdout) failed.
Understanding the Root Cause
fflush(stdout) does not force pixels onto a screen; it only pushes buffered data from the C stdio layer into the underlying file descriptor. If the runtime, pseudo-terminal, compatibility layer, or host environment changed in v14, several things can break that expectation:
- stdout is buffered correctly, but the terminal bridge delays rendering. In that case, libc flushes successfully, yet the terminal frontend does not display the bytes immediately.
- stdout is no longer recognized as a tty-like destination. That can change buffering strategy and prompt visibility.
- Mixing stdio and low-level syscalls can expose runtime-specific behavior. Your program writes output with
printf()and reads input withread(). While this is legal, it relies on the host/runtime to keep terminal semantics predictable. - The issue may be in the v14 runtime regression, not your code. If the same binary worked in v13 and stopped in v14, that strongly suggests a change in the implementation of pipes, ptys, console forwarding, or libc integration.
In short, the bug happens because the flush reaches stdout, but stdout is no longer being surfaced immediately to the interactive console path under v14.
Step-by-Step Solution
The most reliable fix is to make interactive prompts bypass stdio buffering entirely or force a terminal-friendly output path.
1. Write the prompt directly to the terminal file descriptor
Instead of relying on printf() for a prompt, write directly to file descriptor 1:
#include <stdio.h>
#include <unistd.h>
int main() {
write(1, ">", 1);
char buf[10] = {0};
read(0, buf, sizeof(buf) - 1);
printf("Got %s\n", buf);
}
This avoids stdio buffering for the prompt entirely. For simple REPL-style prompts, this is often the most robust workaround.
2. Disable buffering on stdout
If you want to keep using printf(), turn off buffering explicitly:
#include <stdio.h>
#include <unistd.h>
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
printf(">");
char buf[10] = {0};
read(0, buf, sizeof(buf) - 1);
printf("Got %s\n", buf);
}
Using setvbuf() with _IONBF ensures every write to stdout is sent immediately from stdio.
3. Prefer one I/O model consistently
Avoid mixing stdio and POSIX syscalls unless you need to. If you use printf(), consider reading with fgets() instead of read():
#include <stdio.h>
int main() {
char buf[10];
printf(">");
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) != NULL) {
printf("Got %s\n", buf);
}
}
This keeps all interaction inside the same buffered I/O subsystem and can avoid edge-case regressions in certain runtimes.
4. If this is a platform regression, pin or patch the runtime
If the bug was introduced specifically in v14 and not in v13, validate the environment with a small matrix:
# v13
run-test-binary
# v14
run-test-binary
# compare tty / pty behavior, stdout mode, and rendering
If only v14 is broken, the long-term fix is usually one of these:
- pin to v13 temporarily,
- upgrade to a patched v14 release if available,
- use direct
write()for prompts as a compatibility workaround, - report the regression with a minimal reproducible example.
5. Use a safer final version for interactive CLI tools
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
char buf[10] = {0};
if (write(STDOUT_FILENO, ">", 1) < 0) {
return 1;
}
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (n < 0) {
return 1;
}
buf[n] = '\0';
printf("Got %s\n", buf);
return 0;
}
This version avoids prompt buffering issues and also prevents undefined output from printing an unterminated buffer.
Common Edge Cases
- Missing null termination:
read()does not append'\0'. Printingbufwith%swithout null termination can produce garbage or over-read memory. - Prompt hidden by terminal emulation bugs: Even if libc flushes correctly, a broken pty or console renderer can delay visual output.
- Piped output: If stdout is redirected to a pipe or file, interactive prompt assumptions no longer apply.
fflush()flushes bytes, but there may be no live terminal to display them. - Newline-sensitive display behavior: Some terminal layers appear to update more predictably after
\n. A bare prompt character can expose buffering or rendering regressions more easily. - Mixing input APIs: Using
printf()withread(), orwrite()withfgets(), can behave differently across environments when terminal handling changes.
FAQ
Why did fflush(stdout) work in v13 but not v14?
Because the regression is likely in the runtime’s terminal or stdout handling, not in the C source itself. fflush() only flushes libc buffers; it does not guarantee immediate terminal rendering if the environment changed.
Is fflush(stdout) broken in C?
No. In standard C, fflush(stdout) is still valid for output streams. The failure pattern here usually means the bytes were flushed from stdio, but the host console path did not present them when expected.
What is the best workaround for interactive prompts?
Use write(STDOUT_FILENO, ">", 1) for prompts, or disable stdout buffering with setvbuf(stdout, NULL, _IONBF, 0). For CLI tools, direct writes are often the most portable workaround during runtime regressions.
The practical takeaway: the issue is not that fflush suddenly forgot how to flush; it is that v14 changed how flushed stdout reaches the interactive terminal. For production-safe behavior, write prompts directly, null-terminate input correctly, and avoid relying on fragile console-buffering assumptions.