最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++之memcpy導(dǎo)致的深拷貝問題分析

 更新時間:2025年09月18日 09:33:10   作者:一枝小雨  
使用memcpy拷貝vector中自定義類型元素(如string)時,僅復(fù)制指針值導(dǎo)致懸空指針,引發(fā)未定義行為,而循環(huán)賦值通過調(diào)用賦值運算符實現(xiàn)深拷貝,正確復(fù)制對象內(nèi)容,標準庫vector通過類型特質(zhì)區(qū)分類型,對非平凡類型調(diào)用構(gòu)造/析值函數(shù),避免此類問題

代碼與講解承接上文:C++之vector深度剖析及模擬實現(xiàn)

memcpy:更深一層次的深淺拷貝問題

/* 自定義類型 */
void test_vector5()
{
        vector<string> v;
        v.push_back("11111111111111111111111111111");
        v.push_back("22222222222222222222222222222");
        v.push_back("33333333333333333333333333333");
        v.push_back("44444444444444444444444444444");

        for (auto e : v)
        {
                cout << e << " ";
        }
        cout << endl;
}

打印結(jié)果是 3 和 4 沒有問題,但是 1 和 2 都是亂碼。是擴容時出現(xiàn)了問題。

void reserve(size_t n)
{
        // 提前算好size,不然后續(xù)會改變 _start 位置,就算不了size了
        size_t sz = size();
        if (n > capacity())
        {
                T* tmp = new T[n];
                if (_start)        //防止第一次進來_start為空,memcpy出錯
                {
                        memcpy(tmp, _start, sizeof(T) * sz);
                        delete[] _start;/* 這里出現(xiàn)了問題 */
                }
                _start = tmp;
                _finish = tmp + sz;
                _endofstorage = tmp + n;
        }
}

問題分析

  • memcpy是內(nèi)存的二進制格式拷貝,將一段內(nèi)存空間中內(nèi)容原封不動的拷貝到另外一段內(nèi)存空間中
  • 如果拷貝的是自定義類型的元素,memcpy既高效又不會出錯,但如果拷貝的是自定義類型元素,并且自定義類型元素中涉及到資源管理時,就會出錯,因為memcpy的拷貝實際是淺拷貝。

問題根源:memcpy 的淺拷貝特性

memcpy 函數(shù)執(zhí)行的是逐字節(jié)的淺拷貝,它只是簡單地將內(nèi)存中的字節(jié)從一個位置復(fù)制到另一個位置,而不會調(diào)用任何構(gòu)造函數(shù)或賦值運算符。

對于 vector<string> 這種情況:

  • 每個 string 對象內(nèi)部包含指向?qū)嶋H字符串數(shù)據(jù)的指針
  • 使用 memcpy 時,只是復(fù)制了這些指針值,而不是指針指向的實際字符串數(shù)據(jù)
  • 當原 vector 被銷毀時,原 string 對象會調(diào)用析構(gòu)函數(shù)釋放它們指向的內(nèi)存
  • 但新 vector 中的 string 對象仍然指向已被釋放的內(nèi)存區(qū)域,導(dǎo)致懸空指針
  • 訪問這些懸空指針指向的內(nèi)存就是未定義行為,表現(xiàn)為亂碼或程序崩潰

解決方案:使用循環(huán)賦值實現(xiàn)深拷貝

當將擴容代碼改為:

void reserve(size_t n)
{
        // 提前算好size,不然后續(xù)會改變 _start 位置,就算不了size了
        size_t sz = size();
        if (n > capacity())
        {
                T* tmp = new T[n];
                if (_start)        //防止第一次進來_start為空,memcpy出錯
                {
                        // memcpy(tmp, _start, sizeof(T) * sz);        拷貝自定義類型時會導(dǎo)致淺拷貝問題
                        for (size_t i = 0; i < sz; ++i)
                        {
                                tmp[i] = _start[i];
                        }
                        delete[] _start;
                }
                _start = tmp;
                _finish = tmp + sz;
                _endofstorage = tmp + n;
        }
}

這里發(fā)生了以下關(guān)鍵變化:

  • 調(diào)用了賦值運算符:對于每個元素,都會調(diào)用 string::operator=,這是一個深拷貝操作
  • 創(chuàng)建獨立副本:每個新 string 對象都會分配自己的內(nèi)存并復(fù)制字符串內(nèi)容
  • 避免懸空指針:新舊 vector 中的 string 對象指向不同的內(nèi)存區(qū)域,互不影響

Vector 的內(nèi)存布局

當你創(chuàng)建一個vector<string>時,內(nèi)存布局是這樣的:

_vector 對象本身:
_start    -> [string對象1][string對象2][string對象3][string對象4]...
_finish   -> 指向最后一個元素的下一個位置
_endofstorage -> 指向分配的內(nèi)存塊的末尾

關(guān)鍵點是:vector 存儲的是 string 對象本身,而不是指向 string 對象的指針。這些 string 對象在內(nèi)存中是連續(xù)存儲的。

“Vector 存儲的是 string 對象本身”的含義

(下圖中的string類成員是假設(shè)出來的,實際成員可能不一樣,但內(nèi)存布局是一樣的)

_start[0] 這個內(nèi)存位置存儲的是:
[ char* _str | size_t _size | size_t _capacity | ...其他成員 ]

更詳細的內(nèi)存結(jié)構(gòu)圖說明:

 Vector內(nèi)存布局 (棧上或堆上)
┌─────────────────────────────────────────────────────────────┐
│  _start指針  │ 指向vector內(nèi)部數(shù)組的起始位置                 │
├─────────────────────────────────────────────────────────────┤
│  _finish指針 │ 指向最后一個元素的下一個位置                 │
├─────────────────────────────────────────────────────────────┤
│_endofstorage指針│ 指向分配的內(nèi)存塊的末尾                    │
└─────────────────────────────────────────────────────────────┘
    ↓
    ┌─────────┬─────────┬─────────┬─────────┐ ← vector內(nèi)部數(shù)組(在堆上)
    │ string0 │ string1 │ string2 │ string3 │
    └─────────┴─────────┴─────────┴─────────┘
        │         │         │         │
        │         │         │         │
        ▼         ▼         ▼         ▼
    ┌─────┐   ┌─────┐   ┌─────┐   ┌─────┐   ← 每個string對象的_str成員指向的
    │"1111│   │"2222│   │"3333│   │"4444│     字符串數(shù)據(jù)(也在堆上,但不同位置)
    └─────┘   └─────┘   └─────┘   └─────┘

關(guān)鍵點分解

vector的對象數(shù)組:當你創(chuàng)建vector<string> v(4)時,vector會在堆上分配一塊足夠大的連續(xù)內(nèi)存,用來存放4個完整的string對象。

每個string對象:這塊內(nèi)存中的每個"格子"都包含一個完整的string對象,包括:

  • char* _str(指針,通常4或8字節(jié))
  • size_t _size(通常4或8字節(jié))
  • size_t _capacity(通常4或8字節(jié))

可能的其他成員變量

字符串數(shù)據(jù):每個string對象的_str成員指向另一塊堆內(nèi)存,那里存儲著實際的字符串內(nèi)容("1111", "2222"等)。

為什么循環(huán)賦值有效

現(xiàn)在讓我們看看循環(huán):

for (size_t i = 0; i < sz; ++i)
{
    tmp[i] = _start[i];
}

對于每次迭代:

  • _start[i]獲取第i個string對象
  • tmp[i]獲取新數(shù)組中第i個位置(此時可能是一個未初始化的string對象)
  • 調(diào)用string的賦值運算符string::operator=,將右側(cè)string的內(nèi)容復(fù)制到左側(cè)string

重要的是:這不是簡單的內(nèi)存拷貝,而是調(diào)用了string類的賦值運算符,它會進行深拷貝 - 分配新的內(nèi)存并復(fù)制字符串內(nèi)容。

重新理解拷貝問題

現(xiàn)在我們就能明白為什么memcpy有問題而循環(huán)賦值正確了:

memcpy:只復(fù)制了vector數(shù)組內(nèi)存塊(包含string對象的成員變量),包括復(fù)制了_str指針值。結(jié)果是新舊vector中的string對象指向相同的字符串數(shù)據(jù)內(nèi)存。

循環(huán)賦值:tmp[i] = _start[i]調(diào)用了string的賦值運算符,這個運算符會:

  • 釋放tmp[i]原有資源(如果有)
  • 為新的字符串數(shù)據(jù)分配內(nèi)存
  • 復(fù)制字符串內(nèi)容
  • 更新size和capacity成員

一個很好的驗證方式

我們可以添加一些調(diào)試輸出來驗證這個理解:

void test_debug() {
    vector<string> v;
    v.push_back("dfb");
    v.push_back("asdf ds akjfhksdhfkhasdfkhskdfhk");
    v.push_back("12bbbbbb6161rtb616t1b6r1t6516161bbb");
    v.push_back("646asdg56as6dg65s16551agsd");
    
    cout << "Address of vector array: " << (void*)v.begin() << endl;
    for (int i = 0; i < v.size(); i++) {
        cout << "Address of string object " << i << ": " << (void*)&v[i] << endl;
        cout << "Address of string data " << i << ":   " << (void*)v[i].c_str() << endl;
        cout << "Sizeof(string): " << sizeof(string) << endl;
    }
}

這個代碼會顯示string對象本身是連續(xù)存儲的,但每個string對象指向的字符串數(shù)據(jù)在不同的內(nèi)存地址。

為什么標準庫vector沒有這個問題

標準庫的 std::vector 使用了一種叫做"類型特質(zhì)(type traits)"的技術(shù),能夠識別類型是否是"平凡可拷貝(trivially copyable)"的。

對于平凡可拷貝的類型(如基本數(shù)據(jù)類型、簡單結(jié)構(gòu)體),它使用 memcpy 等高效方法;對于非平凡類型(如 string),它會調(diào)用拷貝構(gòu)造函數(shù)或賦值運算符。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • OpenCV邊緣提取算法流程的實現(xiàn)(附DEMO)

    OpenCV邊緣提取算法流程的實現(xiàn)(附DEMO)

    本文主要介紹了OpenCV邊緣提取算法流程的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • C++二維數(shù)組螺旋加密信息

    C++二維數(shù)組螺旋加密信息

    大家好,本篇文章主要講的是C++二維數(shù)組螺旋加密信息,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • 一文搞懂C語言中的文件操作

    一文搞懂C語言中的文件操作

    文件操作想必大家掌握的并不熟練,確實因為我們用的并不多,而本節(jié)內(nèi)容能夠讓大家初步認識文件操作,從文件認識到文件使用,讓我們對c語言文件操作有個初步的了解
    2022-11-11
  • C++詳細講解模擬實現(xiàn)位圖和布隆過濾器的方法

    C++詳細講解模擬實現(xiàn)位圖和布隆過濾器的方法

    位圖(bitset)是一種常用的數(shù)據(jù)結(jié)構(gòu),常用在給一個很大范圍的數(shù),判斷其中的一個數(shù)是不是在其中。在索引、數(shù)據(jù)壓縮方面有很大的應(yīng)用。布隆過濾器是由布隆提出的,它實際上是一個很長的二進制向量和一系列隨機映射函數(shù)。布隆過濾器可以用于檢索一個元素是否在一個集合中
    2022-06-06
  • C++中靜態(tài)存儲區(qū)與棧以及堆的區(qū)別詳解

    C++中靜態(tài)存儲區(qū)與棧以及堆的區(qū)別詳解

    本篇文章是對C++中靜態(tài)存儲區(qū)與棧以及堆的區(qū)別進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • C語言嵌入informix基礎(chǔ)入門示例講解

    C語言嵌入informix基礎(chǔ)入門示例講解

    這篇文章主要介紹了C語言嵌入informix基礎(chǔ)方法,大家參考使用
    2013-11-11
  • 詳解c++ 繼承

    詳解c++ 繼承

    這篇文章主要介紹了c++ 繼承的相關(guān)資料,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • C語言Tinyhttpd服務(wù)器源碼剖析

    C語言Tinyhttpd服務(wù)器源碼剖析

    這篇文章主要為大家介紹了C語言Tinyhttpd服務(wù)器源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • OpenCV繪制正多邊形的方法

    OpenCV繪制正多邊形的方法

    這篇文章主要為大家詳細介紹了OpenCV繪制正多邊形的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • 老程序員教你一天時間完成C++俄羅斯方塊游戲

    老程序員教你一天時間完成C++俄羅斯方塊游戲

    俄羅斯方塊游戲大家應(yīng)該非常熟悉,非常經(jīng)典的一款游戲,本文來詳細講解下俄羅斯方塊游戲的制作過程,趕緊來看下吧!希望能給你帶來幫助
    2021-08-08

最新評論

丰镇市| 米易县| 化隆| 卓尼县| 通渭县| 东明县| 浏阳市| 吴川市| 林州市| 上杭县| 吉水县| 乐平市| 二连浩特市| 丽水市| 岳西县| 安徽省| 吐鲁番市| 麻阳| 黄山市| 开阳县| 鞍山市| 英德市| 明星| 龙山县| 扎鲁特旗| 白水县| 沧州市| 中方县| 陕西省| 平陆县| 资溪县| 临汾市| 定远县| 濮阳市| 武鸣县| 峨眉山市| 安康市| 镇宁| 乡宁县| 肇东市| 辉县市|