При обнулении структуры, такой как sockaddr_in, sockaddr_in6 и addrinfo перед использованием, что является правильным: 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 установит все биты в ноль, что не обязательно является правильным представлением битов для установки каждого элемента в 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 (например, стр. 101 во 2-м изд. И стр. 124 в 3-м изд. (У меня есть оба)).
  • Мне хорошо известно, что они не идентичны по причинам, указанным выше.

5 ответов

Решение

Одна проблема с подходом частичных инициализаторов (то есть{ 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 (или иногда getsockname или же getpeername) получить адреса.

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

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

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

Другие вопросы по тегам