我是靠谱客的博主 幸福小伙,最近开发中收集的这篇文章主要介绍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

最后

以上就是幸福小伙为你收集整理的static和inline在C++头文件中的使用的全部内容,希望文章能够帮你解决static和inline在C++头文件中的使用所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部