Chapter 07

Organizing Code with Functions

Prototypes, extern linkage, default values, overloading, function stacks, inline, name mangling, and overload resolution.

In this chapter

  1. Function Prototypes
  2. The extern Keyword
  3. Default Values
  4. Passing Arrays and References
  5. Function Stacks
  6. Inline Functions
  7. Overloading
  8. Overload Restrictions
  9. Name Mangling
  10. Overload Resolution
1

Function Prototypes

Functions must be declared before they are called. The compiler needs to know a function's signature — its name, return type, and parameter types — before it can validate calls to it.

A prototype is a declaration without a body, placed above the caller (typically at the top of the file or in a header):

// Prototype (declaration only)
double Area(double radius);

int main() {
    Area(5.0);  // valid — compiler already knows Area's signature
}

// Full definition can come after
double Area(double radius) {
    return 3.14159 * radius * radius;
}
Prototypes are how header files (.h) work — they declare functions so other translation units can call them, with the actual definition living in a separate .cpp file that is resolved at link time.

2

The extern Keyword

extern specifies that a symbol has external linkage — it can be accessed from a different translation unit. Can be applied to: Global Variables, Functions, Template Declarations.

Explicitly instantiating constants with extern makes them accessible across translation units.

Internal Linkage with static

The counterpart to extern is using the static keyword at file scope. It gives a symbol internal linkage — the opposite of extern — meaning it's only visible within its own translation unit and can't be accessed from other files.

// fileA.cpp
static int counter = 0; // only accessible within fileA.cpp

// fileB.cpp
extern int counter;    // linker error — counter has internal linkage
Note: static inside a function body means something different (persistent local variable). This is specifically about static at the top level of a file.

Non-const Global Variables

A non-const global variable is extern by default. All other declarations of it across files must use extern to tell the linker which definition is the real one:

// fileA.cpp
int i = 42; // declaration AND definition

// fileB.cpp
extern int i; // declaration only — same as i in fileA

// fileD.cpp — ERROR
int i = 43; // LNK2005: 'i' already has a definition
extern int i = 43; // same error (extern ignored on definitions)

Const Global Variables

const is internal by default. To make a global const accessible externally, add extern to both the initial definition and all declarations.


3

Default Values

Parameters can have default values, making them optional at the call site. Defaults must be placed at the end of the parameter list — you cannot have a required parameter after a defaulted one.

double Area(double radius, double pi = 3.14159);

Area(5.0);           // uses default pi
Area(5.0, 3.14159265); // overrides default pi
Define defaults in the prototype (declaration), not the definition. Putting them in both causes a compiler error.

4

Passing Arrays and References

Passing an Array

Arrays decay to a pointer when passed to a function. Both syntaxes below are equivalent — the function receives a pointer, not a copy:

void Print(int numbers[]);   // array syntax
void Print(int* numbers);    // pointer syntax — identical
Because of array decay, the function has no way of knowing the array's length. Always pass the size as a separate parameter, or use std::array / std::vector instead.

Passing by Reference

A reference parameter binds directly to the caller's variable — no copy is made and changes affect the original:

void Double(int& x) {
    x *= 2;
}

int val = 5;
Double(val);  // val is now 10
Pass by Reference
  • No copy — efficient for large objects
  • Modifications affect the original
  • Use const & to pass efficiently without allowing modification
Pass by Value
  • A full copy is made — safe but expensive for large types
  • Modifications do not affect the caller
  • Fine for small primitives (int, float, etc.)

5

Function Stacks

When a function is called, the CPU uses a call stack to track where to return to. The sequence is:

  1. The compiler converts the function call to a CALL instruction containing the callee's address
  2. When CALL executes, the address of the next line (the return address) is saved on the stack
  3. Execution jumps to the function's position in memory
  4. The RET instruction pops the return address off the stack and jumps back to it
Stack overflow occurs when functions call each other so deeply (often via infinite recursion) that the stack runs out of space. The stack has a fixed size — typically a few MB.

6

Inline Functions

The inline keyword is a request to the compiler to paste the function's code directly at the call site instead of generating a CALL instruction. This eliminates the function-call overhead.

inline int Square(int x) {
    return x * x;
}
A hint, not a guarantee. The compiler is free to ignore inline if the function is too complex or inlining would increase code size unacceptably. Modern compilers also inline functions without the keyword when they judge it beneficial.

7

Overloading

Functions with the same name but different parameter lists are called overloaded functions. The compiler picks the right version based on the arguments at the call site.

double Area(double radius);              // circle
double Area(double width, double height); // rectangle

Area(5.0);         // calls circle version
Area(4.0, 3.0);    // calls rectangle version

8

Overload Restrictions

Overloads must differ in their parameter list. The following are not valid bases for overloading:

Not AllowedWhy
Return type onlyThe compiler cannot distinguish int f() from void f() at a call site
static keyword onlyLinkage is not part of the function signature
Default arguments that create ambiguityf(int x) and f(int x, int y=0) — calling f(1) is ambiguous
Don't confuse overloading on a raw pointer vs. an array — int* arr and int arr[] are identical to the compiler and cannot be overloaded against each other.

9

Name Mangling

To support overloading at the linker level, the compiler encodes the parameter types into each function's symbol name — a process called name mangling. This creates unique symbols even when source names are identical.

void func(int x);     // mangled as something like: _Z4funci
void func(double x);  // mangled as something like: _Z4funcd
void func(std::string x); // mangled with the full type encoding
The exact mangling scheme is compiler-specific (GCC, Clang, MSVC all differ). This is why C++ libraries aren't always ABI-compatible across compilers. Wrapping in extern "C" disables mangling, making a function callable from C.

10

Function Overload Resolution

When you call an overloaded function, the compiler follows these steps to pick the right one:

  1. Gather all functions in scope with the same name
  2. Remove any whose number of parameters doesn't match
  3. If more than one remains, select the best match
  4. If there is no clear winner, report an ambiguity error

What is "Best Match"?

Candidates are ranked from best to worst. The compiler selects the highest-ranked match for each argument:

RankMatch TypeNotes
1 (Best)Exact argument matchTypes match exactly
2A promotione.g. charint. No data loss.
3Standard type conversione.g. intdouble. Possible data loss.
4 (Worst)Constructor / user-defined conversion
The overall match is only as strong as its weakest argument. If one argument is an exact match but another requires a conversion, the whole call is ranked at the conversion level.
← Chapter 6 ↑ Index Chapter 8 →