22

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

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 似乎只需要在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 页。(我拥有两者))。
  • 由于上述原因,我很清楚它们并不相同。
4

5 回答 5

15

部分初始化方法(即 ' { 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()通常是一个有效的替代方案。

于 2009-05-21T18:22:34.097 回答
3

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

于 2009-05-21T18:22:59.853 回答
3

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

于 2011-06-07T02:52:30.207 回答
1

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

于 2009-05-21T18:22:32.833 回答
1

任何一种方法都不应该有问题——填充字节的值应该无关紧要。我怀疑 memset() 的使用源于伯克利主义 bzero() 的早期使用,它可能早于结构初始化程序的引入或更有效。

于 2009-05-21T18:31:17.683 回答