Your first program
A C session is a loop: edit a file, compile it, run the binary, read what happened, edit again. The editor is a matter of taste; this course assumes a terminal and any editor that writes plain text, such as Vi. Everything else is the compiler and the shell.
Write, compile, run
Save the program as first.c. The include line brings in the declaration of printf, and main is the function the runtime calls. Compile with the warnings of this course plus -Wpedantic, which reports anything outside the standard, then run the file the compiler produced.
#include <stdio.h>
int main(void)
{
printf("C runs this line, then returns 0 to the shell.\n");
return 0;
}
The shell needs ./ before the name because the current directory is not on PATH. Without it the shell answers first: command not found, even though the file is right there.
gcc -Wall -Wextra -Wpedantic first.c -o first
./first
C runs this line, then returns 0 to the shell.
Escapes and printf
A string literal is a sequence of bytes. Backslash sequences name bytes that are hard to type: \n is a newline, \t a tab, \" a quote inside a string. The compiler converts them before the program ever runs, so the output contains the tab itself, not the two characters.
#include <stdio.h>
int main(void)
{
printf("name\tscore\n");
printf("Ada\t95\n");
printf("Grace\t98\n");
printf("She said: \"write it once\"\n");
return 0;
}
gcc -Wall -Wextra escapes.c -o escapes
./escapes
name score
Ada 95
Grace 98
She said: "write it once"
The columns line up because the three lines use the same number of tabs, not because printf knows about tables. Lesson 10 replaces the tabs with field widths, which survive longer names.
Reading a line
fgets reads at most one less than the size of the buffer and keeps the newline if there is room. It returns NULL at end of input, which is the case the program must handle. The newline is removed with strcspn, so the greeting does not break in two.
#include <stdio.h>
#include <string.h>
int main(void)
{
char name[32];
printf("Your name: ");
if (fgets(name, sizeof name, stdin) == NULL) {
return 1;
}
name[strcspn(name, "\n")] = 0;
printf("Hello, %s!\n", name);
return 0;
}
Feed the program a line through a pipe, which is what a terminal does when you type the name and press Enter:
gcc -Wall -Wextra greet.c -o greet
printf 'Ada\n' | ./greet
Your name: Hello, Ada!
The prompt has no newline of its own, so the greeting continues on the same line. That is correct terminal behaviour, not a missing \n in the greeting.
Success and failure
A program reports the result through its exit status, not through its output. EXIT_SUCCESS is 0 and EXIT_FAILURE is a non-zero value defined by the implementation. The program below reads one line and returns failure when the input is empty.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char line[64];
if (fgets(line, sizeof line, stdin) == NULL) {
fprintf(stderr, "no input\n");
return EXIT_FAILURE;
}
printf("read: %s", line);
return EXIT_SUCCESS;
}
Run it twice, once with a line and once with nothing. The status is read after each run with $?:
gcc -Wall -Wextra status.c -o status
printf 'hello\n' | ./status; echo "status: $?"
printf '' | ./status; echo "status: $?"
read: hello
status: 0
no input
status: 1
The error text goes to standard error, which is why it still appears with the output redirected. Lesson 67 uses the same status when one process waits for another.
Two broken programs
The first mistake is a missing semicolon. The compiler points at the token where the statement should have ended and often suggests the missing character:
#include <stdio.h>
int main(void)
{
printf("missing semicolon\n")
return 0;
}
gcc -Wall -Wextra nosemi.c -o nosemi
nosemi.c: In function 'main':
nosemi.c:5:34: error: expected ';' before 'return'
5 | printf("missing semicolon\n")
| ^
| ;
6 | return 0;
| ~~~~~~
The second mistake is a missing header. In C23 an undeclared function is an error, and GCC adds a note that names the header to include:
int main(void)
{
printf("no header\n");
return 0;
}
gcc -Wall -Wextra noinc.c -o noinc
noinc.c: In function 'main':
noinc.c:3:5: error: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
3 | printf("no header\n");
| ^~~~~~
noinc.c:1:1: note: include '<stdio.h>' or provide a declaration of 'printf'
+++ |+#include <stdio.h>
1 | int main(void)
noinc.c:3:5: warning: incompatible implicit declaration of built-in function 'printf' [-Wbuiltin-declaration-mismatch]
3 | printf("no header\n");
| ^~~~~~
noinc.c:3:5: note: include '<stdio.h>' or provide a declaration of 'printf'
Read the first error first: later errors are often consequences of the first one. Both programs also show the shape of a diagnostic: file, line, column, severity, message, the source line and a caret under the exact token.
Exercises
- Print a three-column list with tabs, then replace the tabs with spaces and make the columns line up by hand.
- Change the greeting program to read two lines and greet both names. What happens when the second line is missing?
- Make the status program return 2 when the line is empty and 0 when it contains text. Check both paths with $?.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
Why does a freshly built program usually have to be started as
./first?Answer
The current directory is not on
PATHThe current directory is not searched through
PATH, so the explicit./tells the shell where the executable is. -
A buffer of size 32 is passed to
fgets. How many characters can it store at most, including the terminator?Answer
32
fgetsreads at most size minus one characters and always keeps room for the terminating zero, so a 32-byte buffer holds 31 characters plus the terminator. -
What appears between
AandBin the output ofprintf("A\tB\n");?Answer
A tab character
\tis an escape sequence that the compiler turns into a tab byte before the program runs. -
What does this program show on the terminal?
#include <stdio.h> int main(void) { printf("Your name: "); printf("Hello, Ada!\n"); return 0; }Answer
Your name: Hello, Ada!
The prompt has no newline of its own, so the greeting continues on the same line.
-
What is the value of
EXIT_FAILURE?Answer
A non-zero value defined by the implementation
The standard only requires
EXIT_FAILUREto be non-zero, whileEXIT_SUCCESSis zero. -
What does
strcspn(name, "\n")return for a line that ends with a newline?Answer
The index of the newline
strcspncounts the characters before the first byte from the second string, which is the position of the newline. -
This program is built as
statusand run with./status; echo "status: $?". What does the shell print?#include <stdio.h> #include <stdlib.h> int main(void) { char line[64] = ""; if (line[0] == '\0') { fprintf(stderr, "no input\n"); return EXIT_FAILURE; } printf("read: %s", line); return EXIT_SUCCESS; }Answer
no input / status: 1
The empty line takes the failure branch, which prints to standard error and returns
EXIT_FAILURE, 1 on this system. -
What does
printf("read: %s", "hello\n");print?Answer
read: helloand a newlineThe string contains a real newline from
\n, so the line ends afterhello. -
A statement is missing its semicolon. What does the compiler report?
Answer
An error such as
expected ';' before 'return'The parser reaches the next token without the required semicolon and reports the expected character at that point.
-
A file calls
printfbut does not include<stdio.h>. What happens under C23?Answer
It fails with an implicit declaration error
C23 no longer allows an implicit function declaration, so the call is an error and GCC adds a note naming
<stdio.h>.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev