簡體   English   中英

在使用之前將結構如sockaddr_in,sockaddr_in6和addrinfo歸零時,這是正確的:memset,初始化器還是其中之一?

[英]When zeroing a struct such as sockaddr_in, sockaddr_in6 and addrinfo before use, which is correct: memset, an initializer or either?

每當我在書籍,手冊頁和網站中查看真實代碼或示例套接字代碼時,我幾乎總會看到類似下面的內容:

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

例如,在將結構addrinfo提示傳遞給getaddrinfo之前,也可以找到相同的結果。

為什么是這樣? 據我所知,不使用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頁(我同時擁有))。
  • 由於上述原因,我很清楚它們並不完全相同。

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

我要說的是,無論是正確的,因為你永遠不應該創建一個類型的對象sockaddr_ 任何自己。 而是始終使用getaddrinfo (或有時是getsocknamegetpeername )來獲取地址。

“struct sockaddr_in foo = {0};” 僅在第一次有效,而“memset(&foo,0,sizeof foo);” 每次運行該功能時都會清除它。

正如許多人所指出的那樣,任何一個都是正確的。 此外,您可以使用calloc分配這些結構, calloc已經返回一個歸零的內存塊。

兩種方法都不應該有問題 - 填充字節的值無關緊要。 我懷疑memset()的使用源於早期使用Berkeley-ism bzero(),這可能早於結構化初始化器的引入或更高效。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM