c - 在使用前将诸如 sockaddr_in、sockaddr_in6 和 addrinfo 之类的结构归零时,哪个是正确的 : memset, 初始化程序还是两者之一?

标签 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);

代替:

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/

相关文章:

c - 如何在异步模式下使用SIGALRM和SIGPOLL?

c++ - 在库类(std::string)上使用 memset 时出现 "undefined behaviors"的原因是什么?

c - 为什么 memset 采用 int 而不是 char?

c - 在管道上使用 select 返回 10038

c - 使用 Glade 和 GTK+ 进行信号处理

c - 为什么我的 float 被截断了?

c++ - 为什么 memset 不分配 1?

C - 为什么#pragma pack(1) 将 6 位结构成员视为 8 位?

连接服务器-客户端

sockets - 在发送电子邮件之前确定 TLS 支持