Question
Understanding __attribute__((constructor)) and __attribute__((destructor)) in C and C++
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 afterexit()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:
staticat 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
constructorfunction is the crew preparing the stage before the curtain opens. - A
destructorfunction 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.
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 beginsdestroy_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:
-
The program is loaded.
-
Before
main()starts, the runtime finds functions marked with theconstructorattribute. -
init_value()runs automatically. -
init_value()setsglobal_valueto42. -
init_value()prints:constructor: global_value = 42 -
Now
main()begins. -
main()prints:main: global_value = 42 -
main()returns. -
During shutdown, functions marked with the
destructorattribute are called. -
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.
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
| Concept | What it is | When it runs | Portable? | Typical use |
|---|---|---|---|---|
__attribute__((constructor)) | Compiler attribute on a function | Before main() | Compiler-specific | Automatic startup setup |
__attribute__((destructor)) | Compiler attribute on a function | At shutdown | Compiler-specific | Automatic cleanup |
| Normal function call | Regular code you call directly | Wherever you call it | Yes | Clear explicit setup |
| C++ class constructor | Object initialization feature | When an object is created | Yes in C++ | Initialize object state |
| C++ class destructor | Object cleanup feature | When an object is destroyed | Yes in C++ | Release object resources |
Constructor attribute vs explicit init function
| Approach | Advantage | Drawback |
|---|---|---|
| Attribute-based startup | Automatic, convenient | Hidden control flow |
Explicit init() call | Clear and testable | Must be remembered and called |
static vs non-static
| Declaration style | Meaning |
|---|---|
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.
Cheat Sheet
__attribute__((constructor))
void setup(void);
__attribute__((destructor))
void cleanup(void);
__attribute__is a compiler extension, not a normal function.constructorfunction runs beforemain().destructorfunction runs during normal program shutdown.- Works in GCC/Clang-based C, C++, and Objective-C.
staticis optional.staticonly 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.
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.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.