Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A pointer to a local struct becomes invalid when the block containing that struct ends. If a function returns or saves that address and code later dereferences it, the program has undefined behavior: it may crash, appear to work, or behave differently after a change in optimization or call sequence. The safe fix is to ensure the struct outlives every use of its address—often by using caller-owned output storage, returning the struct by value, or deliberately allocating storage with clear ownership.

When does a pointer to a local struct become invalid?

A local variable declared in a block normally has automatic storage duration. Its lifetime ends when execution leaves that block, including when its function returns. Returning its address does not extend its lifetime. CERT C Rule DCL30-C states: “The address of an object with automatic storage shall not be returned from a function.” CERT C Rule DCL30-C

struct Point {
    int x;
    int y;
};

struct Point *make_point(void)
{
    struct Point p = { 3, 4 };
    return &p; /* Invalid: p's lifetime ends when this function returns. */
}

int main(void)
{
    struct Point *point = make_point();
    return point->x; /* Undefined behavior: point does not point to a live object. */
}

The pointer value may still exist, but the object it once designated no longer does. Dereferencing that pointer—or otherwise using it to access the expired object—has undefined behavior. A segmentation fault is one possible symptom, not a guaranteed result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is passing a pointer always unsafe?

No. Passing a pointer to a function is ordinarily safe when the pointed-to object remains alive throughout the function’s use. For example, a caller can create a struct and pass its address to a function that fills it. The risk is returning or retaining a pointer to a struct whose lifetime is about to end, then using it afterward. CERT’s guidance includes declaring an object in a suitable, longer-lived scope and passing it to the function that initializes it. CERT C Rule DCL30-C

void initialize_point(struct Point *out)
{
    out->x = 3;
    out->y = 4;
}

int main(void)
{
    struct Point point;
    initialize_point(&point);
    return point.x; /* Valid while point remains in scope. */
}

In this example, point belongs to the caller and remains alive in main while the function writes to it and while main uses it. The function does not keep or return a pointer to its own local object.

Why can the bug seem to work?

Ending an object’s lifetime does not require the bytes at its former address to change immediately. A later call may reuse that storage, or the bytes may happen to retain their earlier values. Either way, an address that refers to an expired object is not made valid by what a debugger or a particular run appears to show. The behavior can vary with call sequence, optimization, and platform; the language-level defect is the expired lifetime. CERT C Rule DCL30-C C storage duration reference

Which pattern should you use instead?

Pattern Who owns the struct? When it fits Important constraint
Caller-owned output The caller The caller can provide an object for a function to fill. The object must remain alive for every use of the pointer.
Return by value The receiving code gets a struct value The function naturally computes and returns a struct result. Returning a value is different from returning the address of a local object.
Allocated storage Defined by the API or program’s ownership rules The object needs a dynamically managed lifetime. Make responsibility for releasing the allocation explicit.
Static storage The static object A single object with program-long lifetime is genuinely appropriate. A function-local static is shared between calls, which can affect reentrancy and independent results.

Use caller-owned output for a caller-controlled lifetime

Let the caller declare the struct and pass its address to an initializer, as in the earlier example. This makes the object’s lifetime and storage visible to the caller. It also allows separate callers or separate scopes to use separate objects.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Return a struct value for a value result

A function can return a struct itself rather than a pointer to its local variable:

struct Point make_point(void)
{
    struct Point p = { 3, 4 };
    return p;
}

int main(void)
{
    struct Point point = make_point();
    return point.x;
}

The returned result is a struct value, not an address into the function’s expired local object. Choose this when a value-returning interface fits the design; do not confuse it with returning &p.

Allocate when the object needs a managed dynamic lifetime

Allocated storage lasts according to the allocation and deallocation operations, not the block lifetime of the function that requested it. The interface should state who owns the returned object and who must release it. For example, in C, a function can allocate a struct and return the pointer, while documenting that the caller must free it when finished. C object lifetime reference

Use static storage only when sharing is intended

A static object has program-long storage duration, so its address does not expire when a function returns. But a function-local static is the same object on each call, not a fresh result per call. Calls can overwrite earlier contents, and shared mutable state can make the function unsuitable for reentrant use. It is not a general substitute for caller-owned or separately allocated objects. C storage duration reference

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How can you detect escaped pointers?

GCC 16.1 documents -Wdangling-pointer for uses of pointers to automatic objects after their lifetime ends. The documented cases include an automatic object’s address escaping through a pointer parameter. Enable the warning explicitly if needed:

Best Value
gcc -Wdangling-pointer -Wall -Wextra -c example.c

This warning is a useful way to find some lifetime mistakes; the absence of a warning does not prove that a pointer is safe. Its documented behavior is specific to GCC 16.1. GCC 16.1 warning options

What is the temporary-struct array-member edge case?

A related issue involves a pointer to an array member of a temporary struct or union expression. The temporary’s lifetime can end before later use of that array, making the access undefined. This is distinct from returning the address of a named local struct: the common problem is still use after the relevant object’s lifetime ends, but the expression and lifetime rules differ. CERT C Rule EXP35-C discusses this case. CERT C Rule EXP35-C

How to choose an ownership pattern

  • Who owns the struct? Make it clear whether the caller, a returned value, or an allocation owns the data.
  • How long must it live? The object must remain alive for every pointer use, not merely until the function that created it returns.
  • Do callers need independent results? Avoid a shared static object when multiple calls must preserve separate results.
  • Who releases allocated storage? State who calls the matching deallocation operation and when.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.