在使用前将 sockaddr_in、sockaddr_in6 和 addrinfo 等结构清零时,哪个是正确的:memset、初始化程序还是其中之一?

发布于 2024-07-21 11:34:13 字数 1919 浏览 7 评论 0原文

每当我在书籍、手册页和网站中查看实际代码或示例套接字代码时,我几乎总是看到类似以下内容:

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 网络编程 使用 bzero(例如,第 2 版第 101 页和第 3 版第 124 页(我拥有这两个版本))。
  • 我很清楚,由于上述原因,它们并不相同。

Whenever I look at real code or example socket code in books, man pages and websites, I almost always see something like:

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

instead of:

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

or:

struct sockaddr_in foo = { .sin_port = htons(42) }; /* New in C99 */

or:

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

The same can also be found for setting struct addrinfo hints to zero before passing it to getaddrinfo, for example.

Why is this? As far as I understand, the examples that do not use memset are likely to be the equivalent to the one that does, if not better. I realize that there are differences:

  • memset will set all bits to zero, which is not necessarily the correct bit representation for setting each member to 0.
  • memset will also set padding bits to zero.

Are either of these differences relevant or required behavior when setting these structs to zero and therefore using an initializer instead is wrong? If so, why, and which standard or other source verifies this?

If both are correct, why does memset/bzero tend to appear instead of an initializer? Is it just a matter of style? If so, that's fine, I don't think we need a subjective answer on which is better style.

The usual practice is to use an initializer in preference to memset precisely because all bits zero is not usually desired and instead we want the correct representation of zero for the type(s). Is the opposite true for these socket related structs?

In my research I found that POSIX only seems to require sockaddr_in6 (and not sockaddr_in) to be zeroed at http://www.opengroup.org/onlinepubs/000095399/basedefs/netinet/in.h.html but makes no mention of how it should be zeroed (memset or initializer?). I realise BSD sockets predate POSIX and it is not the only standard, so are their compatibility considerations for legacy systems or modern non-POSIX systems?

Personally, I prefer from a style (and perhaps good practice) point of view to use an initializer and avoid memset entirely, but I am reluctant because:

  • Other source code and semi-canonical texts like UNIX Network Programming use bzero (eg. page 101 on 2nd ed. and page 124 in 3rd ed. (I own both)).
  • I am well aware that they are not identical, for reasons stated above.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

可遇━不可求 2024-07-28 11:34:13

部分初始化方法(即 '{ 0 }')的一个问题是 GCC 会警告您初始化程序不完整(如果警告级别足够高;我通常使用 '-Wall',通常是'-Wextra')。 使用指定的初始化程序方法,不应发出该警告,但 C99 仍然没有广泛使用 - 尽管这些部分相当广泛地可用,也许除了 Microsoft 的世界。

倾向于曾经倾向于一种方法:

static const struct sockaddr_in zero_sockaddr_in;

其次是:

struct sockaddr_in foo = zero_sockaddr_in;

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


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() 通常是一个有效的替代方案。

One problem with the partial initializers approach (that is '{ 0 }') is that GCC will warn you that the initializer is incomplete (if the warning level is high enough; I usually use '-Wall' and often '-Wextra'). With the designated initializer approach, that warning should not be given, but C99 is still not widely used - though these parts are fairly widely available, except, perhaps, in the world of Microsoft.

I tend used to favour an approach:

static const struct sockaddr_in zero_sockaddr_in;

Followed by:

struct sockaddr_in foo = zero_sockaddr_in;

The omission of the initializer in the static constant means everything is zero - but the compiler won't witter (shouldn't witter). The assignment uses the compiler's innate memory copy which won't be slower than a function call unless the compiler is seriously deficient.


GCC has changed over time

GCC versions 4.4.2 to 4.6.0 generate different warnings from GCC 4.7.1. Specifically, GCC 4.7.1 recognizes the = { 0 } initializer as a 'special case' and doesn't complain, whereas GCC 4.6.0 etc did complain.

Consider file 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

When compiled with GCC 4.4.2 (on Mac OS X), the warnings are:

$ /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’)
$

When compiled with GCC 4.5.1, the warnings are:

$ /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’)
$

When compiled with GCC 4.6.0, the warnings are:

$ /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]
$

When compiled with GCC 4.7.1, the warnings are:

$ /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]
$

The compilers above were compiled by me. The Apple-provided compilers are nominally GCC 4.2.1 and 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.

$

As noted by SecurityMatt in a comment below, the advantage of memset() over copying a structure from memory is that the copy from memory is more expensive, requiring access to two memory locations (source and destination) instead of just one. By comparison, setting the values to zeroes doesn't have to access the memory for source, and on modern systems, the memory is a bottleneck. So, memset() coding should be faster than copy for simple initializers (where the same value, normally all zero bytes, is being placed in the target memory). If the initializers are a complex mix of values (not all zero bytes), then the balance may be changed in favour of an initializer, for notational compactness and reliability if nothing else.

There isn't a single cut and dried answer...there probably never was, and there isn't now. I still tend to use initializers, but memset() is often a valid alternative.

雪化雨蝶 2024-07-28 11:34:13

我想说这两者都不正确,因为您永远不应该自己创建 sockaddr_anything 类型的对象。 相反,始终使用 getaddrinfo (有时使用 getsocknamegetpeername)来获取地址。

I would say that neither is correct because you should never create objects of type sockaddr_anything yourself. Instead always use getaddrinfo (or sometimes getsockname or getpeername) to obtain addresses.

乱世争霸 2024-07-28 11:34:13

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

"struct sockaddr_in foo = { 0 };" is only valid for the first time, whereas "memset(&foo, 0, sizeof foo);" will clear it each time the function is run.

束缚m 2024-07-28 11:34:13

这两种方法都不应该有问题——填充字节的值应该不重要。 我怀疑 memset() 的使用源于伯克利主义 bzero() 的早期使用,它可能早于结构初始化器的引入,或者效率更高。

There shouldn't be a problem with either approach -- the values of the padding bytes shouldn't matter. I suspect that the use of memset() stems from earlier use of the Berkeley-ism bzero(), which may have predated the introduction of struct initializers or been more efficient.

你如我软肋 2024-07-28 11:34:13

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

Either one is correct as many have pointed out. Additionally you can allocate these structures with calloc which already returns a zeroed memory block.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文