При обнулении структуры, такой как sockaddr_in, sockaddr_in6 и addrinfo перед использованием, что является правильным: memset, инициализатор или любой из них? - PullRequest
22 голосов
/ 21 мая 2009

Всякий раз, когда я смотрю на реальный код или пример кода сокета в книгах, справочных страницах и веб-сайтах, я почти всегда вижу что-то вроде:

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 установит все биты в ноль, что не обязательно является правильным представлением битов для установки каждого элемента в 0.
  • memset также устанавливает биты заполнения на ноль.

Является ли какое-либо из этих различий релевантным или требуемым поведением при установке этих структур на ноль и, следовательно, использование инициализатора вместо этого неправильно? Если да, то почему и какой стандарт или другой источник это подтверждает?

Если оба значения верны, почему memset / bzero появляется вместо инициализатора? Это просто вопрос стиля? Если это так, это нормально, я не думаю, что нам нужен субъективный ответ, на котором лучше стиль.

Обычная обычная практика состоит в том, чтобы использовать инициализатор вместо memset именно потому, что все нулевые биты обычно нежелательны, и вместо этого мы хотим правильное представление нуля для типа (ов). Верно ли обратное для этих структур, связанных с сокетами?

В своем исследовании я обнаружил, что POSIX, по-видимому, требует только обнуления sockaddr_in6 (а не sockaddr_in) на http://www.opengroup.org/onlinepubs/000095399/basedefs/netinet/in.h.html, но не упоминает, как его следует обнулять (memset или initializer?). Я понимаю, что BSD-сокеты предшествуют POSIX, и это не единственный стандарт, так что их соображения совместимости для унаследованных систем или современных не-POSIX систем?

Лично я предпочитаю с точки зрения стиля (и, возможно, хорошей практики) использовать инициализатор и полностью избегать memset, но я не хочу, потому что:

  • Другие исходные коды и полуканонические тексты, такие как Сетевое программирование в UNIX , используют bzero (например, стр. 101 во 2-м изд. И стр. 124 в 3-м изд. (У меня есть оба)).
  • Мне хорошо известно, что они не идентичны по причинам, указанным выше.

Ответы [ 5 ]

14 голосов
/ 21 мая 2009

Одна проблема с подходом частичной инициализации (то есть '{ 0 }') заключается в том, что GCC предупредит вас, что инициализатор неполон (если уровень предупреждения достаточно высок; я обычно использую '-Wall' и часто ' -Wextra '). С назначенным подходом инициализатора, это предупреждение не должно быть дано, но C99 все еще не широко используется - хотя эти части довольно широко доступны, за исключением, возможно, в мире Microsoft.

I склонен использовать в пользу подхода:

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() часто является допустимой альтернативой.

3 голосов
/ 07 июня 2011

Я бы сказал, что ни один из них не является правильным, потому что вы никогда не должны создавать объекты типа sockaddr_ что-либо самостоятельно. Вместо этого всегда используйте getaddrinfo (или иногда getsockname или getpeername) для получения адресов.

2 голосов
/ 21 мая 2009

"struct sockaddr_in foo = {0};" допустимо только в первый раз, тогда как "memset (& foo, 0, sizeof foo);" будет очищать его каждый раз при запуске функции.

1 голос
/ 21 мая 2009

Не должно быть проблем с любым подходом - значения байтов заполнения не должны иметь значения. Я подозреваю, что использование memset () происходит от более раннего использования Berkeley-ism bzero (), которое могло предшествовать введению инициализаторов структуры или было более эффективным.

1 голос
/ 21 мая 2009

Либо один из них правильный, как отмечали многие. Кроме того, вы можете выделить эти структуры с помощью calloc , который уже возвращает обнуленный блок памяти.

...