先做他个天猫网站,明空网络做网站好不好,升级wordpress导入工具,网站被k的迹象ODR细节有点复杂#xff0c;跨越各种情况。基本内容如下#xff1a; ●普通#xff08;非模板#xff09;的noninline函数和成员函数、noninline全局变量、静态数据成员在整个程序中都应当只定义一次。 ●class类型#xff08;包括structs和unions#xff09;、模板…ODR细节有点复杂跨越各种情况。基本内容如下 ●普通非模板的noninline函数和成员函数、noninline全局变量、静态数据成员在整个程序中都应当只定义一次。 ●class类型包括structs和unions、模板包括偏特化但是不包括全特化、inline函数和inline变量在单个编译单元中最多定义一次并且这些定义应该完全一样。 一个编译单元是源文件预处理后的结果也就是说它包含#include指令和宏拓展后的内容。与C语言一样C中所有的预处理指令都是以字符#开头这些指令在编译之前进行处理。 本文将讨论一种违背ODR的典型情况不同编译单元中包含同名结构体结构体内函数定义相同但数据成员不同MyClass1.h
#ifndef MYCLASS1_H
#define MYCLASS1_Hclass MyClass1
{
public:MyClass1();
};#endif // MYCLASS1_H
MyClass1.cpp
#include MyClass1.hstruct Point
{void setValue(int x, int y){this-x x;this-y y;}int z;int x, y;
};MyClass1::MyClass1()
{Point p;p.setValue(0, 0);
}
MyClass2.h
#ifndef MYCLASS2_H
#define MYCLASS2_Hclass MyClass2
{
public:MyClass2();
};#endif // MYCLASS2_H
MyClass2.cpp
#include MyClass2.h#include iostreamstruct Point
{void setValue(int x, int y){this-x x;this-y y;}int x, y;
};MyClass2::MyClass2()
{Point p;p.setValue(10, 10);std::cout p.x std::endl;
}
main.cpp
#include MyClass1.h
#include MyClass2.hint main()
{MyClass1 cl1;MyClass2 cl2;return 0;
}
显然我们的预期打印结果是10。 本人不同喜欢敲指令这里IDE使用Qt CreatorQt版本是5.12.6 MinGW32编译器为g。 在Debug模式下且三个cpp文件的编译顺序是MyClass1.cpp-MyClass2.cpp-main.cpp如下图所示 实际打印结果却是0 依然在Debug模式下编译顺序改为MyClass2.cpp-MyClass1cpp-main.cpp如下图所示 实际打印结果是预期值10 在Release模式下且三个cpp文件的编译顺序是MyClass1.cpp-MyClass2.cpp-main.cpp 实际打印结果也是预期值10 下面来分析为何和出现上述三种不同的情况首先要明确以下四点 1、直接在class {}中定义函数体的函数都是inline的。 2、inline在现代的意义并不是调用处展开函数是否展开由编译器优化决定而是允许在多个编译单元obj文件中出现相同的符号链接时不会报符号重定义。如果在class外面定义非inline的函数体A::A()这样的写法链接是要报错的。 3、如果inline的符号有出现重复链接器会随便选择一个。 4、inline的特性被广泛运用在纯hpp文件造轮子将class的声明和实现都写在头文件中哪里需要哪里include一下就好非常方便无需像原来那样又是h文件又是lib文件还要保证各种编译条件匹配。 关于inline详见Effective C笔记之十五inline函数的里里外外 编译器如何决定是否将函数内联呢 编译器决定是否将函数内联的过程称为内联函数优化。编译器会根据一定的规则和优化策略来决定是否将函数内联。以下是一些关键因素可以影响编译器的决策 ●函数体积如果函数体积较小编译器更可能将其内联。内联函数可以减少函数调用的开销提高代码执行效率。 ●递归函数递归函数通常不会被内联因为递归调用可能导致大量的重复代码从而增加程序的内存占用和执行时间。 ●循环中的函数在循环体内调用的函数也可能被内联。这样可以减少循环中的函数调用开销提高代码执行效率。 ●函数属性编译器可能会根据函数的属性来决定是否内联。例如如果函数具有“inline”属性编译器可能更倾向于将其内联。 ●编译器优化级别编译器的优化级别也会影响其决策。较高的优化级别可能会导致编译器更倾向于内联函数以提高代码执行效率。 ●目标平台编译器会根据目标平台的特性来决定是否内联函数。例如在资源受限的平台上编译器可能更倾向于减少内联以减少程序的内存占用。 总之编译器决定是否值得将函数内联取决于多种因素。编译器会根据这些因素以及优化策略来决定是否将函数内联以提高代码执行效率和减少内存占用。 上面说过inline时是否展开取决于编译器优化在Debug模式下g使用的优化级别是O0默认选项不开启优化方便功能调试。可以明确的是在O0等级下内联不会真正发生。结合前面的现象在Debug模式下链接器都选择了较后参与编译的源文件中的setValue函数。 在Release模式下g使用的优化级别是O2O2是常用的Release级别该级别下几乎执行了所有支持的优化选项它增加了编译时间提高了程序的运行速度会额外打开了一些优化标志比如-finline-functions。结合前面的现象在Release模式下内联真正发生函数在调用处展开所以能得到正确结果尽管如此由于内联的非强制性代码这样写依然是有隐患的。如何判断内联函数有没有在调用处展开呢方法见[C基础]016_内联函数到底有没有被嵌入到调用处呢 除了自己写代码要遵循ODR在使用第三方库时同样要注意下面是一位网友反馈的情况。为何同时用两个不同版本的RapidJSON会导致程序崩溃 rapidjson是一个只包含.h文件就能用的库。意思是它将所有的类定义写在了头文件里面。这种做法很常见。使得调用者非常方便只要include 头文件就能玩耍了不需要再包含.cpp/.lib或者.dll之类的东西。当你的项目里有2个cpp文件[通常遇到问题是因为这两个cpp文件只有一个是你写的另一个是你引用的其他第三方库里的]A.cpp include了rapidjson_v1.hB.cpp include了 rapidjson_v2.h。这下在编译阶段时候编译器发现:咦怎么有两个class rapidjson定义一个在A.cpp里一个在B.cpp里。用哪一个呢。其实这是C普遍存在的问题在.h里面定义了一个class或者template等东东这个头文件被include到多个cpp里在这些cpp里原样展开编译器在链接的时候就会看到多个重复的定义于是C规定了ODR(One Definition Rule)简而言之:看到这种重复定义的类且这些类的代码又长得一模一样编译器就随便选一个用就行了。因为量重复的这些定义都长得一样就随便选一个都行了。这模式一直正常工作。再回到rapidjson原本你想要的结果是A.cpp 使用rapidjson_v1.h里的class rapidjsonB.cpp 使用 rapidjson_v2.h里的class rapidjson。结果现在编译器不管是A.cpp还是B.cpp都给你用rapidjson_v1.h里的class rapidjson[也有可能是rapidjson_v2.h里的class rapidjson]。编译器以为长一样随便选一个就能正常工作结果却不能正常工作应该是rapidjson不同版本间做了一些违背ODR的变动。PS Debug版本和Release版本其实就是优化级别的区别Debug称为调试版本编译的结果通常包含有调试信息没有做任何优化方便开发人员进行调试Release称为发布版本不会携带调试信息同时编译器对代码进行了很多优化使代码更小速度更快发布给用户使用给用户使用以更好的体验。但Release模式编译比Debug模式花的时间也会更多。
原文链接Effective C笔记之二十一One Definition RuleODR-CSDN博客