Chapter 13 of 24

13. Scope, lifetime, and linkage

Where names and objects exist

Three questions help explain this topic: where can code use a name, how long does the named object exist, and can another source file refer to that name? These are scope, storage duration (often discussed as lifetime), and linkage.

  1. A name declared inside a function block is normally usable only in that block.
  2. A local object's lifetime usually ends when execution leaves its block.
  3. File-scope declarations and keywords such as static affect how long objects live or whether names can be shared across source files.

Do not assume that a variable remains valid just because a pointer still contains its address. First identify the declaration, then the lifetime of its object, and finally which parts of the program can name it.

Understanding where a name is visible and how long an object exists prevents
many bugs.

Local variables normally have automatic storage duration. A local static
variable persists between function calls. Global variables have static
storage duration, but widespread mutable global state makes programs harder
to understand and test.

A practical lifetime model

For every object, ask when it comes into existence and when it stops existing.
An automatic local object normally exists during the function or block
execution where it is declared. A static object exists for the whole
program. Allocated storage exists from a successful allocation until it is
released. A pointer does not extend the lifetime of the object it points to.

For example, returning a pointer to a local array is invalid because the
array's lifetime ends when the function returns. Returning a structure by
value is safe because the caller receives its own value. Alternatively, the
caller can provide a destination buffer, or the function can allocate storage
and document that the caller must free it.

struct Pair {
    int first;
    int second;
};

struct Pair make_pair(int first, int second)
{
    struct Pair result = {first, second};
    return result;  // Return a value, not the address of a local object. 
}

By contrast, returning &result would be invalid because the local object
ceases to exist when the function returns.

Scope and lifetime are related but different. Scope answers, “Where can this
name be used in the source?” Lifetime answers, “When does this object exist?”
A block-scope static variable has a local name but a lifetime that lasts for
the entire program. File-scope static controls linkage, making a name
private to that source file.

Checkpoint: For a local integer, a file-scope variable, and a malloc
allocation, state the lifetime and who is responsible for ending it.

The static and extern keywords

The same keyword can affect different properties depending on where it
appears:

Prefer one definition of shared external state and declarations in a header
only when sharing that state is part of the module interface. For small
programs, passing data through parameters is usually clearer than exposing
globals.

 // File scope: this object has internal linkage. 
static int helper_count;

int next_count(void)
{
     // Local name, but the object persists between calls. 
    static int calls;
    ++calls;
    helper_count = calls;
    return helper_count;
}

This belongs in one .c file. Another source file cannot name
helper_count, and the local name calls is visible only inside the
function.

Parameters and local object lifetime

An array parameter declaration such as int values[] is adjusted to a
pointer parameter; it does not copy an array or carry its length. A structure
parameter declared as struct Point point is passed by value. const
qualifiers communicate whether the function is allowed to modify through a
parameter, but they do not extend an object's lifetime.

Automatic variables declared inside a loop body are created as execution
enters their block and cease to exist when it leaves. A pointer saved outside
that block must not be used to access the expired object. For allocated
storage, the pointer can outlive a function call, but the allocation remains
valid only until it is freed.

#include <stddef.h>

void fill_first(int values[], size_t length)
{
    if (length > 0) {
        values[0] = 99;
    }
}

int main(void)
{
    int data[3] = {0};
    fill_first(data, 3);
    return data[0] == 99 ? 0 : 1;
}

The parameter refers to caller-owned storage; passing its length lets the
function check that an element exists before writing.


Suggested topics

0% read