Прежде всего, я понимаю, что делает код (см. в конце поста) в целом, и мне не нужны общие объяснения.
Что я не понимаю, так это конкретную строку кода: memset(&hints, 0, sizeof(struct addrinfo));
Что я понял до сих пор, так это то, что memset() - это функция для заполнения строки, на которую она указывает.
Он имеет три параметра: первый — указатель на строку, второй — устанавливаемое значение, а третий — количество байтов, задаваемых значением.
В этом случае значением, которое нужно заполнить, является &hints, которое будет адресом переменной hints. Значение, которое должно быть установлено, равно 0, поэтому оно заполняется нулями. И последнее заполняется до размера структуры addrinfo.
Так что в этом случае memset() генерирует для переменной подсказки нули к размеру структуры. Я правильно понял?
Если да, то зачем это нужно в моем примере?
#include <stdlib.h>/* EXIT_SUCCESS */
#include <stdio.h>/* printf */
#include <string.h>/* memset() */
#include <errno.h>/* int errno */
#include <sys/types.h>/* socket defines */
#include <sys/socket.h>/* socket() */
#include <netdb.h>/* getaddrinfo() */
#define ECHO_PORT "7"
int main (int argc, char* argv[]){
if (argc != 2) {
printf ("Usage: %s HOSTNAME\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Resolve host addresses: */
struct addrinfo hints;
struct addrinfo* result, *rp;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;/* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;/* Stream socket */
hints.ai_flags = 0;
hints.ai_protocol = 0;/* Any protocol */
int err = getaddrinfo(argv[1], ECHO_PORT, &hints, &result);
/* Handle potential error: */
if (err) {
printf("Error: getaddrinfo: %s\n", gai_strerror(err));
exit(EXIT_FAILURE);
}
/* Print names found: */
printf("Addresses for %s:\n", argv[1]);
for (rp = result; rp != NULL; rp = rp->ai_next) {
int af = rp->ai_family;
char* address = NULL;
int ok;
if (AF_INET == rp->ai_family) {
uint8_t in_addr =((struct sockaddr_in*)rp->ai_addr)->sin_addr.s_addr;
address = malloc(INET_ADDRSTRLEN);
ok = inet_ntop(af, &in_addr, address, INET_ADDRSTRLEN);
}
if (AF_INET6 == rp->ai_family) {
char* in6_addr =((struct sockaddr_in6*)rp->ai_addr)->sin6_addr.s6_addr;
address = malloc(INET6_ADDRSTRLEN);
ok = inet_ntop(af, in6_addr, address, INET6_ADDRSTRLEN);
}
if (ok) {
printf("%s\n", address);
}
else {
perror("inet_ntop");
}
free(address);
}
freeaddrinfo(result);
return EXIT_SUCCESS;
}
Пожалуйста, не отмечайте одновременно C и C++, за исключением вопросов, связанных с взаимодействием или различиями между двумя языками. Ответы на вопросы могут быть разными для C и C++, и это может сбивать с толку. Чтобы задать один и тот же вопрос для C и для C++, введите два отдельных вопроса. Поскольку ваш код выглядит как код C, я удаляю тег C++. Если вы используете C++, а не C, вы можете изменить тег.
@EricPostpischil Я согласен - и мне пришлось изменить свой ответ только на С++ :-)
@underscore_d спасибо за справочную информацию. Я не знал этого.
@EricPostpischil спасибо за подсказку. Я учту это в следующих постах
@TedLyngmo извини, что доставил тебе неприятности. Спасибо за объяснение :)
@лобстер :-) Ты совсем не доставил мне хлопот. Рады помочь и добро пожаловать!





Да, вы правильно поняли.
Это необходимо в приведенном ниже коде, потому что в
struct addrinfo hints;
hints остается неинициализированным, и программист хотел убедиться, что все поля обнулены.
Более простым решением было бы инициализировать его напрямую:
addrinfo hints{}; // C++11 and later
struct addrinfo hints = {0}; /* C and C++ */
и пропустить memset.
Другой вариант — инициализировать его правильными значениями, используя назначенные инициализаторы (C99 и C++20). В C вы можете указать поля не по порядку, но не в C++, поэтому этот порядок будет работать в обоих случаях:
struct addrinfo hints = { /* "struct" not needed in C++ */
.ai_flags = 0,
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
/* .ai_protocol and the rest will be zeroed */
};
Для более старых стандартов без назначенных инициализаторов:
struct addrinfo hints = {
0,
AF_UNSPEC,
SOCK_STREAM
};
Для версии C инициализатора требуется хотя бы одно значение. { 0 } было бы идиоматично. Я не уверен, что С++ принимает это.
@JohnBollinger C++ тоже принимает это, так что я добавлю это, спасибо.
Обратите внимание, что {} будет работать только в C++ 11 и более поздних версиях, а {0} будет работать во всех версиях C++.
@RemyLebeau О, да, лучше запишите и это, спасибо!
Вы правы, что memset используется здесь, чтобы обнулить все байты hints.
Это делается для того, чтобы любое поле, которое позже явно не установлено, имело значение 0. Поскольку hints не инициализировано, его поля имеют неопределенные значения, поэтому при этом все поля устанавливаются в 0. Если вы посмотрите на определение struct addrinfo:
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
Вы можете видеть, что программа явно устанавливает только первые 4 поля. Вызов memset сначала позаботится обо всем остальном.
Да, ваше понимание в основном правильное. Код просто заполняет всю переменную hints байтами 0x00, прежде чем передать ее в getaddrinfo(). Это необходимо для инициализации hints в состояние по умолчанию, что важно, поскольку addrinfo содержит флаги и указатели памяти для управления поведением getaddrinfo(). Таким образом, вы не можете просто оставить hints в неинициализированном состоянии, он будет содержать случайный мусор, который вызовет неопределенное поведение, запутает getaddrinfo() и/или даже приведет к повреждению памяти, сбоям и т. д.
Использование memset() — это быстрый способ инициализировать все поля hints нулями за одну быструю операцию вместо инициализации каждого поля по отдельности. Таким образом, вы можете сосредоточиться на присвоении значений только тем полям, которые вас действительно интересуют.
Вместо этого более простой способ инициализировать hints выглядит следующим образом:
struct addrinfo hints = {0};
Это инициализирует значение первого поля (ai_flags) равным 0, а остальные поля инициализируют их значениями по умолчанию, которые в данном случае также являются нулями.
И, конечно же, «старые компиляторы» включают не только те, которые реализуют старые версии C++, но и все компиляторы C, которые не предоставляют расширения для синтаксиса инициализатора.
Теперь вопрос помечен как C. = {} не является предпочтительным способом инициализации ни на одном языке.
Знаете ли вы, что неинициализированные переменные по умолчанию имеют неопределенное значение? Смысл memset здесь в том, чтобы гарантировать, что объект этого не сделает, и, следовательно, любые поля, которые могут не быть установлены, имеют нулевые значения - вместо мусора, который будет неопределенным поведением для чтения. Если эти 4 присваивания покрывают все поля в структуре, то технически memset является избыточным, но, вероятно, это хорошая практика, так как позже он может получить больше полей, а затем что-то может взорваться, если сначала структура не была обнулена, а кто-то позже попытался прочитать неинициализированный элемент. Кстати, у С++ есть лучший способ сделать это