¿Por qué el primer argumento de getline es un puntero al puntero "char**" en lugar de "char*"?
function gcc (5)
Uso la función getline
para leer una línea de STDIN
.
El prototipo de getline
es:
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
Utilizo esto como un programa de prueba que se obtiene de 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;
}
Esto funciona bien
Mis dudas son?
¿Por qué usar
char **lineptr
lugar dechar *lineptr
como un parámetro de la funcióngetline
?Por qué está mal cuando uso el siguiente código:
char **my_string; bytes_read = getline(my_string, &nbytes, stdin);
Estoy confundido con
*
y&
.
Aquí hay una parte de las advertencias:
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 *’
Uso la versión 4.4.5 de GCC (Ubuntu / Linaro 4.4.4-14ubuntu5).
¿Por qué usar
char **lineptr
lugar dechar *lineptr
como parámetro de la funcióngetline
?
Imagina que el prototipo de getline
veía así:
ssize_t
getline(char *line, size_t n, FILE *stream);
Y lo llamaste así:
char *buffer = NULL;
size_t len = 0;
ssize_t read = getline(buffer, len, stdin);
Antes de llamar a getline
, el buffer
es nulo:
+------+
|buffer+-------> NULL
+------+
Cuando se getline
, la line
obtiene una copia del buffer
porque los argumentos de la función se pasan por valor en C. Dentro de getline
, ya no tenemos acceso al buffer
:
+------+
|buffer+-------> NULL
+------+ ^
|
+------+ |
| line +----------+
+------+
getline
asigna algo de memoria con malloc
y apunta al final del bloque:
+------+
|buffer+-------> NULL
+------+
+------+ +---+---+---+---+---+
| line +------->+ | | | | |
+------+ +---+---+---+---+---+
Después de que getline
regrese, ya no tenemos acceso a la line
:
+------+
|buffer+-------> NULL
+------+
Y estamos de vuelta donde comenzamos. No podemos reajustar el buffer
a la memoria recién asignada dentro de getline
porque solo tenemos una copia de buffer
.
El prototipo de getline
es en realidad:
ssize_t
getline(char **lineptr, size_t *n, FILE *stream);
Y lo llamas así:
char *buffer = NULL;
size_t len = 0;
ssize_t read = getline(&buffer, &len, stdin);
&foo
devuelve un puntero a foo
, por lo que tenemos:
+-------+ +------+
|&buffer+------->+buffer+-------> NULL
+-------+ +---+--+
Cuando se llama a getline
, lineptr
obtiene una copia de &buffer
porque C es call-by-value. lineptr
apunta al mismo lugar que &buffer
:
+-------+ +------+
|&buffer+------->+buffer+-------> NULL
+-------+ +---+--+
^
+-------+ |
|lineptr+------------+
+-------+
getline
asigna algo de memoria con malloc
y apunta el punto de lineptr
(es decir, los puntos a los que apunta la línea) al comienzo del bloque:
+-------+ +------+ +---+---+---+---+---+
|&buffer+------->+buffer+------->+ | | | | |
+-------+ +---+--+ +---+---+---+---+---+
^
+-------+ |
|lineptr+------------+
+-------+
Después de que getline
regrese, ya no tenemos acceso a lineptr
, pero aún podemos acceder a la memoria recientemente asignada a través del buffer
:
+-------+ +------+ +---+---+---+---+---+
|&buffer+------->+buffer+------->+ | | | | |
+-------+ +---+--+ +---+---+---+---+---+
¿Por qué usar char ** lineptr en lugar de char * lineptr como un parámetro de la función getline?
char **lineptr
se usa porque getline()
solicita la dirección del puntero que apunta a dónde se almacenará la cadena.
Utilizaría char *lineptr
si getline()
esperaba el puntero en sí (que no funcionaría, vea por qué en la respuesta de ThisSuitIsBlackNot)
Por qué está mal cuando uso el siguiente código:
char **my_string; bytes_read = getline(my_string, &nbytes, stdin);
Lo siguiente funcionaría:
char *my_string;
char **pointer_to_my_string = &my_string;
bytes_read = getline(my_string, &nbytes, stdin);
Estoy confundido con * y &.
El *
tiene un doble significado.
Cuando se utiliza en una declaración de un puntero, por ejemplo, un puntero a char, significa que quiere un puntero a char en lugar de a char.
Cuando se usa en otro lugar, obtiene la variable a la que apunta un puntero.
El &
obtiene la dirección en memoria de una variable (qué punteros se crearon para contener como valor)
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
significa darme la dirección ( &
) de la variable en la que ptr_to_letter
apunta ( *
), y es lo mismo que ptr_to_letter
Puedes pensar en *
como el opuesto de &
, y que se cancelan uno al otro.
De ahí que la respuesta sea correcta para su primera pregunta. Verifique la página de manual en el futuro, tiene la información que necesita.
Su segunda línea no funciona porque el puntero no está inicializado. Si quieres hacer eso, necesitarías escribir:
char **my_string = malloc(sizeof(char**))
Esencialmente, cuando se crea una variable, * significa un puntero, cuando se hace referencia a una variable, significa desreferenciar el puntero (obtener lo que señala el puntero). & significa "El puntero que apunta a esto".
Habiendo asumido el control de algún código heredado en mi nuevo concierto, creo que debería ofrecer una advertencia contra llamar a calloc y devolver un puntero-puntero. Debería funcionar, pero oscurece cómo opera getline (). El operador & deja en claro que está pasando la dirección del puntero que obtuvo de malloc (), calloc (). Aunque técnicamente idéntico, declarar foo como char ** foo, en lugar de char * foo, y luego llamar a getline (foo ,,) en lugar de getline (& foo ,,) oscurece este importante punto.
getline () le permite asignar almacenamiento y pasar getline () un puntero al puntero que le devuelve malloc (), calloc (), que usted asigna a su puntero. P.ej:
char *foo = calloc(size_t arbitrarily_large, 1);
es posible pasarlo & foo = NULL, en cuyo caso hará una asignación de almacenamiento a ciegas llamando calladamente a malloc (), calloc (), oculto a la vista.
char * foo, ** p_foo = & foo también funcionaría. Luego llame a foo = calloc (size_t, size_t), y luego llame a getline (p_foo ,,); Creo que getline (y foo ,,) es mejor.
Las asignaciones de ciegas son muy malas, y una invitación a la memoria problemática se filtra, porque en ninguna parte de SU código está llamando malloc (), calloc (), por lo que usted o alguien que luego se encargará de mantener su código no sabrá liberar () el puntero a ese almacenamiento, porque alguna función a la que llamas asigna memoria sin que lo sepas (excepto para leer la descripción de la función y entender que está haciendo una asignación ciega).
Como getline () reasignará () la memoria que tu llamada a malloc (), calloc () proporcionó si es demasiado pequeña, lo mejor es asignar tu mejor estimación al almacenamiento requerido con una llamada a calloc () y hacerlo aclare lo que está haciendo el puntero char * foo. No creo que getline () haga nada con el almacenamiento, siempre y cuando tengas calloc () d es suficiente.
Tenga en cuenta que el valor de su puntero puede cambiarse si getline () tiene que llamar a realloc () para asignar más almacenamiento, ya que el nuevo almacenamiento probablemente SERÁ de una ubicación diferente en el montón. IE: si pasa & foo, y la dirección de foo es 12345, y getline () realloc () s su almacenamiento, y en una nueva ubicación, la nueva dirección de foo podría ser 45678.
Esto no es un argumento en contra de hacer su propia llamada a calloc (), porque si establece foo = NULL, se le garantiza que getline () tendrá que llamar a realloc ().
En resumen, haga una llamada a calloc () con algunas buenas suposiciones en cuanto al tamaño, lo que hará obvio para cualquiera que lea su código que la memoria ESTÁ ASIGNADA, que debe ser libre () d, sin importar lo que getline () haga o no haga no hacer más tarde.
if(NULL == line) {
// getline() will realloc() if too small
line = (char *)calloc(512, sizeof(char));
}
getline((char**)&line, (size_t *)&len, (FILE *)stdin);
Porque getline()
asignará la memoria por usted si pasa un puntero a un puntero nulo.
Desde la página man :
getline () lee una línea completa de la ruta, almacenando la dirección del búfer que contiene el texto en * lineptr. El búfer tiene terminación nula e incluye el carácter de nueva línea, si se encontró uno.
Si * lineptr es NULL, getline () asignará un buffer para almacenar la línea, que debe ser liberada por el programa de usuario. (En este caso, el valor en * n se ignora).
Necesita pasar un char**
(es decir, un puntero a un puntero a un carácter) para que la función pueda actualizar el valor del char*
que apunta.
Podrías haber usado:
char *my_string = NULL; // getline will alloc
puts("Please enter a line of text");
bytes_read = getline(&my_string, &nbytes, stdin);
No olvide que si hace esto, usted es responsable de free()
la memoria asignada por getline()
.