C语言中如何深度复制包含字符数组的结构体?(如何复制这些数组?)

11

I have the following struct in my C program

struct person {
    char key[50];
    char color[20];
    int age;
};

我想要创建这个结构体的深拷贝。我已经设置好了我的深拷贝函数,但是我有些困惑如何深拷贝字符串。我听说过一些人使用 strcpy,而另一些人使用 strdup

在我的程序中,我希望深拷贝的人的键和颜色不会受到释放原始人的影响。一旦设置,键和颜色不能更改。对于我的目的,我应该使用 strcpy 还是 strdup 函数?


3
由于发布的结构体中没有“char *”字段,因此除了使用memcpy(destStruct, srcStruct, sizeof destStruct);以外,完全没有必要进行其他操作。 - user3629249
因为数组(不仅是指向它们的指针!)是您结构体的成员,所以浅复制就是深复制。没有间接引用。您的结构体的内存占用量大于70个字节,在堆栈上,在堆上,无论您想放在哪里。 - Peter - Reinstate Monica
可能是[是否对带有Char[]的结构进行浅复制就足够了?]的重复问题(链接:https://dev59.com/nkvSa4cB1Zd3GeqPiOc4)。 - ecatmur
4个回答

16
在标准C中(即Post-K&R),您可以直接分配它们。下面的函数仅是为了使示例清晰,您始终只需在原地分配:
void deepCopyPerson(struct person *target, struct person *src)
{
    *target = *src;
}

为了详细说明:char数组是结构体对象的一部分(真正的数组,而不仅仅是指针!),因此它们与对象一起分配和复制。
为了满足怀疑者;-)的要求,我在标准草案1570中进行了搜索:
6.5.16 赋值运算符
语义
赋值运算符将一个值存储在左操作数指定的对象中。[接下来是类型转换和排序考虑因素,这些与此处无关。]

[...]

6.5.16.1 简单赋值

约束条件

必须满足以下条件之一:

  • [...]
  • 左操作数具有与右侧类型兼容的结构体或联合体类型的原子、限定或非限定版本;

[...]

语义

简单赋值 (=) 中,右操作数的值会被转换为赋值表达式的类型,并替换左操作数所指定的对象中存储的值。


1 《C程序设计语言》的第一版于1978年由贝尔实验室的Brian W. Kernighan和Dennis M. Ritchie编写并出版,是第一个C语言版本的非正式语言规范,通常被称为“K&R C”,以两位书籍作者命名(与10年后规定的ANSI C进行区分)。本书第一版在第121页中包含以下句子:

关键规则是结构体能够执行的唯一操作是使用 & 取其地址,并访问其中的成员。

特别地,你不能对结构体进行赋值。(但他们继续说,“这些限制将在即将推出的版本中被取消”。)

事实上,这是ANSI版本中唯一实质性的(而不是语法上的)增加之一。Kernighan和Ritchie还准备了一本配套的“ANSI C版”书籍。这句话现在在第129页,已更新为:

唯一合法的结构操作是将其作为一个单位进行复制和赋值,使用 & 获取其地址以及访问其成员。他们详细阐述道:复制和赋值包括向函数传递参数以及从函数返回值。这种等效性在最新的C++标准中仍然成立。由于我们在C++的根管道中,下一条语句是一个必然的"结构不能比较"。有趣的是,他们添加了这个句子——显然,在引入内置赋值之后,内置比较自然而然地被提出来,但被拒绝了。在我看来,从来没有一个好的理由:如果你可以按成员方式赋值,为什么不以同样的方式进行比较呢?在许多情况下,这样做就足够了,而手动比较很难维护。在C++20中,使用spaceship operator使这成为可能,当它(明确地)默认时,它会生成所有其他比较;确实,默认为按成员方式比较。

这不会复制一个结构体。为了复制一个结构体,请使用memcpy()而不是赋值。 - user3629249
1
@user3629249 你怎么想的?我认为在inet头文件中他们使用技巧将仅仅是一个数组(而没有其他内容)放入结构体中,这么做是为了防止数组退化,并能够将其作为参数传递。 - Peter - Reinstate Monica
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Peter

7

如需对包含数组(没有任何指针)的结构体进行深度复制,深度复制很简单。

struct person x = {"Key", "Color", 42};   /*  initialise to something */
struct person y = x;

如果“strings”是指针,则此方法无效。需要分配新的字符串,然后使用类似strcpy()的函数来复制成员。

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

struct pointer_person
{
    char *key;
    char *color;
    int age;
};

struct pointer_person deep_copy(struct pointer_person p)
{
     struct pointer_person retval;
     retval.key = malloc(strlen(p.key) + 1);
     strcpy(retval.key, p.key);
     retval.color = malloc(strlen(p.color) + 1);
     strcpy(retval.color, p.color);
     retval.age = p->age;
     return retval;
}

int main()
{
   struct pointer_person p;
   struct pointer_person pc;

   p.key = malloc(50);
   strcpy(p.key, "A key");
   p.color = malloc(20);
   strcpy(p.color, "A colour");
   p.key = 42;

   pc = deep_copy(p);

   /* do stuff with pc and c */

   free(p.key);
   free(p.color);
   free(pc.key);
   free(pc.color);
   return 0;
}

上面的代码中省略了一些错误检查(例如,在复制之前需要检查 malloc() 是否成功)。

我认为 struct person y = x; 是一种初始化,而不是赋值(这在 static struct person y = x; 中会变得相关)。C++ 更清楚地区分了这两者(复制构造函数 vs. 赋值运算符)。 - Peter - Reinstate Monica
@PeterA.Schneider - 我同意 struct person y = x 是一个初始化,而不是赋值。然而,我不明白你评论的原因,因为我在这个答案中没有说明它是初始化还是赋值。 - Peter
这个问题并不是在询问复制结构体本身,而只是关于 char [] 成员的。 - Iharob Al Asimi
@IharobAlAsimi - 好的.....问题标题现在说了,因为您刚刚修改了它(在问题提出三年后),以此来说明。我的答案也不会受到您的编辑影响。无论如何都是无意义的点——看起来OP自从发布这个问题以来就没有在SO上活跃过。我无法预测您最新的编辑是否会说服那三个对您的回答投反对票的人改变主意。 - Peter

-1

strcpy()和strdup()之间有一个区别。

  • strdup()分配空间并返回指向字符串副本的指针,您还必须使用free()释放返回的指针。

  • strcpy()将分配的空间并将字符串复制到其中。

看起来在您的情况下应该使用strcpy(),因为您的结构字段不是指针,所以无法将指向分配空间的指针分配给它们,这正是strdup()返回的内容。

然而,正如this answer中解释的那样,实际上您不需要这样做。


那个memcpy()的用法实际上是浅拷贝。如果结构体包含指针成员,它不会进行深拷贝。 - Peter
5
直接复制这些结构体并不太合适。语言本身已经提供了一个内置的方便赋值的运算符,也就是恰当地命名为“=”的赋值运算符。 - Peter - Reinstate Monica

-1
如果您使用strcpy(),则必须自己分配内存。strdup()将为您完成此操作。您可以使用任何一种方法来创建一个与原始内存块分离的新内存块,但自然而然地,strdup()更简单,因为它不需要单独的malloc(strlen())调用。

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