Почему первый аргумент getline указывает на указатель "char **" вместо "char *"?

Я использую функцию getline для чтения строки из STDIN.

Прототипом getline является:

ssize_t getline(char **lineptr, size_t *n, FILE *stream);

Я использую это как тестовую программу, которая получается из http://www.crasseux.com/books/ctutorial/getline.html#getline

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

int main(int atgc, char *argv[])
{
    int bytes_read = 1;
    int nbytes = 10;
    char *my_string;

    my_string = (char *)malloc(nbytes+1);

    puts("Please enter a line of text");

    bytes_read = getline(&my_string, &nbytes, stdin);

    if (bytes_read == -1)
    {
        puts ("ERROR!");
    }
    else
    {
        puts ("You typed:");
        puts (my_string);
    }

    return 0;
}

Это отлично работает.

Мои сомнения?

  • Зачем использовать char **lineptr вместо char *lineptr как параметр функции getline?

  • Почему это неправильно, когда я использую следующий код:

    char **my_string;
    bytes_read = getline(my_string, &nbytes, stdin); 
    
  • Меня путают с * и &.

Вот часть предупреждений:

testGetline.c: In function ‘main’: 
testGetline.c:34: warning: pointer targets in passing argument 2 of  
  ‘getline’ differ in signedness 
/usr/include/stdio.h:671: 
  note: expected ‘size_t * __restrict__’ but argument is of type ‘int *’  
testGetline.c:40: warning: passing argument 1 of ‘putchar’ makes integer 
  from pointer without a cast 
/usr/include/stdio.h:582: note: expected ‘int’ but argument is of 
  type ‘char *’

Я использую GCC версии 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5).

Ответ 1

Зачем использовать char **lineptr вместо char *lineptr как параметр функции getline?

Представьте, что прототип для getline выглядел так:

ssize_t
getline(char *line, size_t n, FILE *stream);

И вы назвали это следующим образом:

char *buffer = NULL;
size_t len = 0;
ssize_t read = getline(buffer, len, stdin);

Перед вызовом getline, buffer имеет значение null:

+------+
|buffer+-------> NULL
+------+

Когда вызывается getline, line получает копию buffer, потому что аргументы функции передаются по значению в C. Внутри getline у нас больше нет доступа к buffer:

+------+
|buffer+-------> NULL
+------+          ^
                  |
+------+          |
| line +----------+
+------+

getline выделяет некоторую память с помощью malloc и указывает line на начало блока:

+------+
|buffer+-------> NULL
+------+

+------+        +---+---+---+---+---+
| line +------->+   |   |   |   |   |
+------+        +---+---+---+---+---+

После возврата getline у нас больше нет доступа к line:

+------+
|buffer+-------> NULL
+------+

И мы вернулись туда, где мы начали. Мы не можем переопределить buffer в вновь выделенную память внутри getline, потому что у нас есть только копия buffer.


Прототип getline на самом деле:

ssize_t
getline(char **lineptr, size_t *n, FILE *stream);

И вы называете это следующим образом:

char *buffer = NULL;
size_t len = 0;
ssize_t read = getline(&buffer, &len, stdin);

&foo возвращает указатель на foo, поэтому мы имеем:

+-------+        +------+
|&buffer+------->+buffer+-------> NULL
+-------+        +---+--+

Когда вызывается getline, lineptr получает копию &buffer, потому что C - это позывной. lineptr указывает на то же место, что и &buffer:

+-------+        +------+
|&buffer+------->+buffer+-------> NULL
+-------+        +---+--+
                     ^
+-------+            |
|lineptr+------------+
+-------+

getline выделяет некоторую память с помощью malloc и указывает на точку зрения lineptr (т.е. вещь lineptr указывает на) в начале блока:

+-------+        +------+        +---+---+---+---+---+
|&buffer+------->+buffer+------->+   |   |   |   |   |
+-------+        +---+--+        +---+---+---+---+---+
                     ^
+-------+            |
|lineptr+------------+
+-------+

После возврата getline у нас больше нет доступа к lineptr, но мы все равно можем получить доступ к вновь выделенной памяти через buffer:

+-------+        +------+        +---+---+---+---+---+
|&buffer+------->+buffer+------->+   |   |   |   |   |
+-------+        +---+--+        +---+---+---+---+---+

Ответ 2

Потому что getline() будет выделять вам память, если вы передадите указатель на нулевой указатель.

На странице :

getline() читает целую строку из поток, сохраняющий адрес буфер, содержащий текст в * Lineptr. Буфер имеет нулевое завершение и включает в себя символ новой строки, если он был найден.

Если * lineptr равно NULL, то getline() будет выделять буфер для хранения линии, которая должна быть освобождена пользовательской программы. (В этом случае значение в * n игнорируется.)

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

Вы могли бы использовать:

char *my_string = NULL;  // getline will alloc

puts("Please enter a line of text");

bytes_read = getline(&my_string, &nbytes, stdin);

Не забывайте, что если вы это сделаете, вы отвечаете за free() - за память, выделенную getline().

Ответ 3

Ответ на этот вопрос правильный для вашего первого вопроса. Проверьте man-страницу в будущем, у вас есть информация, которая вам нужна.

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

char **my_string = malloc(sizeof(char**))

По существу, когда вы создаете переменную, * означает указатель, когда вы ссылаетесь на переменную, это означает разыменование указателя (получить то, на что указывает указатель). и означает "Указатель, который указывает на это".

Ответ 4

Приобретая какой-то старый код на моем новом концерте, я думаю, что должен предлагать предостережение против вызова calloc и возврата указателя указателя. Он должен работать, но он скрывает, как работает getline(). Оператор и дает понять, что вы передаете адрес указателя, который вы вернули из malloc(), calloc(). В то время как технически идентично, объявляя foo как char ** foo, вместо char * foo, а затем вызывающий getline (foo,) вместо getline (& foo,) заслоняет эту важную точку.

  • getline() позволяет вам распределять память и передавать getline() указатель на указатель, возвращаемый вам malloc(), calloc(), который вы назначаете своему указателю. Например:

    char *foo = calloc(size_t arbitrarily_large, 1);

  • можно передать его & foo = NULL, и в этом случае он сделает слепое выделение хранилища для вас, спокойно вызывая malloc(), calloc(), скрытый от представления.

  • char * foo, ** p_foo = & foo также будет работать. Затем вызовите foo = calloc (size_t, size_t), а затем вызовите getline (p_foo,); Я думаю, что getline (& foo,) лучше.

Отключенные распределения очень плохие, и приглашение на проблемную утечку памяти, потому что нигде в вашем коде вы вызываете malloc(), calloc(), поэтому вам или кому-то, кому позже поручено поддерживать ваш код, не будет знать, чтобы free() указатель на это хранилище, потому что некоторая функция, которую вы вызываете, распределяет память, не зная ее (кроме чтения описания функции и понимания того, что она делает слепое выделение).

Так как getline() будет вызывать realloc() память вашего вызова в malloc(), calloc() при условии, что она слишком мала, лучше всего выделить наилучшее предположение относительно требуемого хранилища с вызовом calloc(), и дайте понять, что делает указатель char * foo. Я не верю, что getline() ничего не делает с хранилищем, если достаточно, чтобы у вас был calloc() d.

Помните, что значение вашего указателя может измениться, если getline() должен вызвать realloc(), чтобы выделить больше хранилища, так как новое хранилище, вероятно, будет из другого места в куче. IE: если вы передаете & foo, а адрес foo - 12345, а getline() realloc() - ваше хранилище, а в новом месте новый адрес foo может быть 45678.

Это не аргумент против вашего собственного вызова calloc(), потому что если вы установите foo = NULL, вы гарантированный, который getline() должен будет вызвать realloc().

В заключение сделайте вызов calloc() с некоторой догадкой относительно размера, что сделает очевидным для всех, кто читает ваш код, что память РАСПРОСТРАНЕНА, которая должна быть бесплатной() d, независимо от того, что getline() делает или не делает позже.

if(NULL == line) {
     // getline() will realloc() if too small
    line = (char *)calloc(512, sizeof(char));
}
getline((char**)&line, (size_t *)&len, (FILE *)stdin);

Ответ 5

Зачем использовать char ** lineptr вместо char * lineptr как параметр функции getline?

char **lineptr используется, потому что getline() запрашивает адрес указателя, указывающий на то, где будет храниться строка.
 Вы бы использовали char *lineptr, если getline() ожидал самого указателя (что бы не сработало, см. Почему в ответе ThisSuitIsBlackNot)

Почему это неправильно, когда я использую следующий код:
char **my_string; bytes_read = getline(my_string, &nbytes, stdin);

Следующие действия будут работать:

char *my_string;
char **pointer_to_my_string = &my_string;
bytes_read = getline(my_string, &nbytes, stdin);

Я путаюсь с * и &.

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

& получает адрес в памяти переменной (какие указатели были созданы для хранения как значения)

char letter = 'c';
char *ptr_to_letter = &letter;
char letter2 = *ptr_to_letter;
char *ptr2 = &*ptr_to_letter; //ptr2 will also point to letter

&*ptr_to_letter означает, что я получаю адрес (&) переменной, в которой ptr_to_letter указывает (*), и тот же, что и написание ptr_to_letter
Вы можете думать о * как о противоположном &, и что они отменяют друг друга.