一個C++引用庫的頭文件預編譯陷阱

寫在前面

老胡最近在工做中,有個場景須要使用一個第三方庫,引用頭文件,連接庫,編譯運行,一切都很正常,可是接下來就遇到了一個很詭異的問題,調用該庫的中的一個對象方法爲對象修改屬性的時候,會影響到對象的另一個屬性,當時百思不得其解,直呼靈異事件。
但後面靜下心來細細看了一下代碼和各類配置,發現了問題所在,如今把這個問題分享在這裏,但願你們在之後的工做中若是遇到了相似的狀況知道應該如何處理。
 ios

場景還原

當時引用的是一個第三方的靜態連接庫,場景很是簡單,在項目中包含頭文件,連接器指定路徑和靜態庫名稱,咱們這裏新建工程來生成一個很是簡單的庫。
this

其中,spa

//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預編譯變量。
 code

運行試試

如今猜一猜輸出是多少?對象

 

解惑

藏在背後的祕密

若是這個結果讓你吃驚,那麼相信我,你不是一我的,當時老胡也驚呆了,無論怎麼看,DoSomething僅僅修改了ValueA,爲何會讓ValueC的值變了?blog

 
祕密就在於編譯庫的時候和編譯客戶端代碼的時候,咱們使用了不一樣的預編譯變量。事件

  • 在客戶端代碼看來,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。
 文檔

如何修復

知道了出問題的地方,修復起來就很簡單了,通常來講兩個辦法。

  • 若是第三方庫能找到源代碼,那咱們能夠從新用咱們但願的預編譯設置編譯一次
  • 若是找不到源代碼,那咱們只有在客戶端代碼添加相應的預編譯設置,確保和編譯庫時候所使用的一致

這兩個辦法都須要仔細閱讀第三方庫的文檔。   但願本文能給遇到了相似問題的小夥伴一點啓示,特別當你遇到了相似的狀況的時候,這篇文章可以給你一些思路,畢竟,編譯器甚至在這種狀況下都不會給出任何警告,咱們只能靠經驗排查了。

相關文章
相關標籤/搜索