V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xuelang
V2EX  ›  C++

C++ 链接符号决议 -- 自以为懂了,结果...

  •  
  •   xuelang ·
    selfboot · 2023-09-19 16:09:34 +08:00 · 1915 次点击
    这是一个创建于 432 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在 C++ 中使用 Protobuf 诡异的字段丢失问题排查这篇文章中,分析过因为两个一样的 proto 文件,导致链接错了 pb ,最终反序列化的时候丢失了部分字段。当时也提到过符号决议的过程,不管是动态链接还是静态链接,实际用的都是靠前面的库的符号定义。本来以为对这里的理解很深入了,直到最近又遇见一个奇怪的“符号重定义”问题。

    ➜ tree
    .
    ├── demoA
    │   ├── libDemoA.a
    │   ├── sum.cpp
    │   ├── sum.h
    │   └── sum.o
    ├── demoB
    │   ├── libDemoB.a
    │   ├── sum.cpp
    │   ├── sum.h
    │   └── sum.o
    └── main.cpp
    

    问题: demoA/sum.h 和 demoB/sum.h 如果都是只有 sum 函数,那么无论哪个先链接,都不会有问题。 但是一旦里面有 class ,定义不一样,那么就会出错。

    更多可以看这篇: 深入理解 C++ 链接符号决议:从符号重定义说起

    23 条回复    2023-11-12 09:36:49 +08:00
    codehz
        1
    codehz  
       2023-09-19 16:28:34 +08:00
    没报错的情况是不是可以报告成连接器的 bug 呢,至少应该给个警告(
    xuelang
        2
    xuelang  
    OP
       2023-09-19 16:41:45 +08:00
    @codehz 不是 bug ,是 feature 的
    codehz
        3
    codehz  
       2023-09-19 17:23:40 +08:00
    @xuelang 怎么就不是 bug 了,根据那啥 ODR 规则,就不能存在同名的符号,但具体到这个情况,倒是有可能被归类为无须诊断的非良构程序
    不能因为恰好通过编译链接,能运行,它就突然变成良构的了
    zmcity
        4
    zmcity  
       2023-09-19 17:43:58 +08:00
    因为链接是通过函数签名选需要链接的产物(.o ),而没有把每个签名对应的函数单独提取出来。
    你先链了 A ,就会把 A.o 链接到产物里,然后 A 中又没有 DemoB(int, int)的签名,所以会把 B.o 链上,就会 multiple definition 。
    yolee599
        5
    yolee599  
       2023-09-19 17:55:18 +08:00
    为啥你们写 C/C++ 那么多奇怪的问题,就是骚操作太多,我写 C 写了几年都没这么多屁事
    xuelang
        6
    xuelang  
    OP
       2023-09-19 18:00:00 +08:00
    @codehz 你说的也有道理,严格来说确实不能有同名符号,至少也得给个 warning 。不然换个链接顺序,结果可能都不一样,这个是不符合预期。
    xuelang
        7
    xuelang  
    OP
       2023-09-19 18:00:42 +08:00
    @zmcity 这里 a 是静态库归档文件,里面有很多 .o ,如果某个 .o 里面的符号都没用到,就会丢掉。
    xuelang
        8
    xuelang  
    OP
       2023-09-19 18:01:14 +08:00
    @yolee599 c 和 c++ 半斤八两吧。。 奇怪问题也不少
    cnbatch
        9
    cnbatch  
       2023-09-19 19:25:43 +08:00
    说实话,既然都用 C++了,那就直接用 namespace 分隔开嘛,何必这样搞成个歧义二进制折磨自己
    xuelang
        10
    xuelang  
    OP
       2023-09-19 19:58:00 +08:00
    @cnbatch 理想很美好。。 现实庞大代码仓库里的代码可不受金科玉律控制,各种奇葩问题都有。
    geelaw
        11
    geelaw  
       2023-09-19 20:10:33 +08:00 via iPhone   ❤️ 2
    无论是看代码还是问 ChatGPT 却不查证都是非常糟糕的学习方法,第一步应该是理解 C++ 标准是如何规定的。

    文章里无论是 int sum(int, int) 还是 class Demo 都是非常严重的 ODR violation 。

    在 [basic.def.odr]/14 里规定了 (14.1) 非内联非模板函数在多个翻译单元中有定义时 (14) 程序不良,且在非模块中无需报错,这适用于 sum 的情况。

    在 [basic.def.odr]/14 里规定了 (14.2) 多个翻译单元中有定义的 class 如果不满足 (14.4) 在所有可达的翻译单元中定义是相同的记号( token )序列,则 (14) 程序不良,且在非模块中无需报错,这适用于 class Demo 的情况。

    至于某个具体的编译器、链接器产生的什么行为,不过是巧合罢了。
    xuelang
        12
    xuelang  
    OP
       2023-09-19 20:15:17 +08:00
    嗯嗯,十分同意,标准才是硬道理,写代码也都要注意到这些。
    就算 sum 这种能编译,链接成功,也是一个很坏的代码习惯。能正常运行的,结果符合预期的,也不一定就是对的实现,可能是编译器的巧合行为,说不定后面就不行了。
    allAboutDbmss
        13
    allAboutDbmss  
       2023-09-19 20:16:16 +08:00
    rust 有类似问题吗?
    geelaw
        14
    geelaw  
       2023-09-19 20:20:00 +08:00 via iPhone   ❤️ 1
    满足 ODR 的程序,如果同一个名字在多个翻译单元里有定义,那么任意可达的定义都是等价的,因此可以随便选(比如第一个赢)。

    随便选最有意义的作用是模板,因为每个使用了某个模板的翻译单元必须包含那个模板的完整定义(需要 SFINAE 和 name lookup ),不能让模板声明在 .hh 里实现在 .cc 里。

    更早的时候链接时优化还不流行,因此内联( C++ 意义的 inline )函数可以促进函数在编译时被内联(优化意义的 inline )。

    链接并不是“决议”工具。
    xuelang
        15
    xuelang  
    OP
       2023-09-19 20:25:59 +08:00
    @geelaw 链接并不是“决议”工具?这里怎么理解
    tool2d
        16
    tool2d  
       2023-09-19 21:46:40 +08:00   ❤️ 2
    这算是 gcc 的问题,你换 vc 一开始 sum 就不能链接成功。

    符号一样,什么前面的函数体去覆盖后面的函数体,对于微软来说,是完全不存在的事情。

    还有一点,linux so 动态链接库里的符号可以是未决的,但是 dll 缺一个函数,都没办法生成。光是这点,微软就已经领先 100 年。
    geelaw
        17
    geelaw  
       2023-09-19 22:09:16 +08:00 via iPhone   ❤️ 1
    @xuelang #15 链接并不能用来挑选(“决议”) A 的这个定义或那个定义,A 自始自终都只有一个定义。
    xuelang
        18
    xuelang  
    OP
       2023-09-20 09:27:52 +08:00
    @geelaw 链接过程是用来找到符号的定义,英文是 resolution ,翻译成决议也还可以。
    xuelang
        19
    xuelang  
    OP
       2023-09-20 09:28:30 +08:00
    @tool2d vc 没用过,不过这里微软做法貌似是合理些。
    geelaw
        20
    geelaw  
       2023-09-20 12:46:27 +08:00   ❤️ 1
    @xuelang #18 汉语“决议”是“会议所确定的较重大的事项”的意思,在计算机技术方面应该理解为复杂的选择过程,比如“重载决议”是可接受的说法,因为要根据形参列表和实参列表从多个可能的选项里根据一系列不那么直接的规则选出最合适的。链接里的 symbol resolution 的 resolution 应该译作“解析”,因为这个过程只是简单地查找符号的定义,不存在复杂的规则。
    xuelang
        21
    xuelang  
    OP
       2023-09-20 13:04:17 +08:00
    @geelaw 嗯嗯,这样解释确实是,符号解析更适合这里的场景。
    MaskRay
        22
    MaskRay  
       2023-11-11 15:51:18 +08:00   ❤️ 1
    ODR violation 参见 https://maskray.me/blog/2022-11-13-odr-violation-detection 我打算把这个检测工具写完……

    > 还有一点,linux so 动态链接库里的符号可以是未决的,但是 dll 缺一个函数,都没办法生成。光是这点,微软就已经领先 100 年。

    这个可以怪 ELF linkers 默认选得不好(-z undefs)。如果链接 DSO 时用-z defs 就不会未决了

    https://maskray.me/blog/2021-06-13-dependency-related-linker-options#z-defs
    xuelang
        23
    xuelang  
    OP
       2023-11-12 09:36:49 +08:00
    @MaskRay 哇,之前看这块就搜到了大佬的博客,原来也在 v 站这里。你在 编译链接这里研究好深,写了好多文章,膜拜~
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2757 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 10:15 · PVG 18:15 · LAX 02:15 · JFK 05:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.