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

Chr*_*ung 22 c sockets 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);
Run Code Online (Sandbox Code Playgroud)

代替:

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);
Run Code Online (Sandbox Code Playgroud)

要么:

struct sockaddr_in foo = { .sin_port = htons(42) }; /* New in C99 */
Run Code Online (Sandbox Code Playgroud)

要么:

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

例如,在将结构addrinfo提示传递给getaddrinfo之前,也可以找到相同的结果.

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

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

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

如果两者都正确,为什么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页(我同时拥有)).
  • 由于上述原因,我很清楚它们并不完全相同.

Jon*_*ler 14

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

倾向于赞成一种方法:

static const struct sockaddr_in zero_sockaddr_in;
Run Code Online (Sandbox Code Playgroud)

其次是:

struct sockaddr_in foo = zero_sockaddr_in;
Run Code Online (Sandbox Code Playgroud)

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


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
Run Code Online (Sandbox Code Playgroud)

使用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’)
$
Run Code Online (Sandbox Code Playgroud)

使用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’)
$
Run Code Online (Sandbox Code Playgroud)

使用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]
$
Run Code Online (Sandbox Code Playgroud)

使用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]
$
Run Code Online (Sandbox Code Playgroud)

上面的编译器是由我编译的.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.

$
Run Code Online (Sandbox Code Playgroud)

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

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