web-dev-qa-db-fra.com

Pourquoi le premier argument de getline est-il un pointeur vers le pointeur "char **" au lieu de "char *"?

J'utilise la fonction getline pour lire une ligne de STDIN.

Le prototype de getline est: 

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

J'utilise cela comme un programme de test obtenu depuis 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;
}

Cela fonctionne bien.

Mes doutes sont?

  1. Pourquoi utiliser char **lineptr à la place de char *lineptr en tant que paramètre de la fonction getline?

  2. Pourquoi c'est faux quand j'utilise le code suivant:

    char **my_string;
    bytes_read = getline(my_string, &nbytes, stdin); 
    
  3. Je suis confus avec * et &.

Voici une partie des avertissements:

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 *’

J'utilise GCC version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5).

22
ct586

Pourquoi utiliser char **lineptr au lieu de char *lineptr en tant que paramètre de la fonction getline?

Imaginez que le prototype de getline ressemble à ceci:

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

Et vous l'avez appelé comme ça:

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

Avant d'appeler getline, buffer est null:

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

Lorsque getline est appelé, line obtient une copie de buffer car les arguments de la fonction sont passés par valeur dans C. Dans getline, nous n'avons plus accès à buffer:

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

getline alloue de la mémoire avec malloc et des points line au début du bloc:

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

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

Après le retour de getline, nous n’avons plus accès à line:

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

Et nous sommes de retour là où nous avons commencé. Nous ne pouvons pas re-pointer buffer vers la mémoire récemment allouée à l'intérieur de getline car nous ne disposons que d'une copie de buffer.


Le prototype de getline est en réalité:

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

Et vous l'appelez comme ça:

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

&foo renvoie un pointeur sur foo, nous avons donc:

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

Lorsque getline est appelé, lineptr obtient une copie de &buffer car C est un appel valeur par valeur. lineptr pointe au même endroit que &buffer:

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

getline alloue un peu de mémoire avec malloc et pointe la pointe de lineptr (c’est-à-dire que la chose que lineptr pointe sur) au début du bloc:

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

Après le retour de getline, nous n’avons plus accès à lineptr, mais nous pouvons toujours accéder à la mémoire nouvellement allouée via buffer:

+-------+        +------+        +---+---+---+---+---+
|&buffer+------->+buffer+------->+   |   |   |   |   |
+-------+        +---+--+        +---+---+---+---+---+
22
ThisSuitIsBlackNot

Parce que getline() allouera la mémoire pour vous si vous passez un pointeur à un pointeur nul.

De la page de manuel :

getline () lit une ligne entière à partir de flux, stockant l'adresse du fichier tampon contenant le texte en * lineptr. La mémoire tampon est terminée par zéro et inclut le fichier caractère de nouvelle ligne, si un a été trouvé.

Si * lineptr est NULL, alors getline () allouera un tampon pour stocker le fichier ligne, qui devrait être libérée par le programme utilisateur. (Dans ce cas, la valeur Dans * n est ignorée.)

Vous devez passer un char** (c'est-à-dire un pointeur vers un pointeur vers un caractère) afin que la fonction puisse mettre à jour la valeur du char* à laquelle elle pointe.

Vous auriez pu utiliser:

char *my_string = NULL;  // getline will alloc

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

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

N'oubliez pas que si vous faites cela, vous êtes responsable de free()- dans la mémoire allouée par getline().

11
John Carter

La réponse est donc correcte pour votre première question. Consultez la page de manuel à l’avenir, elle contient les informations dont vous avez besoin.

Votre deuxième ligne ne fonctionne pas car le pointeur n'est pas initialisé. Si vous voulez faire cela, vous devez écrire:

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

Essentiellement, lorsque vous êtes créant une variable, * signifie un pointeur, lorsque vous êtes référençant une variable, cela signifie de déréférencer le pointeur (obtenir ce que le pointeur pointe sur). & signifie "Le pointeur qui pointe vers ceci".

6
TartanLlama

Pourquoi utiliser char ** lineptr à la place de char * lineptr en tant que paramètre de la fonction getline?

char **lineptr est utilisé car getline() demande l'adresse du pointeur qui pointe vers l'endroit où la chaîne sera stockée.
Vous utiliseriez char *lineptr si getline() prévoyait le pointeur lui-même (ce qui ne fonctionnerait pas, voyez pourquoi dans la réponse de ThisSuitIsBlackNot) 

Pourquoi c'est faux quand j'utilise le code suivant:
char **my_string; bytes_read = getline(my_string, &nbytes, stdin);

Ce qui suit fonctionnerait: 

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

Je suis confondu avec * et &.

Le * a une double signification.
Lorsqu'il est utilisé dans une déclaration d'un pointeur, par ex. un pointeur sur char, cela signifie que vous voulez un pointeur sur char au lieu d'un caractère.
Lorsqu'il est utilisé ailleurs, il obtient la variable à laquelle un pointeur pointe. 

Le & obtient l'adresse en mémoire d'une variable (quels pointeurs ont été créés pour contenir une valeur)

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 signifie me donner l'adresse (&) de la variable à laquelle ptr_to_letter pointe (*), et correspond à l'écritureptr_to_letter
Vous pouvez penser que * est le contraire de & et qu’ils s’annulent. 

0
Sfou