每当我查看书籍、手册页和网站中的真实代码或示例套接字代码时,我几乎总是看到类似这样的内容:
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 的示例等效,如果不是更好的话。我意识到存在差异:
- memset 会将所有位设置为零,这不一定是将每个成员设置为 0 的正确位表示。
- memset 还将填充位设置为零。
当将这些结构设置为零并因此使用初始化器代替时,这些差异是否相关或需要行为?如果是这样,为什么,哪个标准或其他来源对此进行了验证?
如果两者都正确,为什么 memset/bzero 倾向于出现而不是初始值设定项?这只是风格问题吗?如果是这样,那很好,我认为我们不需要主观回答哪种风格更好。
通常 的做法是优先使用初始化器而不是 memset,因为通常不需要所有位都为零,相反我们希望正确表示类型的零。这些与套接字相关的结构是否相反?
在我的研究中,我发现 POSIX 似乎只需要将 sockaddr_in6(而不是 sockaddr_in)归零为 http://www.opengroup.org/onlinepubs/000095399/basedefs/netinet/in.h.html。但没有提及应该如何将其归零(memset 或初始化程序?)。我意识到 BSD 套接字早于 POSIX 并且它不是唯一的标准,那么它们对遗留系统或现代非 POSIX 系统的兼容性考虑是什么?
就个人而言,从风格(也许是良好实践)的角度来看,我更喜欢使用初始化程序并完全避免使用 memset,但我不愿意,因为:
- 其他源代码和半规范文本,如 UNIX Network Programming使用 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()
通常是一个有效的替代方法。
关于c - 在使用前将诸如 sockaddr_in、sockaddr_in6 和 addrinfo 之类的结构归零时,哪个是正确的 : memset, 初始化程序还是两者之一?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/894300/