C语言中的浅拷贝和深拷贝

19

我试过谷歌搜索,但只有面向对象的语言出现在搜索结果中。

据我理解,浅复制是复制结构体的特定成员。

比如说一个结构体是

typedef struct node
{
    char **ok;
    int hi;
    int yep;
    struct node *next;
}node_t

复制 char** 将是浅复制,

但复制整个链表将是深度复制?

我理解的对吗?谢谢。

3个回答

35
不。在这种特定的情况下,浅复制意味着你复制对象的“引用”(指针等),而这些引用或指针的后备存储是相同的,它们指向同一内存位置上的同一个对象。
相比之下,深复制意味着你复制整个对象(结构体)。如果它有可以浅复制或深复制的成员,你也会对它们进行深复制。考虑以下示例:
typedef struct {
    char *name;
    int value;
} Node;

Node n1, n2, n3;

char name[] = "This is the name";

n1 = (Node){ name, 1337 };
n2 = n1; // Shallow copy, n2.name points to the same string as n1.name

n3.value = n1.value;
n3.name = strdup(n1.name); // Deep copy - n3.name is identical to n1.name regarding
                           // its *contents* only, but it's not anymore the same pointer

1

据我所知,C语言中没有实际的“浅拷贝”或“深拷贝”。

有些答案可能是正确的,但如果你从面向对象编程的角度来看,它们会误导你。

在C语言中,只有一种内置的复制技术,即使用“=”运算符时的数据复制。

C语言将结构体的元数据存储在RAM中,并确保一旦创建了结构体,其大小就不会改变,并且可以从分配的内存的特定部分访问变量。

当您使用“=”运算符时,它仅将存储在结构体中的数据按位复制到另一个结构体中,因为它们具有相同的大小。

在结构体中静态分配的变量和数组具有固定的大小,并完全存储在结构体的分配内存中。动态分配的变量都是指针。指针变量是静态分配内存,相当于无符号长整型。

当您在结构体中有一个指针时,它仅存储地址,即它指向的内存,也就是动态分配的位置,结构体实际上没有任何信息表明指针指向的是内存。操作系统和编译器跟踪动态分配的内存。

例如:

#include <stdio.h>
#include<stdlib.h> 
struct Foo
{
    int value_1;
    int *value_2;
    int value_3[3];
}S1,S2;

 
void print(struct Foo S)
{
    printf("%d,%d,%d,%d,%d\n",S.value_1,*(S.value_2),S.value_3[0],S.value_3[1],S.value_3[2]);
}
int main()
{
    S1.value_1=43;
    S1.value_2=(int*)malloc(sizeof(int));
    *(S1.value_2)=55;
    S1.value_3[0]=101;
    S1.value_3[1]=102;
    S1.value_3[2]=103;
    printf("S1:");
    print(S1);
    S2=S1;
    printf("S2:");
    print(S2);
    S1.value_1=4300;
    *(S1.value_2)=5500;
    S1.value_3[0]=10100;
    S1.value_3[1]=10200;
    S1.value_3[2]=10300;
    printf("\nAfter Altering\n");
    printf("S1:");
    print(S1);
    printf("S2:");
    print(S2);
    return 0;
}
 

输出:

S1:43,55,101,102,103
S2:43,55,101,102,103

After Altering
S1:4300,5500,10100,10200,10300
S2:43,5500,101,102,103

简而言之,在C语言中,复制操作有两种行为:

  • 对于动态分配的变量,也就是指针,使用浅拷贝
  • 对于静态分配的变量或数组,使用深拷贝

此外,如果您想要创建一个指向结构体变量(非新分配)的指针,只要内存在作用域内,它将像引用一样起作用。无论是使用普通变量还是结构体指针进行的更改都会导致两者反映相同。

int a=20;
Foo S1,*S2;
S2=&S1;
S1.value1=10;
S1.value2=&a;
S1.value3[0]=101;
S1.value3[1]=102;
S1.value3[2]=103;
S2->value1=15;

在上述情况下,我们尝试操纵值时无论选择哪个都没有关系,因为两者都会影响相同的物理内存。
当您将指针作为指针/地址进行复制时,会发生浅复制。 当您将指针的值复制到另一个变量/指针中时,它的行为类似于在两个非指针之间进行复制。

这是完全误导性的。添加一个全局int(静态),并使s1.value2指向其地址。结果将完全相同。将S1指定为指向Foo结构体的指针,并使其指向malloc分配的内存(当然,使用->S2 = *S1等修复与S1相关的语法)。结果将完全相同。 - Bruno
这是完全误导性的。添加一个全局整型变量(静态),并使s1.value2指向它的地址。结果将完全相同。将S1指针指向一个Foo结构体,并使其指向一个malloc分配的内存(当然要修复代码中与S1相关的语法,例如使用->S2 = *S1等)。结果将完全相同。 - Bruno
@Bruno,我漏掉了一个重要的假设,谢谢你指出来。在这篇文章中,我提到了在使用“=”时复制非指针结构变量的情况。我没有包括指针指向现有内存的单独变量的情况。 - Alan Jimcy

-2

复制构造函数用于使用先前创建的同一类对象初始化新对象。默认情况下,编译器会编写浅拷贝。当不涉及动态内存分配时,浅拷贝可以正常工作,因为当涉及动态内存分配时,两个对象将指向堆中的同一内存位置,因此我们编写了深拷贝以消除这个问题,使得两个对象在内存中都有自己的属性副本。要阅读完整的示例和解释细节,请参见本文关于浅拷贝和深拷贝构造函数之间的区别的部分。


1
这篇文章特别讲述C语言。构造函数和拷贝构造函数是专属于C++(以及面向对象编程)的特性。虽然你的回答解释得很好,但在这个情境下可能会完全误导人。很抱歉,但它应该被踩下去。 - kyriakosSt
同意@kyriakosSt的观点,该帖子与ShadyBears最初提出的问题完全无关。 - dragon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接