C语言结构体成员赋值的深拷贝与浅拷贝详解
在C语言开发中,结构体的直接赋值看似简单,却隐藏着严重的内存隐患。本文将深入剖析当结构体包含指针成员时,“浅拷贝”如何引发野指针与双重释放错误,并提供标准的“深拷贝”实现方案,保障程序内存安全。
结果或目标
明确区分并正确处理 C 语言结构体的两种赋值模式:
- 浅拷贝(Shallow Copy):仅复制值类型成员和指针变量的地址,导致多个对象共享同一块堆内存。
- 深拷贝(Deep Copy):不仅复制数据值,还针对指针成员重新申请堆内存并复制具体数据,使对象间完全独立。
准备条件
为了直观展示差异,我们需要对比以下两种情况:
- 无指针成员的结构体:数据直接在栈上分配,直接赋值是安全的。
- 含指针成员的结构体:关键字符串等数据位于堆内存,需使用
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 语言的直接赋值默认执行的是逐字节拷贝(浅拷贝)。对于不含指针的成员,这是完全可行的;但对于包含堆内存指针的结构体,直接赋值会导致“指鹿为马”式的内存共享,进而引发野指针访问和双重释放崩溃。
处理策略很简单:
- 若结构体成员全为值类型(数组、基本整数/浮点数),直接赋值即可。
- 若涉及指针,必须编写专用的拷贝函数,先释放目标旧内存,再按长度申请新内存并使用
strcpy/memcpy复制内容。
以上就是 C 语言结构体成员赋值的深拷贝与浅拷贝详解的详细内容,更多关于 C语言 结构体 内存管理的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













