static和inline在C++头文件中的使用

先上例子:

// foo.hpp
#ifndef FOO_HPP
#define FOO_HPP
void foo()
{
    //
}
#endif

// a.cpp
#include "foo.hpp"
void func_a()
{
    foo();
}

// b.cpp
#include "foo.hpp"
void func_b()
{
    foo();
}

// main.cpp
extern void func_a();
extern void func_b();
int main()
{
    func_a();
    func_b();

    return 0;
}


编译:

$ arm-linux-gnueabihf-g++ a.cpp b.cpp main.cpp -save-temps -O0 -o main
b.o: In function `foo()':
b.cpp:(.text+0x0): multiple definition of `foo()'
a.o:a.cpp:(.text+0x0): first defined here
collect2.exe: error: ld returned 1 exit status



As is known to us all,上面这个例子链接时会报错:foo函数重复定义。因为a.cpp和b.cpp都包含了foo.hpp文件,相当于foo这个全局函数在a.cpp和b.cpp都有定义了。用nm命令就可以看到a.o和b.o中都存在相同的T _Z3foov这个符号,大写的T说明它是global(external)的,如下所示:

$ nm a.o
00000000 T _Z3foov
0000000c T _Z6func_av

$ nm b.o
00000000 T _Z3foov
0000000c T _Z6func_bv


既然foo作为一个全局函数会导致重复定义,那么自然很容易想到,假如加上static关键字限定其作用域,是不是就没有问题了呢?
尝试一下,链接ok,再看一下目标文件中的符号,a.o和b.o中都存在t _ZL3foov,哈哈,小写的t说明这就是个local的符号,所以在最终生成的main文件中存在2个地址不同的t _ZL3foov:

$ nm a.o
0000000c T _Z6func_av
00000000 t _ZL3foov

$ nm b.o
0000000c T _Z6func_bv
00000000 t _ZL3foov

$ nm main
# ...
000083c8 T _start
000084b4 T _Z6func_av
000084cc T _Z6func_bv
000084c0 t _ZL3foov
000084a8 t _ZL3foov
# ...


However,由于static函数在每个编译单元都有独自一份拷贝,这也显然会造成问题。假如初衷只是将函数定义写在头文件中的话,inline则是更好的选择,详细的说明可以参考这个:Jon Lee的知乎回答。下面看看改为inline后的编译结果:_Z3foov是一个weak symbol。

$ nm a.o
00000000 W _Z3foov
00000000 T _Z6func_av

$ nm b.o
00000000 W _Z3foov
00000000 T _Z6func_bv

$ nm main
# ...
000083c8 T _start
000084b4 W _Z3foov
000084a8 T _Z6func_av
000084c0 T _Z6func_bv
# ...


BTW,在win10下用g++编译的话,会有些不同:a.o和b.o中各有T _Z3foov,最终的main.exe中则是有一个U _Z3foov和一个T _Z3foov。U代表的含义是:

该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。

$ nm a.o
#...
0000000000000000 T _Z3foov
0000000000000000 T _Z6func_av

$ nm b.o
#...
0000000000000000 T _Z3foov
0000000000000000 T _Z6func_bv

$ nm main.exe
                 U _Z3foov
00000001004016f0 T _Z3foov
0000000100401080 T _Z6func_av
00000001004010a0 T _Z6func_bv


在这个浅谈 C++ 中的 inline (1) - 知乎中也对不同编译器中inline的处理方式进行了测试,BTW,文中还测试了在不同的 .cpp 中定义相同的 inline(但不是 static)的 函数(但不是 static),会由于编译顺序不同->链接阶段的符号冲突和混乱链接->导致最终输出结果不一致的情况,说明——

inline 函数应该老老实实在 .h 中定义,而不是为了赶时间手动拷贝到不同的 .cpp 中并顺手修改,以免造成 Debug / Release 模式下结果不同

想要测试的话,把a.cpp和b.cpp做如下更改,分别用不同的编译命令(注意编译文件的顺序和优化选项)看下输出结果就明白啦。

 g++ a.cpp b.cpp main.cpp -save-temps -O0 -o main
 g++ b.cpp a.cpp main.cpp -save-temps -O0 -o main
 g++ a.cpp b.cpp main.cpp -save-temps -O3 -o main
 g++ b.cpp a.cpp main.cpp -save-temps -O3 -o main

// a.cpp
#include <iostream>
inline void foo()
{
    std::cout << "foo a" << std::endl;
}
void func_a()
{
    foo();
}

// b.cpp
#include <iostream>
inline void foo()
{
    std::cout << "foo b" << std::endl;
}
void func_b()
{
    foo();
}



Finally小结,还Jon Lee的知乎回答回答说得好:

谨慎使用 static:如果只是想把函数定义写在头文件中,用 inline,不要用static。
inline最大的用处是:非template 函数,成员或非成员,把定义放在头文件中,定义前不加inline ,如果头文件被多个translation unit(cpp文件)引用,ODR会报错multiple definition。
inline 函数的定义不一定要跟声明放在一个头文件里面:定义可以放在一个单独的头文件 .hxx 中,里面需要给函数定义前加上 inline 关键字,原因看下面第 2.点;然后声明 放在另一个头文件 .hh 中,此文件include 上一个 .hxx。这种用法 boost里很常见:优点1. 实现跟API 分离,encapsulation。优点2. 可以解决有关inline函数的循环调用问题。


————————————————
版权声明:本文为CSDN博主「winwonnn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013213111/article/details/122097285

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++,`static`关键字有多个作用,主要用于存储类别(storage class)和作用域控制。以下是静态关键字的主要用途: 1. 静态存储类别(Storage Class): - **Static Local Variables**: 在函数内部使用`static`声明的变量,其生命周期从定义开始直到程序结束,且仅有一个副本,即使函数被多次调用。这意味着它们只在首次调用时初始化,后续调用时保持不变。 - **Static Member Variables**(Class Variables):类静态成员变量在整个类实例化期间共享,无论创建了多少个对象,每个对象都有自己的静态成员函数的引用。 - **Static Member Functions**: 类静态成员函数不依赖于类的任何实例,可以直接通过类名调用。 2. 静态局部作用域: - 当静态变量出现在函数或代码块的开始时,它只在当前作用域可见,每次进入该作用域都会初始化一次。 3. 文件全局(File Scope): - 在头文件(`.h`)声明的静态变量或函数,会在整个程序全局可见,但仅在一个编译单元(translation unit)内初始化一次。 4. 内联函数: - `inline`关键字配合`static`可以提高内联函数的性能,因为编译器可能会在所有调用的地方直接替换内联代码,而不仅仅是`inline`声明的一次。 5. 链表节点的静态属性: - 在某些数据结构如静态链表静态成员可用于表示链表的头部或尾部元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值