Почему первый аргумент getline указывает на указатель "char**" вместо "char*"?
Я использую getline
функция для чтения строки из STDIN
,
Прототип getline
является:
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
Я использую это как тестовую программу, которую можно найти по http://www.crasseux.com/books/ctutorial/getline.html
#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).
5 ответов
Зачем использовать
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+------->+ | | | | |
+-------+ +---+--+ +---+---+---+---+---+
Так как getline()
will allocate the memory for you if you pass in a pointer to a null pointer.
Со страницы руководства:
getline() reads an entire line from stream, storing the address of the buffer containing the text into *lineptr. The buffer is null-terminated and includes the newline character, if one was found.
Если * lineptr равен NULL, тогда getline () выделит буфер для хранения строки, который должен быть освобожден пользовательской программой. (В этом случае значение в *n игнорируется.)
Вам нужно пройти в char**
(ie a pointer to a pointer to a char) so that the function is able to update the value of the char*
на что это указывает.
Вы могли бы использовать:
char *my_string = NULL; // getline will alloc
puts("Please enter a line of text");
bytes_read = getline(&my_string, &nbytes, stdin);
Don't forget that if you do this you're responsible for free()
-ing the memory allocated by getline()
,
Оттуда ответ правильный для вашего первого вопроса. Проверьте man-страницу в будущем, там есть информация, которая вам нужна.
Ваша вторая строка не работает, потому что указатель не инициализирован. Если вы хотите сделать это, вам нужно написать:
char **my_string = malloc(sizeof(char**))
По сути, когда вы создаете переменную, * означает указатель, когда вы ссылаетесь на переменную, это означает разыменование указателя (получите то, на что указывает указатель). & означает "Указатель, который указывает на это".
Приняв какой-то устаревший код на моем новом концерте, я думаю, что должен предостеречь от вызова 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 (), так что вы или кто-то, кому позже поручено поддерживать ваш код, не будут знать о свободе () указатель на это хранилище, потому что некоторая вызванная вами функция выделяет память без вашего ведома (за исключением чтения описания функции и понимания, что она выполняет слепое распределение).
Так как 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 () с хорошим предположением относительно размера, который сделает очевидным для любого читающего ваш код, что память ВЫДЕЛЕНА, что должно быть free () 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);
Зачем использовать 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
Вы можете думать о *
как противоположность &
и что они отменяют друг друга.