Подтвердить что ты не робот

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

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

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-й (у меня есть оба)).
  • Мне хорошо известно, что они не идентичны по причинам, указанным выше.
4b9b3361

Ответ 1

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

I tend используется для поддержки подхода:

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

Ответ 5

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