При обнулении структуры, такой как sockaddr_in, sockaddr_in6 и addrinfo, перед использованием, что правильно:memset, инициализатор или что-то еще?

StackOverflow https://stackoverflow.com/questions/894300

  •  23-08-2019
  •  | 
  •  

Вопрос

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

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-м изд.(у меня есть оба)).
  • Я прекрасно понимаю, что они не идентичны по причинам, изложенным выше.
Это было полезно?

Решение

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

Другие советы

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top