代码之家  ›  专栏  ›  技术社区  ›  KamilCuk

使用变量声明/定义实现弱链接是否可移植?

c
  •  3
  • KamilCuk  · 技术社区  · 6 年前

    我最近了解到:

    int a;
    

    at file scope是一个变量声明,默认情况下具有外部链接。

    cat >lib.c <<'EOF'
    #include "lib.h"
    #include <stdio.h>
    #include <stdint.h>
    
    // This is declaration
    // It will be initialized to NULL in case no definition is found
    void (* const lib_callback_pnt)(int);
    
    void lib_callback_default(int a) 
    {
       printf("%s %d\n", __func__, a);
    }
    
    void lib_call(int a)
    {
       printf("%s calling %p\n", __func__, 
        // this is not really portable
        (void*)(uintptr_t)(intmax_t)lib_callback_pnt
       );
       // call callback
       void (* const lib_callback_to_call)(int) = 
          lib_callback_pnt == NULL
          ? lib_callback_default
          : lib_callback_pnt;
       lib_callback_to_call(a);
    }
    
    EOF
    

    cat >lib.h <<'EOF'
    #ifndef LIB_H_
    #define LIB_H_
    
    extern void (* const lib_callback_pnt)(int);
    
    void lib_callback_default(int a);
    void lib_call(int a);
    
    #endif
    EOF
    

    cat >main1.c <<EOF
    #include "lib.h"
    
    int main() {
        lib_call(42);
    }
    
    EOF
    

    cat >main2.c <<'EOF'
    #include "lib.h"
    #include <stdio.h>
    
    static void my_lib_callback(int a)
    {
        printf("Hah! Overwritten lib callback!\n");
    }
    
    // this is definition
    void (* const lib_callback_pnt)(int) = my_lib_callback;
    
    int main() {
        lib_call(42);
    }
    
    EOF
    

    cat >Makefile <<'EOF'
    CC=gcc
    CFLAGS=-Wall -Wextra -pedantic -std=c11
    all:
        $(CC) $(CFLAGS) lib.c main1.c -o main1
        $(CC) $(CFLAGS) lib.c main2.c -o main2
    EOF
    

    lib.c 库我声明了一个函数指针 void (* const lib_callback_pnt)(int) 文库

    然后我有两个程序或用户应用程序,即 main1.c main2.c .

    这个 只需调用调用回调的库函数-回调没有初始化,因此它默认初始化为NULL-我可以在库中比较并适当地调用默认回调/选择操作。

    这个 但是声明函数指针 lib_callback_pnt 初始化-这是一个定义。所有源文件中都有一个此变量的定义,因此链接器不会抱怨多个符号定义。当我们调用库时,指针被初始化,因此用户应用程序 main2

    我们可以编译:

    $ make
    gcc -Wall -Wextra -pedantic -std=c11 lib.c main1.c -o main1
    gcc -Wall -Wextra -pedantic -std=c11 lib.c main2.c -o main2
    

    打电话给:

    $ ./main1 
    lib_call calling (nil)
    lib_callback_default 42
    $ ./main2 
    lib_call calling 0x5627c07871cf
    Hah! overwritten lib callback!
    

    问题:

    2 回复  |  直到 6 年前
        1
  •  1
  •   Eric Postpischil    6 年前

    lib.c , void (* const lib_callback_pnt)(int);

    对象的标识符声明,该对象的文件作用域没有初始值设定项,也没有存储类说明符或存储类说明符 静止的 ,构成 尝试性定义 . 如果一个翻译单元包含一个或多个标识符的暂定定义,而该翻译单元不包含该标识符的外部定义,则该行为与该翻译单元包含该标识符的文件范围声明的行为完全相同,复合类型为翻译单元的结尾,初始值设定项等于0。

    这告诉我们在标准C中, lib_callback_pnt 文库

    而且,什么时候 lib\u回调\u pnt main2.c ,这违反了C 2018 6.9 5:

    如果在表达式中使用用外部链接声明的标识符(而不是作为 大小 _对齐 运算符(其结果是整数常量),在整个程序的某个地方,标识符应有一个外部定义;否则,不得超过一个。

    注意

    Unix中有允许声明 int foo; int foo = 1; 在另一个地方。从技术上讲,这违反了C标准,但在编译器和链接器中可能很常见。

        2
  •  2
  •   aschepler    6 年前

    你的lib.c确实定义了 lib_callback_pnt . C11表示 6.9.2p2

    对象的标识符声明,该对象的文件作用域没有初始值设定项,也没有存储类说明符或存储类说明符 static 尝试性定义

    所以 在lib.c中是一个暂定的定义。因为这个翻译单元不包含任何其他的 lib\u回调\u pnt 如果显式地定义了它,则行为应该与用 = 0 ".

    显然,默认情况下,带有ELF输出的gcc并不完全遵循这个要求。
    gcc -c lib.c; nm lib.o | grep lib_callback_pnt ,我得到:

    0000000000000008 C lib_callback_pnt
    

    man nm 文件解释了“C”是指“公共符号”: