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

inline 不能修饰一个全局函数呗?

  •  
  •   amiwrong123 · 11 天前 · 1488 次点击

    我知道一般操作是 static inline 加函数定义放到头文件里。

    first.c

    inline void functionName(int first, int secend) {/****/ }
    

    second.c

    extern void functionName(int first, int secend);
    
    int main()
    {
        functionName(1, 2);
    
        return 0;
    }
    

    上面两个文件这么写,在 vs2019 上会报错。但是我看一些博客上说,是可以让 inline 修饰非 static 的全局函数。

    所以我这么用是错的呗?

    20 条回复    2022-01-15 10:00:45 +08:00
    wudicgi
        1
    wudicgi  
       11 天前
    > 我知道一般操作是 static inline 加函数定义放到头文件里。
    那为什么不按这个来呢

    如果一定要把函数代码写在 .c 里,就去掉 first.c 里函数定义的 inline 修饰符好了,
    让编译器决定什么时候内联,什么时候调用

    又要内联,又要在其他 .c 里调用,这让编译器很难办啊
    Buges
        2
    Buges  
       11 天前 via Android   ❤️ 1
    既要 inline ,又要 extern ,你这是为难编译器吧。
    wevsty
        3
    wevsty  
       11 天前
    inline 编译器要求随时能找到代码,不然怎么给你内联进去。
    catror
        4
    catror  
       11 天前 via Android
    inline 函数展开后符号都没了,extern 自然就没意义了。
    dangyuluo
        5
    dangyuluo  
       11 天前
    什么时候一般操作就是 static inline 函数放在头文件里了。。
    codehz
        6
    codehz  
       11 天前 via Android
    C++的 inline 语义并不是内联函数(内联是副作用)
    主要作用是允许同符号在多个翻译单元中多次出现(然后假设每次都一致),同时 inline 也要求必须在同翻译单元找到定义(除非没用到)
    extern inline 实际上按标准只能做同一个翻译单元里的前向声明。
    而在不同翻译单元混用 extern (无 inline )和 inline 实际上属于未定义行为,因为不符合前面所说的定义一致原则
    GeruzoniAnsasu
        7
    GeruzoniAnsasu  
       11 天前
    首先…………………………

    extern 是「外部声明」,它的作用是告诉链接器 (!!) 注意不是编译器,到其它文件去找这个符号。如果 extern 直接放在定义的地方,则是告诉链接器「我这个符号应该让所有地方都能看得到」。

    但你这里 extern 只是一个声明。


    第二件事,inline 的意义在于告诉编译器 (!!) 注意是编译器,我这个符号的定义可能会重复出现很多次(因为会被 include 到不同文件里去),你就直接把找到的定义照抄放到调用点就行,不用暴露,不会有文件要链接我。




    所以你正在试图一边让链接器去找某个符号,一边让编译器不要暴露这个符号以供链接……




    「 inline 的非 static 全局函数」是这样的:

    a.h
    inline void g_f(){}

    a.c
    #include "a.h"
    void a(){g_f();}

    b.c
    #include "a.h"
    void b(){g_f();}

    main.c
    extern void a();
    extern void b();
    int main() {a();b();}
    iceheart
        8
    iceheart  
       11 天前 via Android
    声明与定义不一致。
    访问 extern 只要一个声明,访问 inline 需要在同一个编译单元内。
    Jabin
        9
    Jabin  
       11 天前 via Android
    加了 inline ,编译器也不一定按 inline 函数处理
    nightwitch
        10
    nightwitch  
       11 天前
    static inline 其实就是等于 static ,含义是被 static 修饰的函数 /变量在每个编译单元内部可见,如果在头文件里的话每个编译单元都会复制一份。
    inline 现在的含义是允许一个函数 /变量在多个编译单元重复定义,在链接的时候会随机挑选一个符号链上。
    这两个关键词出现在头文件的时候,含义是截然不同的。
    jobmailcn
        11
    jobmailcn  
       11 天前 via Android
    static 作用是把符号限制在本编译单元,这样同名函数多个编译单元定义也不会报错; inline 作用是建议编译器内联展开,但是编译器也可能不内联,如果你想函数体
    jobmailcn
        12
    jobmailcn  
       11 天前 via Android
    static 作用是把符号限制在本编译单元,这样同名函数多处定义也不会报错; inline 作用是建议编译器内联展开,但是编译器也可能不内联,如果内联了,当然不存在符号问题,没内联的话,相当于会在.o 里生成一个弱符号函数,链接时同名的只会取一份。所以按需
    jobmailcn
        13
    jobmailcn  
       11 天前 via Android
    static 作用是把符号限制在本编译单元,这样同名函数多处定义也不会报错; inline 作用是建议编译器内联展开,但是编译器也可能不内联,如果内联了,当然不存在符号问题,没内联的话,相当于会在.o 里生成一个弱符号函数,链接时同名的只会取一份。所以按需选择吧:
    如果你定义在.c 文件里面,建议 static inline ,因为如果其他.c 有跟你同名但不同实现的 inline ,是有风险的;
    如果你定义在.h 里,建议 inline ,在保证只有这个.h 里 inline 定义该函数的前提下,可以使没 inline 时共享同一份实现,减小文件体积,当然你怕出错用 static inline 也行;
    还有个 extern inline ,会在.o 里生成一个强符号,也有用处,但也容易出错,几乎没人用。
    agagega
        14
    agagega  
       11 天前
    你这两个地方声明都不一样啊。不过 C 标准里是有 extern inline 这种东西的
    edimetia3d
        15
    edimetia3d  
       10 天前
    我其实并不记得所有的语言规范细节了,我只记得 C 和 C++不一致, 不同编译器可能不一致,不同语言版本可能不一致.

    我脑海里记下的结论为:

    To live simpler, always use `static inline`

    针对你这个问题, C++ 要求 inline 的函数在每个编译单元都是 inline 且可见的.
    amiwrong123
        16
    amiwrong123  
    OP
       10 天前
    @GeruzoniAnsasu #7
    没想到你这个居然编译通过了,那感觉你这个 inline 就好像起到了 static 的作用呢??因为两处的 g_f(){}的符号应该是一样的应该出产生冲突的,但是却没有。

    然后我根据你的这个程序,写了下面这个:
    first.cpp
    ```cpp
    #include <iostream>
    using namespace std;

    inline void g_f() {
    cout << "first" << endl;
    }

    void a() {
    cout << "a" << endl;
    g_f();
    }
    ```

    second.cpp
    ```cpp
    #include <iostream>
    using namespace std;

    inline void g_f() {
    cout << "second" << endl;
    }

    extern void a();
    void b() {
    cout << "b" << endl;
    g_f();
    }

    int main()
    {
    a();
    b();
    return 0;
    }
    ```
    然后打印出来这个,这个怎么解释呢?😂
    a
    second
    b
    second
    amiwrong123
        17
    amiwrong123  
    OP
       10 天前
    @codehz #6
    老哥你看一下我上面那个程序,我这个程序应该就是你说的:允许同符号在多个翻译单元中多次出现
    g_f 函数在两个翻译单元都出现了。

    本来我以为会打印出来
    a
    first
    b
    second
    但是却不是。

    我不知道该怎么解释我的程序结果了
    amiwrong123
        18
    amiwrong123  
    OP
       10 天前
    @jobmailcn #13
    老哥你看一下我上面那个程序,我这个程序应该就是你说的:如果内联了,当然不存在符号问题。

    但是还是有点不明白,调用 a()的时候,不应该用的是 first.cpp 里的函数定义吗,怎么还是用的 second.cpp 里的函数定义。
    amiwrong123
        19
    amiwrong123  
    OP
       10 天前
    first.cpp
    ```cpp
    #include <iostream>
    using namespace std;

    inline void g_f() {
    cout << "first" << endl;
    }

    void a() {
    cout << "a" << endl;
    g_f();
    }
    extern void b();

    int main()
    {
    a();
    b();
    return 0;
    }
    ```

    second.cpp
    ```cpp
    #include <iostream>
    using namespace std;

    inline void g_f() {
    cout << "second" << endl;
    }

    void b() {
    cout << "b" << endl;
    g_f();
    }
    ```

    我把 main 函数放到 first.cpp 里,也是一样。都是打印 second 。不过是在 VS2019 里。

    然后我用 Ubuntu 下的 gcc 又试了一下,main 函数分别 first 里或 second 里,却是都打印的是 first 了。看来是一个随机的情况,也就是楼上说的“inline 现在的含义是允许一个函数 /变量在多个编译单元重复定义,在链接的时候会随机挑选一个符号链上。”
    codehz
        20
    codehz  
       10 天前 via Android
    @amiwrong123 注意括号 然后假设每次都一致,这是前提条件。。。
    来看标准吧,c++20 标准 6.3 节 13 小节
    There can be more than one definition of a
    ... -inline function or variable ([dcl.inline]), ...
    这是允许 inline 函数或变量有多个定义(即使同一个 TU )再看需要满足的要求
    (13.8)
    Each such definition shall consist of the same sequence of tokens, where the definition of a closure type is considered to consist of the sequence of tokens of the corresponding lambda-expression.
    每个定义都需要有相同的 token 序列
    (13.9)
    In each such definition, corresponding names, looked up according to [basic.lookup], shall refer to the same entity, after overload resolution ([over.match]) and after matching of partial template specialization ([temp.over]), except that a name can refer to ...... same entity in all definitions of D.
    每个定义所引用的符号在重载解析,模板偏特化之后除了某些特殊情况外都必须解析到相同的实体上
    最后
    If these definitions do not satisfy these requirements, then the program is ill-formed; a diagnostic is required only if the entity is attached to a named module and a prior definition is reachable at the point where a later definition occurs
    如果这些条件没有满足要求,那么程序是不合法的,而也只有特殊情况(同一个模块且在不一致定义要在同一个单元)才要求编译器给出诊断(报告错误)
    所以一个不要求诊断的错误的代码(甚至不属于 UB )给出任何行为都是符合预期的(链接期随意选择是结果的一部分而不是原因,写程序不是自然科学,不要用实验来推测原因)
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4333 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 02:40 · PVG 10:40 · LAX 18:40 · JFK 21:40
    ♥ Do have faith in what you're doing.