Question

Understanding __attribute__((constructor)) and __attribute__((destructor)) in C and C++

ccppobjective-cgcccompiler-extension

Question

In GCC-style code, what exactly does __attribute__((constructor)) do, and when does the annotated function run?

I want to understand several details:

  • When exactly is a constructor function executed?
  • Why does the syntax use two parentheses?
  • Is __attribute__ a function, a macro, or a language extension?
  • Does this work in C, C++, or both?
  • Does the function need to be declared static?
  • When does __attribute__((destructor)) run?

For example, in Objective-C:

__attribute__((constructor))
static void initialize_navigationBarImages(void) {
    navigationBarImages = [[NSMutableDictionary alloc] init];
}

__attribute__((destructor))
static void destroy_navigationBarImages(void) {
    [navigationBarImages release];
}

Short Answer

By the end of this page, you will understand what GCC/Clang function attributes like constructor and destructor mean, when those functions run, why the syntax looks unusual, and how they are commonly used in C, C++, and Objective-C codebases. You will also learn the limitations, common pitfalls, and when normal initialization patterns are usually a better choice.

Concept

__attribute__((constructor)) and __attribute__((destructor)) are compiler-specific function attributes supported by compilers such as GCC and Clang.

They are used to mark functions that should run automatically:

  • constructor: before main() starts
  • destructor: after main() ends or during program shutdown

These names can be confusing at first because they are not the same thing as C++ class constructors and destructors.

What __attribute__ is

__attribute__ is not a normal function call and not part of standard C syntax in the original language specification. It is a compiler extension that lets you attach extra information to declarations.

For example, you can attach attributes to:

  • functions
  • variables
  • types

The compiler then changes how it treats that declaration.

When constructor functions run

A function marked with:

__attribute__((constructor))

is scheduled to run automatically before control enters main().

Typical timing:

  • after the program image is loaded
  • after runtime startup begins
  • before main() is called

If the code is inside a shared library, the function typically runs when the library is loaded.

When destructor functions run

A function marked with:

__attribute__((destructor))

runs automatically when the program is shutting down or when a shared library is unloaded.

Typical timing:

  • after main() returns, or after exit() is called
  • during program termination
  • for shared libraries, often when the library is unloaded

Why this matters

This feature is useful for:

  • registering plugins automatically
  • initializing internal global state
  • setting up lookup tables
  • opening and closing low-level resources
  • installing instrumentation or hooks

But it should be used carefully, because automatic startup/shutdown behavior can make code harder to follow and harder to test.

Does it work in C and C++?

Yes, compilers like GCC and Clang support it in:

  • C
  • C++
  • Objective-C

However, it is not portable standard C/C++ syntax. It depends on compiler support.

Does the function need to be static?

No. The function does not need to be static for the attribute to work.

static only affects linkage/visibility:

  • static at file scope means the function is only visible inside that source file
  • without static, the symbol may be externally visible

So static is often used simply because the function is an internal helper and should not be exported.

Why there are two parentheses

The syntax is:

__attribute__((...))

There are two layers because the outer parentheses belong to the __attribute__ syntax, and the inner parentheses contain one or more specific attributes.

Example:

__attribute__((constructor))
__attribute__((unused))
__attribute__((aligned(16)))

The inner part can contain a list of attributes, sometimes with arguments.

So this is valid style:

int x __attribute__((aligned(16), unused));

That is the main reason the syntax has a nested-parentheses form.

Mental Model

Think of the program as a theater performance.

  • main() is the moment the play officially begins.
  • A constructor function is the crew preparing the stage before the curtain opens.
  • A destructor function is the crew cleaning up after the audience leaves.

You did not call these functions directly. The runtime and loader arrange for them to happen automatically.

Another way to think about it:

  • normal functions are tasks you call yourself
  • constructor/destructor functions are tasks you put on the program's automatic startup/shutdown checklist

That is convenient, but it also means the work happens somewhat "behind the scenes," so other developers may not immediately see why some state already exists before main() runs.

Take Quiz

Syntax and Examples

The basic syntax looks like this:

__attribute__((constructor))
void setup(void) {
    /* runs before main() */
}

__attribute__((destructor))
void cleanup(void) {
    /* runs during shutdown */
}

Simple C example

#include <stdio.h>

__attribute__((constructor))
static void startup(void) {
    printf("Startup function runs before main()\n");
}

__attribute__((destructor))
static void shutdown_cleanup(void) {
    printf("Shutdown function runs after main()\n");
}

int main(void) {
    printf("Inside main()\n");
    return 0;
}

Expected output order is typically:

Startup function runs before main()
Inside main()
Shutdown function runs after main()

Objective-C example

__attribute__((constructor))
static void initialize_navigationBarImages(void) {
    navigationBarImages = [[NSMutableDictionary alloc] init];
}

__attribute__((destructor))
static void destroy_navigationBarImages(void) {
    [navigationBarImages release];
}

This means:

  • initialize_navigationBarImages() is called automatically before normal program execution begins
  • destroy_navigationBarImages() is called automatically at shutdown

Multiple attributes

You can attach more than one attribute:

static void helper(void) __attribute__((constructor, used));

With an attribute argument

Some attributes take values:

int buffer[100] __attribute__((aligned(32)));

That helps explain why the syntax has nested parentheses.

Step by Step Execution

Consider this example:

#include <stdio.h>

int global_value = 0;

__attribute__((constructor))
static void init_value(void) {
    global_value = 42;
    printf("constructor: global_value = %d\n", global_value);
}

__attribute__((destructor))
static void destroy_value(void) {
    printf("destructor: global_value = %d\n", global_value);
}

int main(void) {
    printf("main: global_value = %d\n", global_value);
    return 0;
}

Step by step:

  1. The program is loaded.

  2. Before main() starts, the runtime finds functions marked with the constructor attribute.

  3. init_value() runs automatically.

  4. init_value() sets global_value to 42.

  5. init_value() prints:

    constructor: global_value = 42
    
  6. Now main() begins.

  7. main() prints:

    main: global_value = 42
    
  8. main() returns.

  9. During shutdown, functions marked with the destructor attribute are called.

  10. destroy_value() runs and prints:

destructor: global_value = 42

The important lesson is that the constructor changed program state before main() ever ran.

Real World Use Cases

Here are common situations where constructor/destructor attributes are useful.

1. Library initialization

A shared library may need to initialize internal state when loaded.

Examples:

  • set up lookup tables
  • initialize mutexes
  • register logging hooks

2. Plugin registration

A plugin can register itself automatically without requiring the main application to call a setup function manually.

__attribute__((constructor))
static void register_plugin(void) {
    /* add plugin to registry */
}

3. Instrumentation and profiling

Low-level tooling may install tracing, counters, or diagnostics at startup and flush data at shutdown.

4. Resource setup and cleanup

Some systems initialize file descriptors, caches, or native handles early and release them at termination.

5. Objective-C runtime setup

In Objective-C projects, these attributes are sometimes used for early initialization logic in frameworks or app infrastructure code.

Even so, many app-level tasks are better placed in clearer lifecycle hooks when possible.

Take Quiz

Real Codebase Usage

In real projects, developers usually use this feature for small, infrastructure-level setup, not for large application logic.

Common patterns

Internal registration

static void register_feature(void) __attribute__((constructor));

static void register_feature(void) {
    /* insert feature into internal registry */
}

This is common in plugin systems and test frameworks.

Guarding setup code

Because startup code runs automatically, developers often add safety checks.

__attribute__((constructor))
static void init_cache(void) {
    if (cache_already_initialized()) {
        return;
    }
    build_cache();
}

Keeping visibility local

static is frequently added so the helper does not become part of the public API.

Small shutdown cleanup

__attribute__((destructor))
static void flush_logs(void) {
    write_pending_logs();
}

Why teams use it sparingly

Although convenient, automatic startup code has trade-offs:

  • order can be harder to reason about
  • hidden side effects can surprise maintainers
  • testing becomes harder because setup runs before test entry points
  • cross-platform portability may suffer

For that reason, many codebases prefer explicit initialization functions unless automatic registration is truly useful.

Common Mistakes

Beginners often run into these problems.

1. Thinking this is a normal C function call

Broken idea:

__attribute__((constructor));

This does nothing by itself. The attribute must be attached to a declaration.

Correct:

__attribute__((constructor))
static void setup(void) {
}

2. Confusing it with C++ class constructors

This:

__attribute__((constructor))
void setup(void) {
}

is not the same as:

class User {
public:
    User();
};

One is a compiler attribute on a function. The other is a language feature of C++ classes.

3. Assuming static is required

Broken assumption:

__attribute__((constructor))
void setup(void) {
}

This is valid. static is optional and only changes linkage.

4. Doing too much work in startup code

Avoid heavy logic such as:

  • complex I/O
  • large memory allocations
  • code that depends on other subsystems being ready

Startup hooks should usually be small and safe.

5. Relying on compiler-specific behavior without checking portability

__attribute__ is a GCC/Clang-style extension. If your code must compile everywhere, verify support or use conditional compilation.

6. Expecting destructor code to run in every termination case

Destructor functions generally run during normal shutdown, but not every possible abnormal termination guarantees cleanup.

7. Ignoring initialization order issues

If multiple files or libraries each use constructor functions, the order may not be obvious. Hidden dependencies between them can cause bugs.

Comparisons

ConceptWhat it isWhen it runsPortable?Typical use
__attribute__((constructor))Compiler attribute on a functionBefore main()Compiler-specificAutomatic startup setup
__attribute__((destructor))Compiler attribute on a functionAt shutdownCompiler-specificAutomatic cleanup
Normal function callRegular code you call directlyWherever you call itYesClear explicit setup
C++ class constructorObject initialization featureWhen an object is createdYes in C++Initialize object state
C++ class destructorObject cleanup featureWhen an object is destroyedYes in C++Release object resources

Constructor attribute vs explicit init function

ApproachAdvantageDrawback
Attribute-based startupAutomatic, convenientHidden control flow
Explicit init() callClear and testableMust be remembered and called

static vs non-static

Declaration styleMeaning
static void setup(void)Visible only in the current source file
void setup(void)May be visible outside the source file

static does not decide whether the constructor/destructor mechanism works. It only affects symbol visibility/linkage.

Take Quiz

Cheat Sheet

__attribute__((constructor))
void setup(void);

__attribute__((destructor))
void cleanup(void);
  • __attribute__ is a compiler extension, not a normal function.
  • constructor function runs before main().
  • destructor function runs during normal program shutdown.
  • Works in GCC/Clang-based C, C++, and Objective-C.
  • static is optional.
  • static only changes linkage/visibility.
  • This is not the same as a C++ class constructor/destructor.
  • Nested parentheses exist because __attribute__ wraps a list of attributes.

Example:

#include <stdio.h>

__attribute__((constructor))
static void setup(void) {
    printf("before main\n");
}

__attribute__((destructor))
static void cleanup(void) {
    printf("during shutdown\n");
}

Use when:

  • auto-registering modules
  • tiny library initialization
  • low-level setup/cleanup

Avoid when:

  • explicit initialization would be clearer
  • portability is important
  • startup order dependencies are fragile

FAQ

Does __attribute__((constructor)) run before main()?

Yes. Its purpose is to run the function automatically before main() starts.

Does __attribute__((destructor)) run after main()?

Yes, during normal program shutdown, typically after main() returns or exit() is called.

Is __attribute__ part of standard C?

No. It is a compiler extension commonly supported by GCC and Clang.

Can I use __attribute__((constructor)) in C++?

Yes, if your compiler supports GCC-style attributes. It is different from a C++ class constructor.

Why are there two parentheses in __attribute__((constructor))?

The outer parentheses belong to the attribute syntax, and the inner parentheses contain one or more attribute names or attribute arguments.

Does the function have to be static?

No. static is optional and only controls file-local visibility.

Is this safe for large application initialization?

Usually not ideal. It is best for small, low-level setup. Large initialization is often clearer when done explicitly.

Do destructor functions always run?

They usually run during normal shutdown, but you should not assume every abnormal termination path will execute them.

Take Quiz

Mini Project

Description

Build a tiny plugin-style registry in C where features register themselves automatically before main() starts. This demonstrates a realistic use of __attribute__((constructor)): self-registration without needing manual setup calls in main().

Goal

Create a program where two features register automatically at startup, and main() prints the registered feature names.

Requirements

  • Create a global registry that stores a few feature names.
  • Write a helper function to add a feature name to the registry.
  • Use at least two functions marked with __attribute__((constructor)) to register features.
  • Print all registered features inside main().
  • Add one __attribute__((destructor)) function that prints a shutdown message.
Take Quiz

Keep learning

Related questions

View more