日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

bug诞生记——隐蔽的指针偏移计算导致的数据错乱

發布時間:2023/11/27 生活经验 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 bug诞生记——隐蔽的指针偏移计算导致的数据错乱 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? ? ? ? C++語言為了兼容C語言,做了很多設計方面的考量。但是有些兼容設計產生了不清晰的認識。本文就將討論一個因為認知不清晰而導致的bug。(轉載請指明出于breaksoftware的csdn博客)

class Base {
public:Base() = default;void set_v_b(int v_b) {_v_b = v_b;}int get_v_b() const {return _v_b;}
private:int _v_b;
};class Derived :public Base
{
public:Derived() = default;void set_v_d(int v_d) {_v_d = v_d;}int get_v_d() const {return _v_d;}
private:int _v_d;
};

? ? ? ? Base是Derived的基類,前者擁有成員變量_v_b,后者擁有前者的_v_b和自己定義的_v_d。

? ? ? ? 我們分別構建一個Base和Derived對象數組

Base * build_base_list(size_t count) {Base *b_list = new (std::nothrow) Base[count];if (!b_list) {return nullptr;}for (size_t i = 0; i < count; i++) {b_list[i].set_v_b(static_cast<int>(i));}return b_list;
}Derived * build_derived_list(size_t count) {Derived *d_list = new (std::nothrow) Derived[count];if (!d_list) {return nullptr;}for (size_t i = 0; i < count; i++) {d_list[i].set_v_b(static_cast<int>(i));d_list[i].set_v_d(static_cast<int>(i));}return d_list;
}

? ? ? ? 我們再提供一個方法,用于遍歷數組中對象的_v_b(Base基類定義的成員變量)。

void print_v_b(Base *b_list, size_t b_list_count) {if (!b_list) {return;}for (size_t i = 0; i < b_list_count; i++) {std::cout << "_v_b(" << i << "):" << b_list[i].get_v_b() << std::endl;}
}

? ? ? ? 然后我們對構建的兩個數組分別調用print_v_b,以期望打印出各自的v_b。

    const size_t count = 8;std::unique_ptr <Base, std::function<void(Base*)>> base_list(build_base_list(count),[](Base* p) {delete [] p;});std::cout << "base_list:" << std::endl;print_v_b(base_list.get(), count);std::unique_ptr <Derived, std::function<void(Derived*)>> derived_list(build_derived_list(count),[](Derived* p) {delete [] p;});std::cout << "derived_list:" << std::endl;print_v_b(derived_list.get(), count);

? ? ? ? 理論上,我們將看到兩組相同的結果。因為base_list和derived_list中每個元素的_v_b是其在數組中的下標。然而結果是

base_list:
_v_b(0):0
_v_b(1):1
_v_b(2):2
_v_b(3):3
_v_b(4):4
_v_b(5):5
_v_b(6):6
_v_b(7):7
derived_list:
_v_b(0):0
_v_b(1):0
_v_b(2):1
_v_b(3):1
_v_b(4):2
_v_b(5):2
_v_b(6):3
_v_b(7):3

? ? ? ? 很明顯,derived_list數組輸出的元素信息不正確。

? ? ? ? derived_list數組中的每個元素都是Base子類Derived的對象。理論上,對Derived對象,通過基類Base的方法訪問,是可以獲得正確數據的。那問題出在哪里?我們還要回到print_v_b方法中

void print_v_b(Base *b_list, size_t b_list_count) {if (!b_list) {return;}for (size_t i = 0; i < b_list_count; i++) {std::cout << "_v_b(" << i << "):" << b_list[i].get_v_b() << std::endl;}
}

? ? ? ? 我們看到第7行是通過數組下標的形式獲取每個元素的。在C語言中,如果一個數組通過下標[]訪問元素,其獲取的元素實際地址是Head+index*sizeof(struct)。

? ? ? ? 我們分別看一個int型和long long型數組通過下標獲取元素的取址值

    const size_t count = 8;int integer_list[count];std::cout << "Head:" << integer_list << " sizeof(int):" << sizeof(int) << std::endl;for (size_t i = 0; i < count; i++) {std::cout << "integer_list[" << i << "] address:" << &integer_list[i] << std::endl;}long long longlong_list[count];std::cout << "Head:" << integer_list << " sizeof(int):" << sizeof(long long) << std::endl;for (size_t i = 0; i < count; i++) {std::cout << "longlong_list[" << i << "] address:" << &longlong_list[i] << std::endl;}

? ? ? ? 可以看到,雖然每次下標只是自增1,但是地址實際增加了每個元素的大小。

Head:0x7fffffffe900 sizeof(int):4
integer_list[0] address:0x7fffffffe900
integer_list[1] address:0x7fffffffe904
integer_list[2] address:0x7fffffffe908
integer_list[3] address:0x7fffffffe90c
integer_list[4] address:0x7fffffffe910
integer_list[5] address:0x7fffffffe914
integer_list[6] address:0x7fffffffe918
integer_list[7] address:0x7fffffffe91c
Head:0x7fffffffe900 sizeof(int):8
longlong_list[0] address:0x7fffffffe9a0
longlong_list[1] address:0x7fffffffe9a8
longlong_list[2] address:0x7fffffffe9b0
longlong_list[3] address:0x7fffffffe9b8
longlong_list[4] address:0x7fffffffe9c0
longlong_list[5] address:0x7fffffffe9c8
longlong_list[6] address:0x7fffffffe9d0
longlong_list[7] address:0x7fffffffe9d8

? ? ? ? 在print_v_b數組中,它默認認為數組中每個元素大小是Base對象的大小。然而derived_list數組中每個元素的是Derived對象大小。Derived類比Base類多一個元素_v_d,從而大小從Base對象的4字節變成了8字節。這樣第7行中,每次下標移動實際只是移動了4字節,于是每個奇數次移動均移動到Derived對象的_v_d前,每個偶數次移動均移動到Derived對象的_v_b前。這就出現了上面的數據錯亂的問題。

? ? ? ? 數組是C的遺產。為了兼容C,C++保留了很多C語言的印記,于是導致自身呈現出一些不清晰的表達。比如下面如下三種寫法

  1. void print_t(T *t)
  2. void print_t(T t[])
  3. void print_t(T &?t)

? ? ? ? 第3種寫法,我們可以知道t是個對象。

? ? ? ? 第2種寫法,我們可以知道t表達了一個數組。

? ? ? ? 第1中寫法,則可以表達出t可以是一個數組,可以是一個對象。那么到底它是個組數還是對象?我們沒法從語法上得知。

? ? ? ? 像本例中,使用者很有可能會把print_v_b的第一元素當成一個對象指針(當然第二個參數透露出其應該是一個數組,但是假如沒有第二個參數呢?),那么他怎么也不會想到,對derived_list調用print_v_b會出錯。

? ? ? ? 這從一個側面可以說明,對于可以靈活表達的C++語言,我們需要采用一些易于理解的方式去設計API。

總結

以上是生活随笔為你收集整理的bug诞生记——隐蔽的指针偏移计算导致的数据错乱的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。