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

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

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

代碼與講解承接上文:C++之vector深度剖析及模擬實(shí)現(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 都是亂碼。是擴(kuò)容時(shí)出現(xiàn)了問題。

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

問題分析

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

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

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

對于 vector<string> 這種情況:

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

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

當(dāng)將擴(kuò)容代碼改為:

void reserve(size_t n)
{
        // 提前算好size,不然后續(xù)會(huì)改變 _start 位置,就算不了size了
        size_t sz = size();
        if (n > capacity())
        {
                T* tmp = new T[n];
                if (_start)        //防止第一次進(jìn)來_start為空,memcpy出錯(cuò)
                {
                        // memcpy(tmp, _start, sizeof(T) * sz);        拷貝自定義類型時(shí)會(huì)導(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)用了賦值運(yùn)算符:對于每個(gè)元素,都會(huì)調(diào)用 string::operator=,這是一個(gè)深拷貝操作
  • 創(chuàng)建獨(dú)立副本:每個(gè)新 string 對象都會(huì)分配自己的內(nèi)存并復(fù)制字符串內(nèi)容
  • 避免懸空指針:新舊 vector 中的 string 對象指向不同的內(nèi)存區(qū)域,互不影響

Vector 的內(nèi)存布局

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

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

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

“Vector 存儲(chǔ)的是 string 對象本身”的含義

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

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

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

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

關(guān)鍵點(diǎn)分解

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

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

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

可能的其他成員變量

字符串?dāng)?shù)據(jù):每個(gè)string對象的_str成員指向另一塊堆內(nèi)存,那里存儲(chǔ)著實(shí)際的字符串內(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個(gè)string對象
  • tmp[i]獲取新數(shù)組中第i個(gè)位置(此時(shí)可能是一個(gè)未初始化的string對象)
  • 調(diào)用string的賦值運(yùn)算符string::operator=,將右側(cè)string的內(nèi)容復(fù)制到左側(cè)string

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

重新理解拷貝問題

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

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

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

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

一個(gè)很好的驗(yàn)證方式

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

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;
    }
}

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

為什么標(biāo)準(zhǔn)庫vector沒有這個(gè)問題

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

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

總結(jié)

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

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

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

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

    詳解c++ 繼承

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

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

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

    OpenCV繪制正多邊形的方法

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

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

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

最新評論

海阳市| 双流县| 上蔡县| 南江县| 竹山县| 确山县| 明光市| 惠州市| 新闻| 灵川县| 喀喇沁旗| 泉州市| 资兴市| 陇西县| 石泉县| 鱼台县| 增城市| 正阳县| 湟中县| 汉源县| 思茅市| 乌拉特前旗| 久治县| 临颍县| 台安县| 桃源县| 方山县| 鄯善县| 贵溪市| 岳池县| 临安市| 夏河县| 嵊泗县| 社旗县| 沿河| 客服| 澎湖县| 清流县| 浦县| 青浦区| 乐山市|