Fundamentals 20 min read

How Weak Symbols and Weak References Simplify Cross‑Platform C Development

This article explains the concept of weak symbols and weak references in C, demonstrates their behavior with practical examples, and shows how they can be leveraged to create maintainable, cross‑platform embedded drivers while addressing compiler differences and linker nuances.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How Weak Symbols and Weak References Simplify Cross‑Platform C Development

Weak Symbols

Weak symbols are created by adding the __attribute__((weak)) qualifier to a function or variable definition, allowing the symbol to be overridden by a strong definition with the same name.

__attribute__((weak)) void test_weak_attr(void) {
    printf("Weak Func!
");
}

Purpose and Example

In driver development, strong symbols tie the code to a specific device implementation, making maintenance difficult when supporting multiple devices. Weak symbols act as default implementations that can be replaced by strong definitions without modifying the original driver code.

Example files:

// test_weak_attr.c
__attribute__((weak)) void test_weak_attr(void) {
    printf("this is a weak func
");
}
// main.c (strong override)
void test_weak_attr(void) {
    printf("this is a strong func
");
}

void app_main(void) {
    printf("init done
");
    test_weak_attr();
}

When the strong version is present, the program prints this is a strong func . If the strong definition is removed, the weak implementation is used, printing this is a weak func .

Weak References

A weak reference creates an alias to another symbol without providing an implementation. It is declared with __attribute__((weakref("target"))).

static void test_weakref(void) __attribute__((weakref("test_weak_ref")));

If the target function test_weak_ref is not defined, the reference resolves to NULL at link time, allowing the program to check for its existence at runtime.

Cross‑Platform Solution Using Weak Functions

To support multiple hardware platforms (e.g., STM32 Standard Library, HAL, LL, RT‑Thread), the code is split into a Common layer containing shared logic and a Port layer providing platform‑specific APIs. Several strategies are discussed:

Manually adding platform‑specific source files.

Conditional compilation with macros such as #if defined(USE_STM32_STD_LIB) to select the appropriate GPIO API.

Function pointers passed from Port to Common.

Extern declarations implemented in the Port.

Weak functions defined in Common as default implementations that can be overridden by strong definitions in the Port.

Example of a weak macro supporting various compilers:

#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 5000000)
#define MY_WEAK __attribute__((weak))
#elif defined(__IAR_SYSTEMS_ICC__)
#define MY_WEAK __weak
#elif defined(__MINGW32__)
#define MY_WEAK
#elif defined(__GNUC__)
#define MY_WEAK __attribute__((weak))
#endif
#ifndef MY_WEAK
#define MY_WEAK
#endif

Compiler Support

Most GCC‑compatible compilers and ARMCC support __attribute__((weak)). MSVC lacks full weak‑symbol support; it only provides #pragma weak on x86/x64, not on ARM, and the feature has known bugs.

Port Classification with Weak

Ports are categorized as:

Core ports without a default implementation – must be provided, otherwise the system cannot run.

Core ports with a default implementation – can be overridden when customization is needed.

Edge ports without a default – optional; a weak stub can emit a runtime error if used without a proper implementation.

Examples of weak default implementations:

MY_WEAK void port_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}
MY_WEAK void port_thread_start(port_thread_t *thread) {
#ifdef __linux
    pthread_mutex_lock(&thread->mutex);
    pthread_cond_signal(&thread->cond);
    pthread_mutex_unlock(&thread->mutex);
#elif defined(USE_FREERTOS)
    vTaskResume(thread->thread);
#else
    #error "port_thread_start() needs user implementation"
#endif
}

For edge ports like port_reboot, a weak stub can abort with an informative message:

MY_WEAK void port_reboot(void) {
    printf("Error: port_reboot() requires user implementation
");
    while (1);
}

Linker Issues with Weak Symbols

When linking static libraries with GCC, the linker may stop after finding the first definition, causing a strong definition to be ignored if a weak one appears first. The workaround is to link the whole archive:

-Wl,--whole-archive

This forces the linker to include all objects from the library, ensuring that the intended strong definitions override weak defaults.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

C programmingCross‑platform developmentembedded systemsLinkercompiler attributesweak symbols
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.