使用赋值而不是memcpy()在C中复制结构体

24

直到最近,我只看到过使用memcpy()复制结构字段的情况。在课程和在线教程中,将一个结构体的内容复制到另一个结构体中通常看起来像是:

Up until recently, I have only seen copying of structure fields done with memcpy(). In classes and online instructions, copying the contents of one struct into another generally looks like

struct block *b0 = malloc(sizeof(struct block));
struct block *b1 = malloc(sizeof(struct block));
/* populate fields in *b0 */
memcpy(b1, b0, sizeof *b1); /* copy contents of b0 into b1 */
/* free b0, b1 */

然而,这个任务也可以通过一个简单的赋值语句来完成,取代 memcpy() 函数。

*b1 = *b0; /* dereferenced struct assignment */

为什么这种方法没有被广泛使用(至少在我的有限经验中)?这两种方法——赋值和memcpy()——是否等效,或者一般情况下使用memcpy()是否有一些强制的理由?


7
在 C 语言中,这两种方法是等价的,并且都不会进行深度拷贝。 - Some programmer dude
3
memcpy(b1, b0, sizeof(struct block)); 这种写法非常不好,容易出错。建议使用赋值语句或者 memcpy(b1, b0, sizeof *b1); 进行替换。 - R.. GitHub STOP HELPING ICE
@R..: 你可能也可以主张使用memcpy(b1, b0, sizeof *b0)。使用sizeof *消除了两个可能的错误之一,而使用sizeof(struct block)则没有消除任何一个。 - Steve Jessop
@Jeyaram之前忘记感谢你的编辑了,谢谢。 - olliezhu
5个回答

36

这两种方法等效且执行一个浅拷贝。这意味着结构本身被复制,但是结构所引用的任何内容都不会被复制。

至于为什么memcpy更受欢迎,我不确定。旧版本的C语言不支持结构赋值(尽管从1978年开始作为常见扩展使用),因此也许memcpy风格作为一种制作更可移植代码的方式成为主流?无论如何,在PC编译器中广泛支持结构赋值,并且使用memcpy容易出错(如果大小错误,可能会发生糟糕的事情),因此最好在可能的情况下使用结构赋值。

然而,只有在某些情况下memcpy才能起作用。例如:

  • 如果要将结构复制到或从未对齐的缓冲区-例如,保存/加载到/自磁盘或通过网络发送/接收,则需要使用memcpy,因为结构赋值需要源和目标都正确对齐。
  • 如果您要在结构之后打包其他信息,例如使用零元素数组,则需要使用memcpy并将此附加信息纳入大小字段。
  • 如果要复制结构的数组,则可能更有效地进行单个memcpy而不是循环并逐个复制结构。但是,它可能也不会。难以说,memcpy实现在其性能特征上有所不同。
  • 一些嵌入式编译器可能不支持结构赋值。当然,该编译器可能没有其他更重要的支持。

需要注意的是,尽管在C中memcpy和结构体赋值通常是等价的,在C++中memcpy和结构体赋值是不等价的。一般来说,在C++中最好避免memcpy结构体,因为结构体赋值可以重载执行额外的操作,如深度复制或引用计数管理。


好的,谢谢。对于深拷贝和浅拷贝的混淆我感到抱歉。我原本以为浅拷贝就像指针赋值一样,比如 b1 = b0 而不是 *b1 = *b0 - olliezhu

7
这可能不是你想要的确切答案。
我正在解释我遇到的场景。
当我们使用memcpy()时,它会按字节复制到目标位置。因此,在ARM架构中不必担心数据对齐问题。如果您使用=运算符,并且任何一个地址未对齐到4字节,则会出现对齐错误。
根据Arm网站:
指向超出最后一个写入的字节的目标位置的指针。这使得内存块的字符串连接可以“完美对齐”地继续写入过程。 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0175k/Cihbbjge.html

2
编译器不应该负责确保所有内容都正确对齐吗? - alk
2
@alk,编译器假设所有内容都已正确对齐,这可以在许多平台上实现显著的性能提升。如果您只是使用malloc为结构分配内存,它将确保其对齐,但是如果您开始进行指针算术运算或从文件或网络中读取结构,则不再适用,并且可能会违反编译器的假设,导致未定义的行为。请注意,在x86上看不到这一点,因为大多数x86操作可以透明地处理不对齐的访问,但会略微降低性能。 - bdonlan
@bdonlan 好的,我明白了与读取/写入可能对齐不良的I/O缓冲区相关的问题。无论如何,我不明白为什么使用(正确类型的)指针算术会有问题,因为编译器应该在编译时知道生成正确代码所需的所有内容。 - alk
1
@alk 没错。只要你遵守规则,一切都是对齐的。一旦你开始将指针转换为 char *,添加偏移量,并将它们转换回来,编译器就不再知道发生了什么。 - bdonlan
如果分配成功,返回的指针将被适当地对齐,以便可以将其分配给任何类型对象的指针,然后用于访问所分配空间中的该对象或该对象数组。因此,在这里对齐不是问题,而且memcpy与结构赋值一样好(除了更容易出错)。-- C99 §7.20.3 - Craig Barnes

6

我重新激活这个旧问题,因为答案没有解释为什么 memcpy 实际上更受欢迎。

memcpy 更受欢迎是因为它清楚地表明程序员想要复制内容而不仅仅是指针。

在下面的例子中,这两个赋值语句做了两件非常不同的事情:

struct Type *s1,*s2;
*s1=*s2;
s1=s2;

不小心使用一个而不是另一个可能会产生灾难性的影响。编译器不会抱怨。除非当使用未初始化的指针时程序崩溃,否则这个错误可以长时间不被注意并产生奇怪的副作用。

将其写成以下之一:

memcpy(s1,s2,sizeof(*s1));
memcpy(s1,s2,sizeof(*s2));
memcpy(s1,s2,sizeof(struct Type));

请让读者知道,这里的意图是为了复制内容(牺牲类型安全和边界检查)。

一些编译器(例如 gcc)甚至在遇到以下代码时会发出 sizeof 的警告:

memcpy(s1,s2,sizeof(s1));

最近在一次讨论中,一位同事坚持认为结构体赋值更具类型安全性,因此更好。我喜欢你的回答,但不知道如何看待我的同事的观点。我想双方都有其优点。 - Thagomizer

0
有些人喜欢使用memcpy,因为这是他们学到的,他们从未想过可以直接进行赋值操作(在古代,赋值操作是不被允许的,但那是很久以前的事了)。由于malloc()分配的内存总是正确对齐的,所以不需要担心对齐问题。而且,由于编译器可以轻松地将此赋值转换为memcpy调用,因此它永远不会比memcpy更慢或更多代码。当然,也有一些嵌入式系统使用着非常过时的编译器。

-2

在嵌入式平台上工作的人们更喜欢使用memcopy而不是直接分配结构。 主要是当你处理嵌入式平台时,一些编译器不支持直接结构分配,因此需要使用memcopy。 如果你在PC上工作,那么两种方法都没有问题,都是有效的。


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