首页 健康生活文章正文

C++标准库学习 memcpy:内存世界的 “搬家公司”,栈堆搬运全解析

健康生活 2025年09月07日 06:44 1 admin
C++标准库学习 memcpy:内存世界的 “搬家公司”,栈堆搬运全解析

C++ memcpy:内存世界的 “搬家公司”,栈堆搬运全解析

先给 memcpy 贴个 “身份标签”:这货到底是干啥的?

想象你要搬家 ——



  • 栈(Stack)就像小区门口的 “自动储物柜”:空间小但存取快,租期短(随函数生灭),里面放的都是临时物品(局部变量)。
  • 堆(Heap)就像郊外的 “自助仓库”:空间大但存取慢,租期灵活(手动申请释放),放的都是长期物品(动态分配的内存)。



而memcpy就是一家 “专业搬家公司”,不管你要搬的东西在储物柜(栈)还是仓库(堆),它都能按你说的 “搬多少字节”,原封不动从 “源地址” 搬到 “目标地址”。但这家公司有点 “死板”:只负责搬,不检查目标空间够不够,也不管源和目标地址是不是重叠 —— 搬坏了概不负责~

从内存角度看 memcpy:搬家公司的 “操作流程”

memcpy的函数原型长这样:



cpp

运行

void* memcpy(void* dest, const void* src, size_t n);



  • dest:目标地址(新家地址)
  • src:源地址(旧家地址)
  • n:要搬的字节数(搬家物品体积)
  • 返回值:目标地址(方便链式操作)



它的核心逻辑很简单:从src开始,连续复制n个字节到dest,像用复印机一页页复印文件,字节级精准复制。

案例 1:栈内存之间的 “短途搬家”—— 复制局部变量

栈上的变量(比如数组、基本类型)就像储物柜里的箱子,memcpy可以快速在相邻储物柜之间搬东西。

代码:stack_memcpy.cpp

cpp

运行

#include <iostream>#include <cstring>  // memcpy的“营业执照”在这里int main() {    // 栈上的源数组(旧家:储物柜A)    int src_stack[3] = {10, 20, 30};  // 每个int占4字节,共12字节    // 栈上的目标数组(新家:储物柜B,大小和源一样)    int dest_stack[3];  // 未初始化,像空箱子    // 打印搬家前的新家(全是随机值,储物柜里的旧垃圾)    std::cout << "搬家前的dest_stack:";    for (int i = 0; i < 3; i++) {        std::cout << dest_stack[i] << " ";  // 输出随机数,比如:-1217635216 32767 0    }    std::cout << std::endl;    // 调用memcpy搬家:从src_stack搬12字节到dest_stack(3个int)    // 源和目标都在栈上,属于“短途搬家”    memcpy(dest_stack, src_stack, sizeof(src_stack));  // sizeof算总字节数,省心    // 打印搬家后(新家东西和旧家一模一样)    std::cout << "搬家后的dest_stack:";    for (int i = 0; i < 3; i++) {        std::cout << dest_stack[i] << " ";  // 输出:10 20 30    }    std::cout << std::endl;    // 验证内存地址(都在栈上,地址相近)    std::cout << "src_stack地址:" << &src_stack << std::endl;  // 比如:0x7ffd9a5b167c    std::cout << "dest_stack地址:" << &dest_stack << std::endl;  // 比如:0x7ffd9a5b1668(和上面很接近)    return 0;}

编译运行:

bash

g++ stack_memcpy.cpp -o stack_memcpy -std=c++11./stack_memcpy  # Linux/Mac# 或 stack_memcpy.exe(Windows)

内存分析:栈上的 “邻里搬家”

  • src_stack和dest_stack都是main函数里的局部变量,存放在栈上(自动储物柜区域)。
  • 栈内存地址 “向下生长”(地址值越来越小),所以两个数组地址很接近(像相邻的储物柜)。
  • memcpy在这里就是把src_stack的 12 个字节(3 个 int)逐个复制到dest_stack,速度极快(栈操作像拿隔壁柜子的东西,一步到位)。

案例 2:堆内存之间的 “长途搬家”—— 复制动态数组

堆上的内存(用new或malloc申请)就像自助仓库的隔间,memcpy也能在两个仓库隔间之间搬东西,只是 “路远点”(地址可能相差很大)。

代码:heap_memcpy.cpp

cpp

运行

#include <iostream>#include <cstring>int main() {    // 堆上的源数组(旧家:仓库A)    int* src_heap = new int[3]{100, 200, 300};  // 申请12字节堆内存    // 堆上的目标数组(新家:仓库B,必须提前申请足够大的空间!)    int* dest_heap = new int[3];  // 申请12字节堆内存,准备接收    // 打印搬家前的新家(堆上的随机值,仓库旧垃圾)    std::cout << "搬家前的dest_heap:";    for (int i = 0; i < 3; i++) {        std::cout << dest_heap[i] << " ";  // 比如:4196128 0 4195776    }    std::cout << std::endl;    // 调用memcpy搬家:从堆上的src_heap搬12字节到堆上的dest_heap    memcpy(dest_heap, src_heap, 3 * sizeof(int));  // 3个int,每个4字节    // 打印搬家后    std::cout << "搬家后的dest_heap:";    for (int i = 0; i < 3; i++) {        std::cout << dest_heap[i] << " ";  // 100 200 300    }    std::cout << std::endl;    // 验证内存地址(堆地址通常比栈大,且两个堆地址可能相差较远)    std::cout << "src_heap地址:" << src_heap << std::endl;  // 比如:0x55f9d999a2a0    std::cout << "dest_heap地址:" << dest_heap << std::endl;  // 比如:0x55f9d999a2c0(和上面差几十字节)    // 堆内存要手动释放(还仓库钥匙!)    delete[] src_heap;    delete[] dest_heap;    return 0;}

编译运行:

bash

g++ heap_memcpy.cpp -o heap_memcpy -std=c++11./heap_memcpy

内存分析:堆上的 “跨区搬家”

  • src_heap和dest_heap是堆内存的指针(存放在栈上的 “仓库地址条”),指向的实际数据在堆上(自助仓库)。
  • 堆内存地址通常比栈大很多,且两个堆地址可能不连续(像不同区域的仓库)。
  • memcpy不管距离,只要地址有效,就按字节复制。但必须保证目标堆内存足够大(比如这里都申请了 3 个 int),否则会 “搬超了”(缓冲区溢出)。

案例 3:栈→堆和堆→栈的 “跨区搬家”

memcpy还能跨类型搬家:从栈搬到堆,或从堆搬到栈,就像从储物柜搬东西到仓库,反之亦然。

代码:cross_memcpy.cpp

cpp

运行

#include <iostream>#include <cstring>int main() {    // 1. 栈→堆:把栈上的数组搬到堆上    int stack_arr[2] = {1, 2};  // 栈上的源(8字节)    int* heap_dest = new int[2];  // 堆上的目标(提前申请8字节)    memcpy(heap_dest, stack_arr, sizeof(stack_arr));  // 栈→堆搬家    std::cout << "栈→堆结果:" << heap_dest[0] << ", " << heap_dest[1] << std::endl;  // 1, 2    // 2. 堆→栈:把堆上的数组搬回栈上    int stack_dest[2];  // 栈上的目标    memcpy(stack_dest, heap_dest, 2 * sizeof(int));  // 堆→栈搬家    std::cout << "堆→栈结果:" << stack_dest[0] << ", " << stack_dest[1] << std::endl;  // 1, 2    // 清理堆内存    delete[] heap_dest;    return 0;}

运行结果:

plaintext

栈→堆结果:1, 2堆→栈结果:1, 2

关键说明:

  • 跨区搬家和同区搬家逻辑一样,memcpy只认地址和字节数,不管地址在栈还是堆。
  • 核心是:目标地址必须有足够的空间,否则会 “挤爆”(比如堆上只申请 1 个 int,却要搬 2 个 int 的字节,就会覆盖其他堆内存)。

案例 4:memcpy 的 “致命缺点”—— 处理不好 “重叠地址”

如果源地址和目标地址有重叠(比如同一个数组内部复制),memcpy会出问题,就像搬家时新家和旧家在同一个隔间,东西还没搬完就被覆盖了。

代码:overlap_memcpy.cpp

cpp

运行

#include <iostream>#include <cstring>int main() {    int arr[5] = {1, 2, 3, 4, 5};  // 栈上的数组(源和目标重叠)    std::cout << "原数组:";    for (int i = 0; i < 5; i++) std::cout << arr[i] << " ";  // 1 2 3 4 5    std::cout << std::endl;    // 错误:用memcpy从arr[1]复制3个int到arr[2](地址重叠)    memcpy(&arr[2], &arr[1], 3 * sizeof(int));  // 想把2,3,4复制到位置2,3,4,预期结果1 2 2 3 4    std::cout << "memcpy处理重叠后:";    for (int i = 0; i < 5; i++) std::cout << arr[i] << " ";  // 实际结果:1 2 2 2 3(出错了!)    std::cout << std::endl;    // 正确:用memmove处理重叠(memmove是“智能搬家公司”,会先备份)    int arr2[5] = {1, 2, 3, 4, 5};    memmove(&arr2[2], &arr2[1], 3 * sizeof(int));  // 安全处理重叠    std::cout << "memmove处理重叠后:";    for (int i = 0; i < 5; i++) std::cout << arr2[i] << " ";  // 1 2 2 3 4(正确)    std::cout << std::endl;    return 0;}

运行结果:

plaintext

原数组:1 2 3 4 5 memcpy处理重叠后:1 2 2 2 3 memmove处理重叠后:1 2 2 3 4 

原因分析:

  • memcpy是 “从前往后” 复制,当dest在src后面且重叠时,先复制的字节会覆盖还没复制的源数据(比如先把 arr [1]=2 复制到 arr [2],然后复制 arr [2] 时已经变成 2 了,而不是原来的 3)。
  • memmove会先判断地址是否重叠,重叠的话就 “从后往前” 复制或先备份,避免覆盖 —— 这是它比memcpy聪明的地方。

总结:memcpy 的 “使用说明书”

  1. 适用场景:非重叠内存的字节级复制,不管源和目标在栈还是堆。
  2. 核心要求:目标地址必须有足够大的空间(至少n字节),否则会缓冲区溢出(像搬家时新家太小,东西堆到邻居家)。
  3. 避坑点:不处理重叠内存(用memmove替代)。不检查地址合法性(传野指针会崩溃,像给搬家公司假地址)。按字节复制,不关心数据类型(比如复制float和int会乱码,因为二进制格式不同)。



把memcpy当 “傻瓜式搬家公司” 用就对了:给足空间、地址别重叠、类型要匹配,它就能高效完成任务~

标题:

  1. C++ memcpy:栈堆内存的 “搬家公司” 全解析
  2. 从字节到地址:memcpy 在栈和堆上的操作指南

简介:

本文用搬家公司的幽默类比,详解 C++ 中 memcpy 函数在栈内存和堆内存上的操作原理,通过 4 个案例展示同区复制、跨区复制及重叠内存处理等场景,分析其优缺点和使用注意事项,步骤清晰可实操,助你掌握 memcpy 的内存操作逻辑。

关键词:

#C++ #memcpy #内存操作 #栈内存 #堆内存

发表评论

泰日号Copyright Your WebSite.Some Rights Reserved. 网站地图 备案号:川ICP备66666666号 Z-BlogPHP强力驱动