一个C++引用库的头文件预编译陷阱
写在前面
老胡最近在工作中,有个场景需要使用一个第三方库,引用头文件,链接库,编译运行,一切都很正常,但是接下来就遇到了一个很诡异的问题,调用该库的中的一个对象方法为对象修改属性的时候,会影响到对象的另外一个属性,当时百思不得其解,直呼灵异事件。
但后面静下心来细细看了一下代码和各种配置,发现了问题所在,现在把这个问题分享在这里,希望大家在以后的工作中如果遇到了类似的情况知道应该如何处理。
场景还原
库
当时引用的是一个第三方的静态链接库,场景非常简单,在项目中包含头文件,链接器指定路径和静态库名称,我们这里新建工程来生成一个非常简单的库。
其中,
//LibObject.h #pragma once struct LibObject { int valueA{ 0 }; #ifdef AdditionalValue int valueB{ 0 }; #endif int valueC{ 0 }; void DoSomething(); }; //LibObject.cpp #include "LibObject.h" void LibObject::DoSomething() { valueA = 10; #ifdef AdditionalValue valueB = 10; #endif }
简单至极,若预编译变量定义了AdditionalValue则定义多一个valueB并且在方法中赋值。编译库的时候我们指定AdditionalValue。
客户端代码
//main.cpp #include "LibObject.h" #include <iostream> using namespace std; int main() { LibObject obj; cout << obj.valueA << endl; cout << obj.valueC << endl; obj.DoSomething(); cout << obj.valueA << endl; cout << obj.valueC << endl; return 0; }
客户端代码也很简单,声明一个对象,调用它的方法并在调用前后检查它的值,在编译客户端代码的时候,我们不定义AdditionalValue预编译变量。
运行试试
现在猜一猜输出是多少?
解惑
藏在背后的秘密
如果这个结果让你吃惊,那么相信我,你不是一个人,当时老胡也惊呆了,不管怎么看,DoSomething仅仅修改了ValueA,为什么会让ValueC的值变了?
秘密就在于编译库的时候和编译客户端代码的时候,我们使用了不同的预编译变量。
- 在客户端代码看来,LibObject是一个仅仅包含2个int类型的结构体,并且DoSomething方法会赋值给一个int,该int相对于this指针偏移是0。
- 另一方面,在库代码看来,这个结构体包含了3个int类型变量,DoSomething会赋值给相对于this指针偏移为0和4的两个int。
所以答案揭晓了,为什么valueC的值会被影响,在于DoSomething执行的时候,相当于this指针偏移为4的int被赋值了,但是在我们从客户端代码构建的结构体中,这个位置存放的是valueC。
从这里可以看出,在方法执行的过程中,所谓的valueB其实内存地址和valueC是一样的。所以其实是那句给valueB赋值的语句把值给了valueC。
如何修复
知道了出问题的地方,修复起来就很简单了,一般来说两个办法。
- 如果第三方库能找到源代码,那我们可以重新用我们希望的预编译设置编译一次
- 如果找不到源代码,那我们只有在客户端代码添加相应的预编译设置,确保和编译库时候所使用的一致
这两个办法都需要仔细阅读第三方库的文档。
希望本文能给遇到了类似问题的小伙伴一点启示,特别当你遇到了类似的情况的时候,这篇文章能够给你一些思路,毕竟,编译器甚至在这种情况下都不会给出任何警告,我们只能靠经验排查了。
- C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
- C\C++小知识: 如何引用一个已经定义过的全局变量 与 全局变量可不可以定义在可被多个.C文件包含的头文件中
- C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
- C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
- C++ : 编译单元、声明和定义、头文件作用、防止头文件在同一个编译单元重复引用、static和不具名空间
- C++ : 编译单元、声明和定义、头文件作用、防止头文件在同一个编译单元重复引用、static和不具名空间
- C++ : 编译单元、声明和定义、头文件作用、防止头文件在同一个编译单元重复引用、static和不具名空间
- Linux下编写C++代码引用opencv头文件,编译报错 undefined reference to `cv::imread的解决办法
- C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
- [undefine reference to...]c++ 已经引用头文件的情况编译显示未定义
- C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
- C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
- C++ 关于声明,定义,类的定义,头文件作用,防止头文件在同一个编译单元重复引用,不具名空间
- Ubuntu 10.04下安装OpenCV及编译一个简单例程(找不到头文件的问题:error: cv.h: No such file or directory )
- 如何引用一个已经定义过的全局变量 与 全局变量可不可以定义在可被多个.C文件包含的头文件中
- Android Studio 1.0 JNI 引用多个C++文件编译找不到类的问题
- ?C++编译链接时的一个小问题
- 在linux环境下编译一个c/c++工程
- 关于sublime 编译C与gc++编译C的一个问题
- VS2012 编译报错:找不到编译动态表达式所需的一个或多个类型。是否缺少引用?