为什么要在memset上使用bzero?


156

在上学期的系统编程课程中,我们必须用C语言实现一个基本的客户端/服务器。在初始化结构(如sock_addr_in或char缓冲区(我们用来在客户端和服务器之间来回发送数据)时,指示我们仅使用bzero而不memset初始化它们。他从不解释原因,我很好奇这是否有正当理由吗?

我在这里看到:http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdownbzero是更有效的,由于一个只能将要归零记忆的事实,所以也没有必须做memset可能做的任何其他检查。不过,这似乎仍不一定是绝对不memset用于清零内存的原因。

bzero被视为已弃用,而且不是标准C函数。根据该手册,由于这个原因,它memset是首选bzero。那么,为什么还要使用bzeromemset呢?仅仅是为了提高效率,还是更多?同样,memset超过的收益有什么好处bzero,使其成为较新程序的事实上的首选选择?


28
“为什么在内存集上使用bzero?” - 不要 Memset是标准的,bzero不是。

30
bzero是BSDism()。memset()是ansi-c。如今,bzero()可能会实现为宏。一定要让你的教授刮胡子并读一些书。效率是一个虚假的论点。系统调用或上下文切换很容易造成成千上万个时钟滴答,一个缓冲区以总线速度运行一次。如果您想优化网络程序:最小化系统调用的数量(通过读取/写入更大的块)
wildplasser 2013年

7
memset由于“多了点检查”而使效率可能稍低的想法肯定是过早优化的情况:当您可能会损害CPU的可移植性时,忽略一两个CPU指令可能会带来的收益是不值得的码。bzero已过时,这是不使用它的充分理由。
dasblinkenlight

4
通常,您可以添加一个初始化器`= {0}`,而根本不调用任何函数。当在世纪之交的C时代不再要求预先声明局部变量时,这变得更加容易。但是,在上个世纪,仍然有一些真正的旧纸器仍然存在。
MSalters 2015年

1
@SSAnne否,但它很可能来自他受其影响的课程的推荐书,如以下答案之一所述:stackoverflow.com/a/17097072/1428743
PseudoPsyche

Answers:


152

我看不出有任何理由,更喜欢bzeromemset

memset是标准C函数,而bzero从未使用过C标准函数。其基本原理可能是因为您可以使用memsetfunction 实现完全相同的功能。

现在,关于效率,像这样的编译器gcc使用内置的实现,memset0检测到常量时,它们会切换到特定的实现。同为glibc当建宏被禁用。


谢谢。这很有道理。我非常确定memset在这种情况下应该始终使用它,但是对于为什么不使用它感到困惑。感谢您澄清并重申我的想法。
PseudoPsyche

1
我在bzero实现失败方面遇到了很多问题。在非对齐数组上,它通常会超出所提供的长度,并将更多字节清零。切换到以后再也不会遇到这样的问题memset
rustyx 2014年

memset_s如果您要确保编译器不会出于某些与安全性相关的目的(例如,清空包含敏感内容的内存区域),不要忘记使用哪个方法来“清理”内存,请不要忘记应该使用哪个选项。一条信息,例如明文密码)。
Christopher Schultz,

69

我猜您是使用W. Richard Stevens的UNIX Network Programming(或您的老师受到了影响)的。他bzero经常使用代替memset,即使在最新版本中也是如此。这本书太受欢迎了,我认为它已经成为网络编程中的一种习惯用法,这就是为什么您仍然看到它的原因。

我会坚持这样做,memset因为bzero它已被弃用并降低了可移植性。我怀疑您会发现使用一个在另一个之上会带来什么真正的收益。


4
你是正确的。本课程没有必要的教科书,但是我只是再次检查了课程提纲,并且UNIX网络编程确实被列为可选资源。谢谢。
PseudoPsyche

9
实际上比这更糟。在POSIX.1-2001中不推荐使用,在POSIX.1-2008 中将其删除
paxdiablo

9
W. Richard Stevens 引用UNIX Network Programming第三版的第8页- 的确,TCPv3的作者犯了一个错误,即在第一次打印时出现10次时将第二个和第三个参数交换为memset。AC编译器无法捕获此错误,因为这两种情况都相同...这是一个错误,可以使用bzero避免,因为如果使用函数原型,则将两个参数交换为bzero总是会被C编译器捕获。但是,正如paxdiablo指出的那样,不赞成使用bzero。
亚伦牛顿

@AaronNewton,您应该在迈克尔的回答中加上这一点,因为它证实了他的讲话。
Synetech

52

在一个优势,我认为bzero()有超过memset()设定存储到零的是,有一个错误的机会减少正在取得进展。

我不止一次遇到一个看起来像这样的错误:

memset(someobject, size_of_object, 0);    // clear object

编译器不会抱怨(尽管也许在某些编译器上可能会提高一些警告级别),结果是不会清除内存。因为这不会浪费对象-只是将其保留-很有可能该bug不会表现为任何明显的东西。

bzero()不标准的事实是轻微刺激。(FWIW,如果程序中的大多数函数调用都是非标准的,我不会感到惊讶;实际上,编写此类函数是我的职责)。

在这里对另一个答案的评论中,Aaron Newton引用了Stevens等人的Unix Network Programming,第1卷,第3版中的以下内容,第1.2节(添加了重点):

bzero不是ANSI C函数。它源自早期的Berkely网络代码。尽管如此,我们在全文中使用它而不是ANSI C memset函数,因为bzero(只有两个参数)比memset(只有三个参数)更容易记住。几乎每个支持套接字API的供应商都提供bzero,如果不提供,则在unp.h标头中提供宏定义。

确实,TCPv3的作者[TCP / IP Illustrated,第3卷-Stevens 1996]犯了一个错误,即memset在第一次打印中,在10次出现中将第二个和第三个参数交换为。AC编译器无法捕获此错误,因为两个参数的类型相同。(实际上,第二个参数是an int,第三个参数是size_t,通常是an unsigned int,但指定的值0和16仍可用于其他类型的参数。)调用memset仍然有效,因为只有a实际上,很少有套接字函数要求将Internet套接字地址结构的最后8个字节设置为0。但是,这是一个错误,可以通过使用来避免这种错误。 bzero,因为bzero如果使用函数原型,则交换两个参数将始终由C编译器捕获。

我还相信,对的绝大多数调用memset()都是针对零内存的,所以为什么不使用针对该用例量身定制的API?

这样做的一个可能的缺点bzero()是,memcpy()由于它是标准的,因此编译器可能更可能进行优化,因此可能会编写它们来识别它。但是,请记住,正确的代码仍然比经过优化的错误代码更好。在大多数情况下,使用bzero()不会对程序的性能产生明显影响,它bzero()可以是扩展为的宏或内联函数memcpy()


是的,我想这可能是在这样的教室环境中工作的原因,这样可以使学生减少混乱的感觉。但是,我认为教授的情况并非如此。他是一个非常大的RTFM老师。如果您有手册可以回答的问题,他会在课堂上拉上投影仪的手册页并向您展示。他非常想让所有人都知道该手册可供阅读并回答您的大多数问题。与其他教授相比,我对此表示感谢。
PseudoPsyche 2013年

5
我认为这是一个甚至可以在课堂外提出的论据-我已经在生产代码中看到了这个错误。这使我很容易犯一个错误。我还猜想,绝大多数memset()调用只是将内存块归零,我认为这是的另一个论点bzero()bzero()无论如何,“ b” 代表什么?
Michael Burr 2013年

7
+1。这memset违反了“ buffer,buffer_size”的通用参数顺序,这使其特别容易出错。
jamesdlin

在Pascal中,他们通过将其称为“ fillchar”来避免这种情况,它需要一个char。大多数C / C ++编译器都会采用这种方式。这让我感到奇怪,为什么编译器不说“您正在传递期望字节的32/64位指针”,而将您踢入编译器错误。
2013年

1
@Gewure第二和第三个参数顺序错误;引用的函数调用完全不执行任何操作
Ichthyo

4

想提一下有关bzero vs. memset参数的内容。安装ltrace,然后比较其功能。在具有libc6(2.19-0ubuntu6.6)的Linux上,调用完全相同(通过ltrace ./test123):

long m[] = {0}; // generates a call to memset(0x7fffefa28238, '\0', 8)
int* p;
bzero(&p, 4);   // generates a call to memset(0x7fffefa28230, '\0', 4)

有人告诉我,除非我在libc深层工作或任何数量的内核/系统调用接口工作,否则不必担心它们。我只需要担心的是,该调用满足将缓冲区清零的要求。其他人提到哪一个比另一个更可取,所以我在这里停止。


发生这种情况是因为某些版本的GCC会memset(ptr, 0, n)在看到时发出代码, bzero(ptr, n)并且无法将其转换为内联代码。
zwol

@zwol实际上是一个宏。
SS Anne

1
我计算机上的@SSAnne gcc 9.3本身可以完成此转换,而无需系统头文件中的宏的任何帮助。extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }拨打到的电话memset。(包括stddef.hsize_t没有其他任何可能干扰。)
zwol

4

您可能不应该使用bzero,它实际上不是标准C,这是POSIX的东西。

并请注意,“ was”一词- 在POSIX.1-2001 中已弃用,并在POSIX.1-2008中根据memset 删除了该词,因此最好使用标准C函数。


标准C是什么意思?您是说在标准C库中找不到它?
Koray Tugay 2015年

@Koray,标准C表示ISO标准,是的,bzero不属于其中。
paxdiablo 2015年

不,我的意思是,我不知道您所说的任何标准。ISO标准是否意味着标准C库?语言附带的?我们知道它将存在的最小库吗?
Koray Tugay 2015年

2
@ Koray,ISO是负责C标准的标准组织,当前的组织为C11,早期的组织为C99和C89。他们确定了实现才被视为C所必须遵循的规则。因此,是的,如果标准说实现必须提供内存集,那么它将为您服务。否则,它不是C.
paxdiablo

2

对于memset函数,第二个参数是an int,第三个参数是size_t

void *memset(void *s, int c, size_t n);

这通常是一个unsigned int,但是如果0 and 16分别以错误的顺序(如16和0)为第二和第三个参数输入类似的值,则对memset的这种调用仍然可以工作,但将无济于事。因为要初始化的字节数指定为0

void bzero(void *s, size_t n)

通过使用bzero可以避免这种错误,因为如果使用函数原型,则将两个参数交换为bzero总是会被C编译器捕获。


1
如果仅将调用视为“将此内存设置为此大小的该值”,或者如果您拥有一个为您提供原型的IDE,或者即使您只是知道自己在做什么,那么使用memset也可以避免此类错误。做:-)
paxdiablo 2015年

同意,但是此功能是在此类智能IDE不支持时创建的。
2015年

2

简而言之:则 memset需要更多的组装操作bzero

这是来源:http//fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown


是的,这是我在OP中提到的一件事。实际上,我什至链接到该确切页面。事实证明,由于某些编译器优化,似乎并没有太大的区别。有关更多详细信息,请参见ouah接受的答案。
PseudoPsyche

6
这仅表明memset的一个垃圾实现很慢。在MacOS X和某些其他系统上,memset使用的代码是在启动时设置的,具体取决于您使用的处理器,它充分利用了向量寄存器,并且对于大容量的代码,它以巧妙的方式使用预取指令来获取最后一位的速度。
gnasher729 2015年

更少的指令并不意味着执行速度更快。实际上,由于循环展开,函数内联,循环对齐,优化通常会增加二进制大小和指令数量...查看任何体面的优化代码,您通常会发现它比
卑鄙的

2

随心所欲。:-)

#ifndef bzero
#define bzero(d,n) memset((d),0,(n))
#endif

注意:

  1. 原始内容不bzero返回任何内容,memset返回空指针(d)。可以通过将typecast添加到定义中的void来解决此问题。
  2. #ifndef bzero即使原始功能存在,也不会阻止您隐藏它。它测试宏的存在。这可能会引起很多混乱。
  3. 创建指向宏的函数指针是不可能的。当bzero通过函数指针使用时,这将不起作用。

1
@Leeor这是什么问题?对宏有普遍的反感吗?还是您不喜欢该宏可以与函数混淆(甚至可能将其隐藏)的事实?
Palec 2015年

1
@Palec,后者。将重新定义隐藏为宏会导致很多混乱。另一个使用此代码的程序员认为他正在使用一件事,在不知不觉中被迫使用另一件事。那是定时炸弹。
Leeor,2015年

1
再三考虑之后,我同意这确实是一个不好的解决方案。除其他外,我发现了一个技术原因:bzero通过函数指针使用时,这将不起作用。
Palec 2015年

您确实应该将宏称为bzero。这是暴行。
丹·贝查德

-2

memset需要3个参数,bzero需要2个内存,这限制了额外的参数将再占用4个字节,并且在大多数情况下,它将用于将所有内容设置为0

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.