警惕rapidxml的陷阱:新增節點時,請保證變數的生命週期

小 樓 一 夜 聽 春 雨發表於2013-08-27

http://www.cnblogs.com/chutianyao/p/3246592.html

 

專案中要使用xml打包、解析協議,HQ指定了使用rapidxml--號稱是最快的xml解析器。

功能很快完成了,但發現rapidxml為了追求效能,做了一些對使用者來說並不友好的設計。下面來說一說:

 

給xml物件在新增節點時,不可新增臨時變數

 

按照一般用法,使用如下方式新增節點:

複製程式碼
rapidxml::xml_document<> doc;

void addNode(std::string value)
{
   rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "unregister_context");
   doc.append_node(root);

   root->append_node(doc.allocate_node(rapidxml::node_element, "who_register", value.c_str()));
}
複製程式碼

 但在rapidxml中這麼寫實有問題的,得這麼寫:

複製程式碼
rapidxml::xml_document<> doc;

void addNode(std::string value)
{
   rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "unregister_context");
   doc.append_node(root);

   root->append_node(doc.allocate_node(rapidxml::node_element, "who_register", doc.allocate_string(value.c_str())));
}
複製程式碼

看出差別了嗎?

待插入的值"變數value"是作為引數傳遞進來的,是臨時變數。rapidxml為了追求極致效能,在append_node()函式中是直接通過指標來訪問value變數的,並沒有進行記憶體拷貝--因此rapidxml在這裡提出了一個隱晦的前提條件:在xml物件doc的生命週期內,必須保證"變數value"能夠被正常訪問。

那麼實際情況呢?

仔細檢查一下,就會發現"變數value"是臨時變數,在addNode()函式執行完畢後就會被銷燬;此時xml物件rapidxml::xml_document<> doc內部儲存的值還指向“變數value”的記憶體地址,而該地址已經不可用了。因此在訪問xml物件時就會發生segment fault。

問題出現了,該怎麼解決?我們是無法控制臨時變數的生命週期的,因此只能對該變數進行拷貝。rapidxml已經提供了該功能,這就是allocate_string()函式。該函式在rapidxml物件內部的記憶體池中為我們的變數申請了一份記憶體,然後將“變數value”的值拷貝過去;由於是xml物件自己維護該記憶體池,因此就不存在變數地址失效的問題了。

 

以上情況僅針對allocate_node()待插入的值是臨時變數這種情況;如果使用者能保證待插入變數的生命週期、或者是常量,應該不需要使用allocate_string()函式來分配記憶體了。例如:

rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "data_coming", "some data");

這裡第三個引數"some data"是常量,生命週期等於整個程式的生命週期,因此就不用再為它分配記憶體了。

(ps:此種情況僅是推測,未做測試。)

在為xml物件新增節點時,請保證變數的生命週期!

 

總結:

rapidxml為了追求效能,減少記憶體拷貝,就儘可能的通過指標(記憶體地址)來訪問使用者的變數;這就對使用者提出了要求:必須保證變數的生存週期,如果變數被銷燬了,rapidxml就會訪問無效的記憶體地址,引發不可控的後果。

而對於普通使用者來說,一般都比較少注意到這個細節。

 

為了追求效能,而犧牲了一定的可用性。這種設計是否合理?

 

 

PS:剛遇到了類似的問題,解決用了個笨辦法。。。

std::vector<char*> vec;

...

...

char * name = new char[128];

vec.push_back(name);

...

 

最後xml的doc儲存後將vec中的堆上分配記憶體逐個釋放。。。

日~

 

相關文章