Почему первый аргумент getline указывает на указатель «char **» вместо «char *»? - PullRequest
25 голосов
/ 21 апреля 2011

Я использую функцию 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;
}

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

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

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

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

    char **my_string;
    bytes_read = getline(my_string, &nbytes, stdin); 
    
  3. Я путаюсь с * и &.

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

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).

Ответы [ 5 ]

26 голосов
/ 19 марта 2016

Зачем использовать 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 равно нулю:

+------+
|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+------->+   |   |   |   |   |
+-------+        +---+--+        +---+---+---+---+---+
12 голосов
/ 21 апреля 2011

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

Со страницы руководства :

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

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

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

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

char *my_string = NULL;  // getline will alloc

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

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

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

6 голосов
/ 21 апреля 2011

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

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

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

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

2 голосов
/ 14 апреля 2014

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

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

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

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

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

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

Поскольку getline () будет перераспределять () память, которую вы вызываете для 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);
1 голос
/ 12 ноября 2016

Зачем использовать 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 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
Вы можете думать о * как о противоположности &, и они отменяют друг друга.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...