memcpy和memset的历史与分配和初始化

时间:2018-07-13 17:59:44

标签: c

所以,我的理解是以下代码:

somestruct_t a = {0};
somestruct_t b;
b = a;
在可能的情况下,

总是可取的:

somestruct_t a;
somestruct_t b;
memset(&a, 0, sizeof(a));
memcpy(&b, &a, sizeof(a));

几乎总是可以使用顶级构造……这引出了我的问题:由于顶级代码的性能都很好,并且对我来说显然对学习语言的人来说更直观,为什么memsetmemcpy模式在C甚至非OO C ++代码中如此流行吗?从字面上看,我从事数十年的每个项目都喜欢底部模式。

我认为这是有历史原因的,例如非常老的编译器不支持它或类似的东西,但是我非常想知道具体原因。

我知道一般历史问题是题外话,但这是我想更好理解的非常具体错误做法。

编辑我并不是要断言memcpy和memset通常不好。我说的是一个非常具体的分配或初始化单个结构的使用模式。

3 个答案:

答案 0 :(得分:2)

听起来您的经历与我的经历以及这里的其他一些评论员都大不相同。

我不认识任何喜欢的人

memcpy(&a, &b, sizeof(a));

a = b;

在我的编程世界(以及我可以想象的几乎所有世界)中,简单分配比memcpy更可取。 memcpy用于移动任意数据块(类似于strcpy,但当它是任意字节而不是以空终止的字符串时)。很难想象为什么有人会主张使用memcpy而不是结构分配。当然,到处都有个别的程序员养成各种不良习惯,因此,我想如果有人喜欢相反的习惯,我也不会感到惊讶,但是我不得不说,我通常不同意他们的工作。

有人在评论中推测也许有一些历史先例在起作用,但是至少对于memcpy-对-分配问题,我可以肯定地说这不是事实。

从前,有C90 memcpy之前有BSD bcopy,但是有bcopy之前没有 标准功能用于从a点到b点有效复制一堆字节。但是有结构赋值,实际上几乎从一开始就已经存在于语言中。而且,结构分配通常使用一个不错的,紧密的,编译器生成的字节复制循环。所以有一段时间,做这样的事情很时髦:

#define bcpy(a, b, n) (*(struct {char x[n];} *)a = *(struct {char x[n];} *)b)

我可能弄错了语法,但是这劫持了编译器执行有效结构分配的能力,并重新利用它来将n个字节从任意指针b复制到任意指针a ,即就像bcopymemcpy一样。

换句话说,并不是像memcpy先出现,然后是结构分配-实际上恰恰相反!

现在,memset与结构初始化是另一回事。

大多数将结构归零的“干净”方法是初始化,但是当然要在定义之后的某个时刻将结构全部设置为零并不罕见。具有动态分配的结构并使用malloc / realloc而不是calloc的情况也很常见。因此,在这些情况下,memset很有吸引力。我认为现代C具有可以随时使用的结构常数,但是我想我不是唯一一个仍然不了解它们的人,因此仍然倾向于使用memset

因此,我不会考虑使用memset是较差的样式,而不是将memcpy用作结构分配的较差样式。

尽管我已经看过并编写了类似代码的代码

struct s zerostruct = { 0 };

然后再

a = zerostruct;

作为“更好的样式”替代

memset(&a, 0, sizeof(a));   

底线:我不同意建议使用memcpy而不是结构分配,我批评任何喜欢它的人。但是memset对于归零结构非常有用(而且不建议使用),因为替代方案并不那么引人注目。

答案 1 :(得分:2)

有一个用例,其中

struct somestruct  foo = { 0 };

不够,并且

struct somestruct  foo;
memset(&foo, 0, sizeof foo);

需要代替:当结构中的填充可能很重要时。

您会看到,两者之间的唯一区别是后者也被隔离以清除结构填充为零,而前者仅被隔离以清除结构成员为零。

人们可能关心填充的原因是基于向上/未来的兼容性。如果当前程序中的填充被隔离为零,则库的未来版本可以将填充“重新使用”用于新的数据字段,并且仍然可以与较旧的二进制文件一起使用。

自C99以来,新的C库确实并且应该为此目的明确保留一些成员。这通常就是为什么您在许多库定义的结构中甚至在Linux内核-用户空间接口中看到“保留”字段的原因。因此,填充问题实际上仅与C99支持广泛传播之前开发的结构有关。换句话说,仅在旧图书馆中。

我知道应该始终使用memset()清除的一种结构是struct sigaction,在POSIX.1中定义。在大多数POSIXy系统中,它是一个完全正常的结构(因此,只需清除该结构的成员的代码就可以在那些系统上正常工作),但是由于在不同时间有各种不同的实现方式(尤其是如何实现信号屏蔽) ,我相信仍然存在带有C库的系统,这些系统的结构版本对清除填充仍然很重要。

(这是因为sa_handlersa_sigaction成员通常是一个联合体,和/或因为sigset_t的定义可能已更改。)

在其他一些较旧的库中可能还有其他库,因此在处理具有1999之前的根的库时,我建议使用memset()惯用语,其示例代码也使用它。

答案 2 :(得分:1)

好吧,总是总是 优于memset()memcpy()来进行结构赋值和“零初始化”。同样可能是编译器在大小或速度上优化memset/cpy()(具有这两个标准库函数的“特殊知识”)方面做得更好。当然,这有点奇怪,但是可能。

此外,由于“零初始化”在堆分配的结构上不起作用,因此要保持始终使用memset()的一致性,有话要说。

类似的情况适用于您可能想要复制几个相邻结构(数组的一部分)的情况-再次,如果您始终使用memcpy(),则代码更加一致。

从历史的角度来看,我曾使用过破坏本地结构初始化的工具链。在经过此类工具链的项目中,即使取消了此类工具链,也将始终使用“始终使用memset()”。事实是,即使您的工具链的memset()损坏了,您也可以自己创建,但不能进行自己的本地结构初始化...