所以,我的理解是以下代码:
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));
几乎总是可以使用顶级构造……这引出了我的问题:由于顶级代码的性能都很好,并且对我来说显然对学习语言的人来说更直观,为什么memset
和memcpy
模式在C甚至非OO C ++代码中如此流行吗?从字面上看,我从事数十年的每个项目都喜欢底部模式。
我认为这是有历史原因的,例如非常老的编译器不支持它或类似的东西,但是我非常想知道具体原因。
我知道一般历史问题是题外话,但这是我想更好理解的非常具体错误做法。
编辑我并不是要断言memcpy和memset通常不好。我说的是一个非常具体的分配或初始化单个结构的使用模式。
答案 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
,即就像bcopy
或memcpy
一样。
换句话说,并不是像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_handler
和sa_sigaction
成员通常是一个联合体,和/或因为sigset_t
的定义可能已更改。)
在其他一些较旧的库中可能还有其他库,因此在处理具有1999之前的根的库时,我建议使用memset()
惯用语,其示例代码也使用它。
答案 2 :(得分:1)
好吧,总是总是 优于memset()
和memcpy()
来进行结构赋值和“零初始化”。同样可能是编译器在大小或速度上优化memset/cpy()
(具有这两个标准库函数的“特殊知识”)方面做得更好。当然,这有点奇怪,但是可能。
此外,由于“零初始化”在堆分配的结构上不起作用,因此要保持始终使用memset()
的一致性,有话要说。
类似的情况适用于您可能想要复制几个相邻结构(数组的一部分)的情况-再次,如果您始终使用memcpy()
,则代码更加一致。
从历史的角度来看,我曾使用过破坏本地结构初始化的工具链。在经过此类工具链的项目中,即使取消了此类工具链,也将始终使用“始终使用memset()
”。事实是,即使您的工具链的memset()
损坏了,您也可以自己创建,但不能进行自己的本地结构初始化...