C / CONSOLE INPUT AND OUTPUT
puts, putchar and choosing the right output call
Choose puts, fputs, putchar or printf deliberately: which appends a newline, which parses % directives, and which is safe for arbitrary text.
What you will learn
- Use puts for a whole line of text: it appends the newline, printf does not.
- Use fputs(s, stdout) when the string must not end in a newline.
- Print %-bearing or untrusted text with puts(s) or printf("%s", s), never printf(s).
- Emit single characters with putchar; no format string is scanned at runtime.
Understanding puts, putchar and choosing the right output call
The three calls differ mainly in how much interpretation happens at runtime. putchar hands exactly one character to stdout. puts hands over a whole NUL-terminated string and then adds a newline of its own. printf treats its first argument as a small program that it scans at runtime, hunting for % directives and pulling matching arguments off the call, which is why it does the most work per character printed.
The newline behavior is the part that trips people up, and it is not arbitrary. puts exists for the common case of "write this as a line", so it supplies the '\n' for you. fputs is the general stream version and writes exactly the bytes you gave it, because silently appending a newline would break any code that builds one line out of several pieces. putchar(c) is nothing more than fputc(c, stdout), so pairing fputs with an explicit putchar('\n') gives you full control over where lines end.
Preferring puts over printf for plain text is a correctness matter, not a style preference. printf's first argument is a format, so if that string came from a file, from argv or from a user, a stray % makes printf begin a conversion and read an argument that was never passed: undefined behavior, and historically a way to leak or corrupt memory. puts never inspects the bytes it copies, so it cannot be steered that way, and it is cheap enough that gcc quietly rewrites printf("text\n") into puts("text") behind your back.
<stdio.h>
int main(void)
{
puts("puts writes the string and the newline");
fputs("fputs writes the string only", stdout);
putchar('\n');
const char *word = "grid";
for (int i = 0; word[i] != '\0'; i++) {
if (i > 0)
putchar('-');
putchar(word[i]);
}
putchar('\n');
const char *from_data = "80% done, %s left";
puts(from_data);
return 0;
}
puts, fputs and putchar copy bytes literally while printf runs a format language at runtime, so the right call is the least interpretive one that still produces the line you want.
Worked examples
Building a line out of pieces
Mixing fputs and putchar to assemble text where no value needs formatting.
<stdio.h>
static void bar(const char *label, int n)
{
fputs(label, stdout);
fputc(':', stdout);
for (int i = 0; i < n; i++)
putchar('#');
putchar('\n');
}
int main(void)
{
bar("cpu", 7);
bar("mem", 3);
bar("net", 0);
return 0;
}
Example explained
Line 1fputs(label, stdout) writes the label with no newline, so the bar keeps building on the same line.
Line 2putchar('#') writes one byte per call and there is no format string for the library to scan.
Line 3putchar(c) is defined as fputc(c, stdout), which is why the two calls interleave without any special handling.
Line 4With n == 0 the loop body never runs, so "net:" is followed straight away by the newline from putchar.
Text containing a percent sign
Why data must be an argument to printf rather than its format, and what putchar returns.
<stdio.h>
int main(void)
{
char line[] = "discount: 100%s";
puts(line);
printf("%s\n", line);
int c = putchar('A');
printf(" putchar returned %d\n", c);
return 0;
}
Example explained
Line 1puts(line) copies the bytes unchanged, so 100%s appears literally; printf(line) would read %s as a conversion.
Line 2printf("%s\n", line) is the safe form when you need printf anyway: the data is an argument, not the format.
Line 3putchar returns the character it wrote converted to int, so c holds 65, the ASCII code for 'A'.
Line 4The A and the text after it share one line because putchar wrote no newline of its own.
Important notes
putchar takes an int, not a string: putchar('x') is correct, putchar("x") passes a pointer and is a bug even where the compiler only warns.
puts is only guaranteed to return some nonnegative value on success, so test its result against EOF rather than expecting a character count.
Common mistakes
Writing puts("done\n"): puts adds its own newline, so you get an unwanted blank line after done.
Expecting fputs to behave like puts: fputs("a", stdout); fputs("b", stdout); prints ab with no line break, and the next output is glued to it.
Calling printf(msg) when msg is data: a % inside it makes printf read arguments that were never passed, giving garbage, a crash, or leaked stack bytes.
Try it yourself
Change, predict, then run
Take the three calls printf("Name: "); printf("%s\n", "ada"); printf("done\n"); and rewrite each one with the least interpretive call that still produces byte-identical output. Then add a loop that underlines the word ada with putchar('~').
Open the C workspaceCheck your understanding
A string variable holds the text 50% complete and you want it printed on a line of its own. Which call is both correct and safe?
- printf(label);
- fputs(label, stdout);
- puts(label);
- putchar(label);
Show answer
puts copies every byte of label unchanged and then supplies the newline, so the % is just a percent sign. printf(label) is the tempting answer because it looks like ordinary printing, but the string is used as the format, so % starts a conversion and printf reads an argument that was never passed. fputs is safe but writes no newline, and putchar expects a character, not a pointer.