TRT
Sign in
easyFoundationscoding

How do you organize a C++ program across multiple source files and build it?

Explain header/implementation separation, the compile-then-link model, one-definition rule, and a simple build script.

TRT practice prompt — not a verified question from a named employer.

The Runtime Theory Team2 min read
Learn it firstProject: A Contact Book Across Several FilesBuild a multi-file contact book: structs, file I/O, line parsing, and a simple build script that compiles everything into one program.

A strong answer

A multi-file C++ program has three layers: headers (declarations), implementation files (definitions), and a build script (compile + link).

Header (contact.h): Contains the Contact struct definition and function declarations (parse_contact, load_contacts, save_contacts, to_csv). Wrap in header guards (#ifndef CONTACT_H ... #endif). Every .cpp that needs the type includes this header — they all see the exact same definition.

Implementation files (parse.cpp, io.cpp, main.cpp): Each includes contact.h and defines its functions. parse.cpp has the CSV parser. io.cpp has file load/save. main.cpp has the interactive loop. None of them defines Contact — they all get it from the header.

Build script: Compile each .cpp to an object file: g++ -std=c++17 -c parse.cpp -o parse.o (repeat for io.cpp, main.cpp). Then link: g++ parse.o io.o main.o -o contact_book. The linker resolves load_contacts from io.o, parse_contact from parse.o, and main from main.o.

One Definition Rule: The Contact struct is defined once in the header. Because every translation unit includes the header, they all agree on its layout. If you accidentally defined Contact in two .cpp files with different members, the linker would not catch it (each TU compiles separately) but the program would have undefined behavior. Header guards prevent double inclusion within one TU; the convention of define in header, declare in header, implement in one .cpp prevents cross-TU conflicts.

Incremental builds: The script above recompiles everything. A real build system (make, ninja, CMake) tracks timestamps: if parse.cpp changes, only parse.cpp recompiles; the link step always runs because the final executable depends on all objects.

What the interviewer is listening for

  • Header guards and why they matter.
  • Compile-then-link: .cpp → .o (compile) → executable (link).
  • One Definition Rule: one definition of each entity across the whole program.
  • Why Contact goes in the header (shared layout) while function bodies go in one .cpp (single definition).
  • How the linker resolves symbols across object files.
  • Incremental build concept: timestamps → dependency graph → minimal recompile.

Common mistakes

  • Putting function definitions (not just declarations) in the header without inline or static — causes multiple-definition linker errors when included from multiple .cpp files.
  • Forgetting header guards → double inclusion breaks compilation.
  • Defining the same function in two .cpp files (e.g., parse_contact in both parse.cpp and main.cpp) → linker multiple definition.
  • Not linking all object files — undefined reference at link time.
  • Assuming the compiler sees all files at once — it compiles each TU independently.

This answer walks

Practice follow-ups

  1. 01What changes if you add a fourth .cpp file that also needs the Contact type?
  2. 02How would you make the build incremental so only changed files recompile?

Not started

Sign in to save your learning progress.

Sign in to save