SoFunction
Updated on 2025-04-04

Introduction to strong and weak symbols in C language

The concept of symbols has been mentioned in the detailed explanation of the usage of extern "C" before. It is a mark of variables and functions by the compiler. The rules of the C and C++ code when producing symbols are different. In addition to the differences in the names of the symbols, there are also distinctions between strong symbols and weak symbols.

Let's look at a simple code first

Copy the codeThe code is as follows:

/* */ 
void hello(); 
int main() 

    hello(); 
    return 0; 


Obviously, this code cannot be linked to pass, it will report an error undefined reference to hello, which means hello is not defined, because here we only declare the function hello, but do not define it. But we slightly modify the code as follows
Copy the codeThe code is as follows:

__attribute__((weak)) void hello(); 
int main() 

    hello(); 
    return 0; 


At this time, you will find that the compiled link can be passed, but the operation will report an error, because at this time we declare hello as a weak symbol. When linking, the weak symbol will be treated as 0 by the linker. Execution of a function with address 0 will of course report an error. If you change it to the following code, it will not report an error, but it has no output
Copy the codeThe code is as follows:

__attribute__((weak)) void hello(); 
int main() 

    if(hello) 
        hello(); 
    return 0; 

The compiler believes that functions and initialized global variables are strong symbols, and uninitialized global variables are weak symbols. The linker has the following rules when dealing with strong symbols and weak symbols.

1. In different target files, strong symbols with the same name are not allowed.
2. If a symbol is a strong symbol in a target file and a weak symbol in other target files, select a strong symbol.
3. If a symbol is a weak symbol in all target files, choose the one that takes up the most space. For example, there is double global_var in target file A, int global_var in file B, double takes 8 bytes, and 4 bytes greater than int. After A and B are linked, the symbol global accounts for 8 bytes.

We can simply verify this, there are two files below

Copy the codeThe code is as follows:

/* */ 
char global_var; 
int main() 

    return 0; 

 
/* */ 
int global_var; 

The global variable global_var is not initialized in both files, so they are both weak symbols. Execute the compilation command gcc and use readelf to view the symbol table readelf -s. For the convenience of viewing, we only output the last few lines.

Copy the codeThe code is as follows:

Num:    Value          Size Type    Bind   Vis      Ndx Name 
62: 0000000000600818     4 OBJECT  GLOBAL DEFAULT   25 global_var 
63: 0000000000400474    11 FUNC    GLOBAL DEFAULT   13 main 
64: 0000000000400358     0 FUNC    GLOBAL DEFAULT   11 _init 

The size occupied by the symbol global_var here is 4, which means that the linker chooses an int global_var that takes up a larger space. We will make a little modification to initialize the global variables in it, as follows

Copy the codeThe code is as follows:

/* */ 
char global_var = 1; 
int main() 

    return 0; 

 
/* */ 
int global_var; 

At this time, the global_var in this case is a strong symbol, and the global_var in this case is a weak symbol. After compilation, use readelf to view the symbol table readelf -s as follows

Copy the codeThe code is as follows:

Num:    Value          Size Type    Bind   Vis      Ndx Name 
62: 0000000000600818     1 OBJECT  GLOBAL DEFAULT   25 global_var 
63: 0000000000400474    11 FUNC    GLOBAL DEFAULT   13 main 
64: 0000000000400358     0 FUNC    GLOBAL DEFAULT   11 _init 

At this time, the size occupied by the symbol global_var is 1, which means that the linker has selected a strong symbol.

When writing code, try to avoid different types of symbols, otherwise it will cause very strange and difficult to detect errors. In order to avoid the following measures:

1. Best strategy: Eliminate all global variables
2. Zhongshou: declare global variables as static type and provide interface for access
3. The worst-case scenario: Global variables must be initialized, even if they are initialized to 0.
4. Must-have: Turn on the -fno-common option of gcc, it will prohibit different types of symbols

Having said so much, it seems that you should try to use strong symbols. So what is the use of weak symbols? The so-called existence is reasonable. Sometimes we even need to display and define weak symbols, which will be very useful for library functions. For example, the weak symbols in the library can be overwritten by user-defined strong symbols to realize a customized library version, or when using certain extension functions, the user can define a weak symbol. When the function is linked, the function module can be used normally. If the function module is removed, the program can also be linked normally, but some functions are missing. For example, we can use the following code to determine whether the program has linked the pthread library, thereby deciding what kind of operation to perform.

Copy the codeThe code is as follows:

/* */ 
#include <> 
#include <> 
 
__attribute__((weak)) int pthread_create(  
    pthread_t*,  
    const pthread_attr_t*,  
    void*(*)(void*),  
    void*); 
 
int main() 

    if (pthread_create) 
    { 
        printf("This is multi-thread version!\n"); 
    } 
    else 
    { 
        printf("This is single-thread version!\n"); 
    } 
    return 0; 

The compilation and operation results are as follows

Copy the codeThe code is as follows:

$ gcc  
$ ./ 
This is single-thread version! 
$ gcc -lpthread 
$  
This is multi-thread version!