Понимание функции getaddrinfo в C

#c #linux #network-programming #getaddrinfo

#c #linux #сетевое программирование #getaddrinfo

Вопрос:

Я новичок в программировании на C и socket, просто вопрос по getaddrinfo функции. Прототипом функции getaddrinfo является:

 int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result);
  

и getaddrinfo возвращает результат, который указывает на связанный список структур addrinfo, каждая из которых указывает на структуру адреса сокета, соответствующую хосту и службе.
введите описание изображения здесь

Ниже приведены мои вопросы:

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

Q2 -последний параметр struct addrinfo **result , почему это указатель на указатель? Почему это не struct addrinfo *result так, а затем getaddrinfo создает sth внутренне и позволяет result ( struct addrinfo * ) указывать на него? кто-то говорит, что это связано с getaddrinfo malloc внутренним вызовом, но я также могу написать такой код

 int main() 
{
   char *ptr_main;
   test(ptr_main);
   free(ptr_main);
}

void test(char * ptr)
{ 
    ptr = malloc(10); 
}
  

таким образом, параметром функции является char *ptr , а не char **ptr .

Комментарии:

1. «это только один уникальный адрес сокета» Неверно. Во-первых, имя хоста может иметь много разных адресов , связанных с ним. «и пусть result(struct addrinfo *) указывает на это» Если ваша функция хочет что-то изменить, она должна принять указатель на это что-то в качестве параметра. result является указателем, поэтому ему нужен указатель на указатель.

Ответ №1:

getaddrinfo() возвращает список адресов, поскольку имя хоста может содержать не только адрес. Подумайте, например, о тех сайтах с высоким трафиком, которым необходимо распределять посетителей по разным IP-адресам.

Поскольку getaddrinfo()

объединяет функциональность, предоставляемую функциями gethostbyname(3) и getservbyname(3), в единый интерфейс, но в отличие от последних функций, getaddrinfo() является реентерабельным и позволяет программам устранять зависимости IPv4 от IPv6

это может вызвать сеанс DNS для разрешения имени хоста. В случае вышеупомянутых сайтов с высоким трафиком одно и то же имя хоста будет соответствовать списку фактических адресов.


Вы также спрашиваете:

struct addrinfo **result почему это указатель на указатель?

В C указатель на что-либо передается в качестве параметра функции, когда она должна его изменить. Так, например, в случае, если вам нужно изменить целое число, вы передаете int * . Этот конкретный вид модификации очень распространен в C, когда вы хотите вернуть что-то через параметр; в нашем предыдущем примере мы можем вернуть дополнительное целое число, обратившись к указателю, переданному в качестве параметра.

Но что, если функция хочет что-то выделить? Это приведет к внутреннему результату в a type * var = malloc() , что означает, что type будет возвращен указатель на. И для того, чтобы вернуть ее в качестве параметра, нам нужно передать type ** параметр.

Понятна ли логика? Учитывая a type , если вы хотите вернуть его в качестве параметра, вы должны определить его как указатель на type .

В заключение, в нашем случае функции getaddrinfo необходимо изменить переменную, тип которой struct addrinfo * , как struct addrinfo ** и тип параметра.

Просто чтобы упомянуть значение этого параметра:

Функция getaddrinfo() выделяет и инициализирует связанный список структур addrinfo, по одной для каждого сетевого адреса, соответствующего узлу и службе, с учетом любых ограничений, налагаемых подсказками, и возвращает указатель на начало списка в res. Элементы в связанном списке связаны полем ai_next .

Как вы можете видеть, у нас действительно есть распределение внутри функции. Таким образом, эту память необходимо будет в конечном итоге освободить:

Функция freeaddrinfo() освобождает память, которая была выделена для динамически выделяемого связанного списка res.


Почему не просто type * параметр?

Ваш пример кода приводит к неопределенному поведению, и когда я его запускаю, это вызывает сбой программы.

Почему?Как я писал выше, в C параметры передаются по значению. Это означает, что в случае func(int c) функции, вызываемой таким образом

 int b = 1234;

funct(b);
  

параметр c , используемый функцией внутри, будет копией b , и любые изменения в нем не будут сохраняться за пределами функции.

То же самое происходит в случае func(char * ptr) (пожалуйста, обратите внимание на огромный интервал, чтобы подчеркнуть, как тип char * и переменная ptr ): любое изменение ptr не сохранится за пределами функции. ** Вы сможете изменить память, на которую она указывает, и эти изменения будут доступны после возврата функции, но переменная, переданная в качестве параметра, будет такой же, какой она была до вызова func() .

И какое значение ptr_main в вашем примере было вызвано до test того, как оно было вызвано? Мы не знаем, поскольку переменная неинициализирована. Итак, поведение не определено.

Если у вас все еще есть сомнения, вот программа, которая демонстрирует, что вновь выделенный адрес, полученный по значению, не может быть доступен извне the function :

 #include <stdlib.h>
#include <stdio.h>

void test(char * ptr)
{ 
    ptr = malloc(10); 

    printf("test:t%pn", ptr);
}

int main() 
{
    char *ptr_main = (char *) 0x7777;

    printf("main-1:t%pn", ptr_main);
    test(ptr_main);
    printf("main-2:t%pn", ptr_main);
}
  

Вывод:

 main-1: 0000000000007777
test:   0000000000A96D60
main-2: 0000000000007777
  

Даже после вызова функции значение ptr_main остается тем же, что и после того, как я его инициализировал ( 0x7777 ) .

Комментарии:

1. Спасибо за ваш ответ, поэтому для вопроса 2 я также могу сделать подобное int test(char * ptr){ ptr = malloc(10); } , поэтому параметр не обязательно должен быть s char **ptr

2. @amjad Хм ... нет. Я, вероятно, был недостаточно ясен, и мне придется перефразировать. Параметры передаются по значению, поэтому ptr не будет использоваться вне вашей функции (вы не сможете ее освободить). Поэтому, если вам нужно вернуть указатель, вам нужен параметр двойного указателя.

3. Я добавил в свой пост некоторый дополнительный код, чтобы удалить его, я все еще могу освободить ресурс, выделенный malloc , это правда, что два указателя будут указывать на один и тот же ресурс, но вызов free() на одном из них может освободить ресурс.

4. @amjad извините за позднее обновление, но я был на работе. Я обновил свой ответ, показывающий, почему ваше предыдущее утверждение неверно. Я не уверен в причине, по которой у вас не произошел сбой (как у меня). Возможно, это происходит автоматически после завершения процесса, или, может быть, ваш компилятор пропустил free, потому что это произошло непосредственно перед завершением процесса (когда ОС все равно освободит память).

5. Большое вам спасибо за ваш ответ, я вижу 🙂