每当我查看书籍,手册页和网站中的真实代码或示例套接字代码时,我几乎总会看到类似的内容:
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);
例如,在将add addrinfo提示传递给getaddrinfo之前,也可以将其设置为零。
这是为什么?据我所知,不使用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,但我不愿意因为:
答案 0 :(得分:14)
部分初始化方法的一个问题(即“{ 0 }
”)是GCC会警告您初始化程序不完整(如果警告级别足够高;我通常使用'-Wall
} '经常'-Wextra
')。使用指定的初始化方法,不应该给出警告,但C99仍然没有被广泛使用 - 尽管这些部分可以广泛使用,但可能在微软的世界中。
我倾向习惯于赞成一种方法:
static const struct sockaddr_in zero_sockaddr_in;
其次是:
struct sockaddr_in foo = zero_sockaddr_in;
在静态常量中省略初始化程序意味着一切都为零 - 但编译器不会干扰(不应该干扰)。赋值使用编译器的固有内存副本,除非编译器严重不足,否则它不会比函数调用慢。
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()
通常是有效的替代方法。
答案 1 :(得分:3)
我会说这两者都不对,因为你永远不应该自己创建sockaddr_
类型的对象。相反,请始终使用getaddrinfo
(或有时getsockname
或getpeername
)来获取地址。
答案 2 :(得分:2)
“struct sockaddr_in foo = {0};”仅在第一次有效,而“memset(& foo,0,sizeof foo);”每次运行该功能时都会清除它。
答案 3 :(得分:1)
任何一个都是正确的,正如许多人所指出的那样。此外,您可以使用calloc分配这些结构,这些结构已经返回一个归零的内存块。
答案 4 :(得分:1)
两种方法都不应该有问题 - 填充字节的值无关紧要。我怀疑memset()的使用源于Berkeley-ism bzero()的早期使用,它可能早于引入struct初始化器或更高效。