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.
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
#endifCompiler 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-archiveThis forces the linker to include all objects from the library, ensuring that the intended strong definitions override weak defaults.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
