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

  1. Print a three-column list with tabs, then replace the tabs with spaces and make the columns line up by hand.
  2. Change the greeting program to read two lines and greet both names. What happens when the second line is missing?
  3. 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.

  1. Why does a freshly built program usually have to be started as ./first?

    Answer

    The current directory is not on PATH

    The current directory is not searched through PATH, so the explicit ./ tells the shell where the executable is.

  2. A buffer of size 32 is passed to fgets. How many characters can it store at most, including the terminator?

    Answer

    32

    fgets reads 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.

  3. What appears between A and B in the output of printf("A\tB\n");?

    Answer

    A tab character

    \t is an escape sequence that the compiler turns into a tab byte before the program runs.

  4. 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.

  5. What is the value of EXIT_FAILURE?

    Answer

    A non-zero value defined by the implementation

    The standard only requires EXIT_FAILURE to be non-zero, while EXIT_SUCCESS is zero.

  6. What does strcspn(name, "\n") return for a line that ends with a newline?

    Answer

    The index of the newline

    strcspn counts the characters before the first byte from the second string, which is the position of the newline.

  7. This program is built as status and 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.

  8. What does printf("read: %s", "hello\n"); print?

    Answer

    read: hello and a newline

    The string contains a real newline from \n, so the line ends after hello.

  9. 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.

  10. A file calls printf but 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