在使用之前将诸如sockaddr_in,sockaddr_in6和addrinfo之类的结构清零时,这是正确的:memset,初始化器还是其中之一?

每当我在书籍,手册页和网站中查看真实代码或示例套接字代码时,我几乎总会看到类似下面的内容:

struct sockaddr_in foo; memset(&foo, 0, sizeof foo); /* or bzero(), which POSIX marks as LEGACY, and is not in standard C */ foo.sin_port = htons(42); 

代替:

 struct sockaddr_in foo = { 0 }; /* if at least one member is initialized, all others are set to zero (as though they had static storage duration) as per ISO/IEC 9899:1999 6.7.8 Initialization */ foo.sin_port = htons(42); 

要么:

 struct sockaddr_in foo = { .sin_port = htons(42) }; /* New in C99 */ 

要么:

 static struct sockaddr_in foo; /* static storage duration will also behave as if all members are explicitly assigned 0 */ foo.sin_port = htons(42); 

例如,将struct addrinfo提示设置为零之前也可以找到相同的值,然后将其传递给getaddrinfo。

为什么是这样? 据我所知,不使用memset的例子很可能等同于那样做的例子,如果不是更好的话。 我意识到存在差异:

  • memset将所有位设置为零,这不一定是将每个成员设置为0的正确位表示。
  • memset还会将填充位设置为零。

将这些结构设置为零时,这些差异中的任何一个是相关的还是必需的行为,因此使用初始化器是错误的? 如果是,为什么,以及哪个标准或其他来源validation了这一点?

如果两者都正确,为什么memset / bzero倾向于出现而不是初始化器? 这只是风格问题吗? 如果是这样,那很好,我不认为我们需要一个主观的答案,哪个是更好的风格。

通常的做法是优先使用初始化程序而不是memset,因为通常不需要所有位零,而是我们希望类型的正确表示为零。 这些与套接字相关的结构是否相反?

在我的研究中,我发现POSIX似乎只需要在http://www.opengroup.org/onlinepubs/000095399/basedefs/netinet/in.h.html中将sockaddr_in6(而不是sockaddr_in)归零,但是没有提到如何它应该归零(memset或初始化器?)。 我认为BSD套接字早于POSIX,它不是唯一的标准,它们对遗留系统或现代非POSIX系统的兼容性考虑因素是什么?

就个人而言,我更喜欢从一种风格(也许是良好实践)的角度来使用初始化器并完全避免使用memset,但我不情愿因为:

  • 其他源代码和半规范文本(如UNIX网络编程)使用bzero(例如,第2版的第101页和第3版的第124页(我同时拥有))。
  • 由于上述原因,我很清楚它们并不完全相同。

部分初始化方法(即’ { 0 } ‘)的一个问题是GCC会警告您初始化程序是不完整的(如果警告级别足够高;我通常使用’ -Wall ‘并经常使用’ -Wextra ‘) 。 使用指定的初始化方法,不应该给出警告,但C99仍然没有得到广泛使用 – 尽管这些部分可以广泛使用,但可能在Microsoft的世界中。

倾向于赞成一种方法:

 static const struct sockaddr_in zero_sockaddr_in; 

其次是:

 struct sockaddr_in foo = zero_sockaddr_in; 

在静态常量中省略初始化器意味着一切都为零 – 但编译器不会干扰(不应该干扰)。 赋值使用编译器的固有内存副本,除非编译器严重不足,否则它不会比函数调用慢。


GCC随着时间的推移发生了变化

GCC版本4.4.2至4.6.0从GCC 4.7.1生成不同的警告。 具体来说,GCC 4.7.1将= { 0 }初始化程序识别为“特殊情况”并且不会抱怨,而GCC 4.6.0等则抱怨。

考虑文件init.c

 struct xyz { int x; int y; int z; }; struct xyz xyz0; // No explicit initializer; no warning struct xyz xyz1 = { 0 }; // Shorthand, recognized by 4.7.1 but not 4.6.0 struct xyz xyz2 = { 0, 0 }; // Missing an initializer; always a warning struct xyz xyz3 = { 0, 0, 0 }; // Fully initialized; no warning 

使用GCC 4.4.2(在Mac OS X上)编译时,警告是:

 $ /usr/gcc/v4.4.2/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c init.c:9: warning: missing initializer init.c:9: warning: (near initialization for 'xyz1.y') init.c:10: warning: missing initializer init.c:10: warning: (near initialization for 'xyz2.z') $ 

使用GCC 4.5.1编译时,警告是:

 $ /usr/gcc/v4.5.1/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c init.c:9:8: warning: missing initializer init.c:9:8: warning: (near initialization for 'xyz1.y') init.c:10:8: warning: missing initializer init.c:10:8: warning: (near initialization for 'xyz2.z') $ 

使用GCC 4.6.0编译时,警告是:

 $ /usr/gcc/v4.6.0/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c init.c:9:8: warning: missing initializer [-Wmissing-field-initializers] init.c:9:8: warning: (near initialization for 'xyz1.y') [-Wmissing-field-initializers] init.c:10:8: warning: missing initializer [-Wmissing-field-initializers] init.c:10:8: warning: (near initialization for 'xyz2.z') [-Wmissing-field-initializers] $ 

使用GCC 4.7.1编译时,警告是:

 $ /usr/gcc/v4.7.1/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c init.c:10:8: warning: missing initializer [-Wmissing-field-initializers] init.c:10:8: warning: (near initialization for 'xyz2.z') [-Wmissing-field-initializers] $ 

上面的编译器是由我编译的。 Apple提供的编译器名义上是GCC 4.2.1和Clang:

 $ /usr/bin/clang -O3 -g -std=c99 -Wall -Wextra -c init.c init.c:9:23: warning: missing field 'y' initializer [-Wmissing-field-initializers] struct xyz xyz1 = { 0 }; ^ init.c:10:26: warning: missing field 'z' initializer [-Wmissing-field-initializers] struct xyz xyz2 = { 0, 0 }; ^ 2 warnings generated. $ clang --version Apple clang version 4.1 (tags/Apple/clang-421.11.65) (based on LLVM 3.1svn) Target: x86_64-apple-darwin11.4.2 Thread model: posix $ /usr/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c init.c:9: warning: missing initializer init.c:9: warning: (near initialization for 'xyz1.y') init.c:10: warning: missing initializer init.c:10: warning: (near initialization for 'xyz2.z') $ /usr/bin/gcc --version i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00) Copyright (C) 2007 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ 

正如SecurityMatt在下面的评论中所指出的, memset()对于从内存复制结构的优势在于内存中的副本更昂贵,需要访问两个内存位置(源和目标)而不是一个。 相比之下,将值设置为零不必访问内存以获取源,而在现代系统上,内存是瓶颈。 因此,对于简单的初始化器(其中相同的值,通常是所有零字节,都放在目标内存中), memset()编码应该比复制更快。 如果初始化器是一个复杂的值组合(并非所有零字节),那么可以更改余额以支持初始化器,以获得符号紧凑性和可靠性(如果没有别的话)。

没有一个剪切和干燥的答案……可能从来没有,现在没有。 我仍倾向于使用初始化程序,但memset()通常是一种有效的替代方法。

我会说这两者都不正确,因为你永远不应该自己创建sockaddr_类型的对象。 相反,总是使用getaddrinfo (有时候是getsocknamegetpeername )来获取地址。

“struct sockaddr_in foo = {0};” 仅在第一次有效,而“memset(&foo,0,sizeof foo);” 每次运行该function时都会清除它。

两种方法都不应该有问题 – 填充字节的值无关紧要。 我怀疑memset()的使用源于Berkeley-ism bzero()的早期使用,它可能早于引入struct初始化器或更高效。

正如许多人所指出的那样,任何一个都是正确的。 此外,您可以使用calloc分配这些结构, calloc已经返回一个归零的内存块。