C语言结构体成员赋值的深拷贝与浅拷贝详解

许南星
许南星
发布时间:2026-10-15 | 6人浏览过
原创

在C语言开发中,结构体的直接赋值看似简单,却隐藏着严重的内存隐患。本文将深入剖析当结构体包含指针成员时,“浅拷贝”如何引发野指针与双重释放错误,并提供标准的“深拷贝”实现方案,保障程序内存安全。

结果或目标

明确区分并正确处理 C 语言结构体的两种赋值模式:

  • 浅拷贝(Shallow Copy):仅复制值类型成员和指针变量的地址,导致多个对象共享同一块堆内存。
  • 深拷贝(Deep Copy):不仅复制数据值,还针对指针成员重新申请堆内存并复制具体数据,使对象间完全独立。

准备条件

为了直观展示差异,我们需要对比以下两种情况:

  1. 无指针成员的结构体:数据直接在栈上分配,直接赋值是安全的。
  2. 含指针成员的结构体:关键字符串等数据位于堆内存,需使用 malloc 分配,直接赋值会切断内存隔离。

操作顺序

第1步:分析不含指针成员的浅拷贝行为

目的:验证纯值类型结构体赋值的独立性。
入口:定义仅包含数组和基本类型的结构体。

对于非指针成员,C 编译器会按字节逐一复制,两个对象互不影响:

#include <stdio.h>
typedef struct {
    char name[64];
    int age;
}Member;
 
int main(){
    Member stu1 = { "LiMing", 18 };
    Member stu2;
    stu2 = stu1; // 直接赋值,安全
    printf("%s,%d\n", stu2.name, stu2.age);
    return 0;
}

运行结果如下:

可以看到,stu2 成功获得了 stu1 的完整副本,两者内存空间完全独立。

第2步:演示含指针成员的浅拷贝风险

目的:观察当结构体包含 char* 等指针成员时,直接赋值带来的副作用。
入口:定义包含指针成员的结构体,并使用 malloc 初始化。

当执行 Member2 = Member1; 时,指针 name 的值(即内存地址)被复制,而非其指向的内容:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
typedef struct {
 char *name;
 int age;
}Member;
 
int main() {
 Member Member1, Member2;
 
 // 初始化 Member1
 Member1.name = malloc(sizeof(char) * 64);
 if (NULL == Member1.name)
 {
  printf("malloc failed\n");
 }
 memset(Member1.name, 0, 64);
 snprintf(Member1.name, 64, "LiMing");
 Member1.age = 18;
 
 // 【浅拷贝发生处】
 Member2 = Member1; /* 此时 Member2.name 也指向 LiMing 的地址 */
 
 // 修改 Member2 不会影响 Member1 的数据本身,但... 
 // 在销毁阶段会出现严重问题
 snprintf(Member2.name, 64, "LiXiaoYao"); // 覆盖内存内容
 Member2.age = 29;
 
 printf("%s, %d\n", Member1.name, Member1.age);
 
 // 清理工作
 if (NULL != Member1.name) {
  free(Member1.name);
  Member1.name = NULL;
 }
 system("pause");
 return 0;
}

运行结果如下:

虽然程序能打印出修改后的值,但这掩盖了逻辑漏洞:因为两个指针指向同一块内存,修改其中一个的数据实际上影响了另一个的逻辑视图。更致命的是内存管理问题。

第3步:验证野指针与内存泄漏

目的:证明浅拷贝会导致对象销毁时的异常崩溃。
入口:模拟完整的生命周期创建、赋值、释放。

如果两个对象各自尝试释放同一个堆地址:

// ...前文省略,关键在于 Member1 = Member2 这种赋值...
// 假设执行了 Member1 = Member2;
// 此时 Member1.name 和 Member2.name 指向相同地址

// 清理时...
if (NULL != Member1.name) {
  free(Member1.name);   // 第一次释放,内存被归还系统
  Member1.name = NULL;
}
// 注意:上面例子中可能没有处理Member2的释放,但若都释放:
if (NULL != Member2.name) {
  free(Member2.name);   // 【危险!】对已释放内存或野指针二次释放
  Member2.name = NULL;
}

如运行截图所示,多次释放同一块堆内存会触发运行时错误:

这表明一片空间被两个不同的子对象共享,改变了对象的析构行为后会产生不可预知的后果。

第4步:实施深拷贝修复方案

目的:手动为指针成员分配新内存并复制内容。
入口:在赋值前释放旧内存,按需分配新内存。

正确的做法是在接收方销毁原有资源后,根据发送方的数据大小重新开辟空间:

#include <stdio.h>
#include <stdlib.h>
#include <string.h> 

typedef struct {
 char *name;
 int age;
}Member;
 
int main() {
 Member Member1;
 // 初始化 Source
 Member1.name = malloc(sizeof(char) * 64);
 if (NULL == Member1.name)
 {
  printf("malloc failed\n");
 }
 memset(Member1.name, 0, 64);
 snprintf(Member1.name, 64, "LiMing");
 Member1.age = 18;
 
 Member Member2;
 // 初始化 Destination
 Member2.name = malloc(sizeof(char) * 64);
 if (NULL == Member2.name)
 {
  printf("malloc failed\n");
 }
 memset(Member2.name, 0, 64);
 snprintf(Member2.name, 64, "LiXiaoYao");
 Member2.age = 29;
 
 // 【深拷贝核心步骤】
 // 1. 释放接收方原有的内存
 if (Member1.name != NULL) {
  free(Member1.name);
  Member1.name = NULL;
 }
 // 2. 根据发送方数据长度申请新内存
 Member1.name = malloc(strlen(Member2.name) + 1);
 // 3. 逐个字符复制内容
 strcpy(Member1.name, Member2.name);
 
 printf("%s, %d\n", Member1.name, Member1.age);
 
 // 清理
 if (NULL != Member1.name) {
  free(Member1.name);
  Member1.name = NULL;
 }
 if (NULL != Member2.name) {
  free(Member2.name);
  Member2.name = NULL;
 }
 system("pause");
 return 0;
}

运行结果如下,程序稳定结束,无内存泄漏或崩溃:

完成判断

  • 检查输出日志是否显示预期数值且无 Runtime Error。
  • 确认 Memory Leak 扫描工具未报告丢失的堆内存。
  • 验证对拷贝后的 name 进行修改不会影响源数据的独立性。

简要总结

C 语言的直接赋值默认执行的是逐字节拷贝(浅拷贝)。对于不含指针的成员,这是完全可行的;但对于包含堆内存指针的结构体,直接赋值会导致“指鹿为马”式的内存共享,进而引发野指针访问和双重释放崩溃。

处理策略很简单:

  1. 若结构体成员全为值类型(数组、基本整数/浮点数),直接赋值即可。
  2. 若涉及指针,必须编写专用的拷贝函数,先释放目标旧内存,再按长度申请新内存并使用 strcpy/memcpy 复制内容。

以上就是 C 语言结构体成员赋值的深拷贝与浅拷贝详解的详细内容,更多关于 C语言 结构体 内存管理的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表