Compilation vs. Linking - C Plus Plus

  1. Why can the resulting executable still contain code based on a buffer size of 64, and what is the appropriate remedy?
    Answer: app.o already contains code compiled from the old header; recompile app.cpp and then relink the updated object. Whenever you see a question about build systems and macros in C++, focus on when each tool runs and what each tool operates on. The compiler and linker are separate stages with completely different inputs and responsibilities. Macros like BUFFER_SIZE are handled entirely by the preprocessor, which runs as part of compilation — before the linker ever gets involved. When app.cpp is compiled, the preprocessor substitutes BUFFER_SIZE with 64, and the compiler bakes that concrete value into the machine code stored in app.o. By the time the object file exists, there is no trace of the macro name — just raw instructions operating on a 64-element array. Changing settings.h afterward has no effect on the already-compiled app.o. The correct remedy, as described in C, is to recompile app.cpp so the preprocessor reads the updated header and produces a fresh object file, then relink that new object. A is a fabrication — the linker has no knowledge of macros whatsoever, let alone a cache of their values. Deleting the executable changes nothing about the stale object file. B misunderstands the build model; headers are not linked. Their contents are textually included during compilation, not at link time. Object files absolutely do contain results derived from headers — that's precisely the problem here. D is nonsense; the linker does not interpret or choose between constant values based on size or optimization preferences. A useful rule of thumb: if a change lives in a header, every translation unit that includes that header must be recompiled — not just relinked — to reflect the update.
  2. Assuming ordinary C++ linkage rules, what is the most likely outcome?
    Answer: Both files compile, but linking cannot find the declared convert(int) definition provided for the call. When studying C++ compilation and linking, you need to track what each translation unit knows versus what the linker sees. These are two completely separate stages with different rules. Here's what happens in this scenario: main.cpp includes api.h, so it sees the declaration long convert(int). The compiler generates a call to a symbol with that exact signature — in C++, name mangling encodes the parameter types into the symbol name, so the linker looks for something like _Z7converti (the mangled form of convert(int)). Meanwhile, implementation.cpp defines long convert(double), which gets mangled into a different symbol, something like _Z7convertd. Both files compile successfully on their own — neither has an error in isolation. But when the linker tries to resolve main.cpp's reference to convert(int), it finds no matching definition anywhere. The result is a linker error: an undefined reference. That makes C correct. A is wrong because the compiler has no problem with main.cpp — it sees a valid declaration and a compatible call. Integer-to-double conversion is irrelevant here; the issue isn't type compatibility, it's symbol resolution at link time. B is wrong because the linker does not perform overload resolution or implicit conversions. It performs symbol matching only. Adapting calls between different function signatures is not within the linker's responsibilities. D is wrong because overload resolution is a compile-time process, not a runtime one. The linker would have to find the symbol first — which it can't. Remember this rule: C++ name mangling means different signatures = different symbols. Overload resolution happens at compile time, and the linker only does exact symbol matching.
  3. Why can the executable link step fail even though compilation succeeded?
    Answer: Compilation verifies that the source can produce object code, but executable linking still requires the program's expected entry point. When you see a question about compilation vs. linking, recognize that these are two distinct phases with different responsibilities. Compilation checks syntax and translates source code into machine-code object files. Linking assembles those object files into a runnable executable — and that requires satisfying additional constraints the compiler never checks. The critical constraint here is the entry point. On most platforms, the runtime expects an executable to begin execution at a symbol called main. When the linker tries to build a standalone executable from helper.o, it searches for main and finds nothing. That missing symbol causes a linker error — even though helper() compiled perfectly. This is exactly what A describes: compilation only confirms that valid object code can be produced; linking still demands the entry point exist. B is wrong because a successful compilation absolutely does not guarantee a complete executable. Compilation and linking are separate stages, and linking can fail for many reasons beyond file permissions — missing symbols being the most common. C is a fabrication. The linker has no opinion about what integer a function returns. It operates on symbols and memory layout, not return values. This choice invents a nonexistent rule to mislead you. D confuses how linkers actually work. Linkers do not require every compiled function to be called. Uncalled functions are simply omitted or retained depending on flags — they are never a linker error by themselves. Study tip: Always mentally separate "did it compile?" from "does it link?" When a question describes a standalone executable, immediately ask yourself: *is main present?* That's the linker's non-negotiable requirement.
  4. Which outcome is most likely, and why?
    Answer: The program compiles and links because the unused declaration does not create a reference requiring resolution. When reading a C++ question about declarations and definitions, your first instinct should be to ask: does anything in the code actually reference this symbol in a way that forces the linker to find it? That distinction drives this entire question. In C++, a function declaration tells the compiler a function exists with a given signature — it makes no promise about where the body lives. A definition actually provides that body. The linker only needs to resolve symbols that are genuinely used: called, addressed, or otherwise referenced in a way that generates a symbol lookup. Since audit is declared but never called and its address is never taken, the compiler emits no reference to it in the object file. The linker therefore never goes searching for it. The program compiles and links cleanly — making C correct. A is wrong because C++ imposes no rule requiring that every declaration have a definition in the same translation unit, or even anywhere at all, unless the function is actually used. Declarations exist precisely so the compiler can type-check call sites without seeing the body yet. B reflects a common misunderstanding: the linker does not scan declarations in source code. It works with symbol tables in object files and libraries, resolving only the symbols that compiled code actually references. An unused declaration leaves no footprint in those tables. D is fiction — there is no C++ runtime loader phase that hunts down every declared function before main starts. Dynamic loading of shared libraries involves a similar concept, but it applies to symbols that are actually linked, not mere declarations. A good study habit: always ask whether code uses a symbol (call, address-of, ODR-use) or merely mentions it. That line separates compilation concerns from linking concerns in almost every related question.
  5. What is the most likely build result?
    Answer: Each file compiles, but the linker reports multiple definitions of the same externally visible function. When you compile multiple .cpp files into a single executable, each translation unit is compiled independently into an object file. The linker's job is then to connect these object files together — and this is where the One Definition Rule (ODR) becomes critical. The ODR states that any external symbol with normal linkage may have exactly one definition across the entire program. It doesn't matter whether the definitions are byte-for-byte identical; the linker simply counts them. A is correct because each file compiles without issue — the compiler only sees its own translation unit — but when the linker attempts to combine both object files, it finds two symbols both named version with external linkage. This is a linker error, not a compiler error, regardless of how identical the function bodies are. B is wrong because compilers do not inspect previously compiled object files during compilation. Each .cpp file is compiled in isolation. Duplicate-symbol detection is purely a linker-phase concern. C is wrong because the linker does not perform source-text comparison or merge definitions based on matching content. Standard C++ linkers enforce the ODR structurally: one external name, one definition, full stop. (Some toolchains support special "COMDAT folding" for identical code, but that's a non-standard optimization, not guaranteed behavior, and it requires specific linkage attributes like inline or templates.) D is wrong because runtime selection between duplicate symbol definitions is not part of standard C++ linking. No "loading order" arbitration mechanism exists for this scenario under normal compilation. Study tip: Whenever you see duplicate function names across .cpp files, immediately think "linker error, ODR violation" — and remember that inline or anonymous namespaces are the standard fixes.
  6. What change is needed for the program to pass both compilation and linking?
    Answer: Include the vendor header during compilation and retain the vendor library as an input during the link step. When building a C++ program that uses an external library, you need to satisfy two separate stages: compilation and linking. The compiler needs to know a function's signature (its name, return type, and parameters) to type-check your call site. The linker needs the actual compiled implementation to resolve that call to a real machine-code address. These are distinct requirements, and mixing them up is exactly what this question tests. D is correct because it addresses both stages properly. Including the vendor's header gives the compiler a declaration like int encode(int);, which is all the compiler needs to verify your call encode(5) is type-safe. Retaining the vendor library as a linker input ensures the linker can find the compiled body of encode and wire everything together. Header + library = successful build. A is wrong because compilers cannot inspect binary libraries to infer function signatures — that's not how the compilation model works. Removing the library would also break linking entirely, leaving encode unresolved. B is wrong because a declaration is not an implementation. Writing int encode(int); in your source tells the compiler the function exists, but the linker still needs the library to find the actual machine code. Omitting the library guarantees a linker error ("undefined reference to encode"). C is wrong because the compiler never reads the linker's input list during compilation. Library order can matter for resolving dependencies between libraries at link time, but it has absolutely no effect on compilation. A useful rule of thumb: headers satisfy the compiler, libraries satisfy the linker — you almost always need both when consuming third-party code.
  7. Which sequence best describes what happens during the build and after the syntax error in support.cpp is corrected?
    Answer: Compilation of support.cpp initially fails; after the syntax correction, linking fails because report remains undefined. When you see a question about build failures in C++, mentally separate the two distinct phases: compilation (each .cpp file processed independently) and linking (object files combined, references resolved). During compilation, the compiler only checks syntax and whether declared names are used consistently — it does not require function definitions to exist yet. In main.cpp, report is declared with void report();, so the compiler is satisfied; it trusts the definition will appear later. In support.cpp, the missing semicolon after return 1 is a syntax error, so that file fails to compile. Once you fix that semicolon, both files compile cleanly into object files. Then the linker tries to resolve every function call to an actual definition. Since report is called in main.cpp but defined nowhere, the linker throws an "undefined reference" error. That's exactly what B describes: support.cpp compilation fails first, and after correction, linking fails due to the missing report definition. A is wrong on two counts: the linker doesn't catch syntax errors (that's the compiler's job), and linkers never auto-supply definitions for missing functions. C incorrectly claims main.cpp fails to compile — remember, a declaration is enough for the compiler; the missing definition only matters at link time. D is wrong because an unused function (helper) is not a linker error; unreferenced symbols are simply omitted or ignored, and the real problem is the undefined report, not the unused helper. A useful mental model: compiler = syntax + declarations; linker = definitions. If something is declared but never defined, you'll always get a linker error, not a compiler error.
  8. Which explanation and next step are most accurate?
    Answer: The command created an object file but did not link an executable; link main.o into an executable before launching it. When working with g++, it helps to understand the two-stage build process: compilation and linking. Compilation transforms source code into machine-readable object files; linking combines object files (and libraries) into a runnable executable. Keeping these stages separate is essential for diagnosing build issues. The -c flag tells g++ to stop after compilation — it produces main.o, an object file containing machine code but no entry point infrastructure, no resolved library references, and no executable header the OS needs to launch a program. To get a runnable application, you must link it: g++ main.o -o main (or equivalent). That's exactly what B describes, making it correct. A is wrong on two counts: -c does not mean "syntax check only" — it performs full compilation into machine code. And the advice to "compile without creating any object file" is backwards; object files are a normal, useful intermediate artifact. C reflects a common misconception that file extensions determine whether something is executable. The issue isn't the .o extension — the file fundamentally lacks the structure of a linked executable. Renaming it accomplishes nothing. D invents a concept that doesn't exist. There is no "library mode" triggered by a main function in an object file, and operating systems cannot "start" raw object files. Object files are inputs to the linker, not launchable artifacts. Study tip: Memorize the two-stage mental model — compile (source → .o) then link (.o → executable). Any time a build succeeds but the output won't run, ask yourself: "Did I actually link?"
  9. The executable links successfully but still prints 2. Which explanation best accounts for this result?
    Answer: The linker used the supplied stale value.o; it does not normally recompile or inspect the revised source file. When working through build-system questions, focus on what each tool actually does: the compiler transforms source files into object files, and the linker combines object files into an executable. Neither tool goes back to re-examine source files the other already handled. Here, the developer recompiled main.cpp but linked against the old value.o — the one compiled from the original source that returned 2. The linker's job is to resolve symbols and stitch object files together; it reads value.o as binary machine code and has no mechanism to peek at value.cpp, detect that the source changed, or trigger a recompilation. So the executable faithfully reflects the stale object file, printing 2 even though the source now says 3. That makes D the correct explanation. A is wrong because compilers don't reverse-engineer previously linked executables to enforce consistency. A compiler reads source and produces object code — it has no awareness of prior builds and certainly doesn't alter your source's return value. B is wrong on two counts: the linker never compares return values between object code and source files, and there is no "choose the smaller value" logic anywhere in the toolchain. This conflates linking with some imaginary semantic analysis. C is wrong because the runtime loader works with compiled binaries, not source files. C++ source code is not interpreted at runtime; by the time execution begins, all code is already machine instructions in the executable. As a study tip: any time a C++ question describes a partial rebuild, ask yourself which object files were actually recompiled. Stale object files are a classic real-world bug — and a reliable exam topic.
  10. Which statement best classifies the resulting failure?
    Answer: The original build can have succeeded; the later failure occurs while loading the linked program for execution. When thinking about build and runtime failures in C++, you need to clearly separate the three distinct phases: compilation, linking, and loading/execution. Each phase can fail independently, and this question tests whether you understand where shared libraries actually come into play. Shared libraries (.so on Linux, .dll on Windows) are deliberately not bundled into your executable at link time. Instead, the linker records a dependency — essentially a promise — that the library will be available when the program is launched. The actual symbol resolution for shared libraries is deferred to the dynamic loader, which runs when you execute the program. This means a project can compile and link cleanly on one machine, yet fail to launch on another if the required library is absent. That is precisely what option D describes, making it correct: the original build genuinely succeeded, and the failure occurs later during the loading phase. Option A is wrong because the compiler never touches shared libraries at runtime — it processes source code into object files and exits. Option B misunderstands the linker's role: the linker runs once at build time and does not re-execute when you launch the program. The linker's job is already done; it's the dynamic loader that takes over at launch. Option C reflects a fundamental misconception — object files and executables do not embed shared library code. That is the whole point of a shared library: the code stays separate and is mapped into memory at load time. As a study tip, remember this mantra: compile → link → load → run. Each arrow is a potential failure point, and shared library errors almost always strike at the "load" arrow, not during the build.

Start this quiz