web-dev-qa-db-de.com

Warum ist das erste Argument von getline ein Zeiger auf den Zeiger "char **" anstelle von "char *"?

Ich verwende die Funktion getline, um eine Zeile aus STDIN zu lesen.

Der Prototyp von getline lautet: 

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

Ich benutze dies als Testprogramm, das von http://www.crasseux.com/books/ctutorial/getline.html#getline stammt.

#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;
}

Das funktioniert gut.

Meine Zweifel sind?

  1. Warum char **lineptr anstelle von char *lineptr als Parameter der Funktion getline verwenden?

  2. Warum ist es falsch, wenn ich den folgenden Code verwende:

    char **my_string;
    bytes_read = getline(my_string, &nbytes, stdin); 
    
  3. Ich bin mit * und & verwechselt.

Hier ist ein Teil der Warnungen:

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

Ich verwende die GCC-Version 4.4.5 (Ubuntu/Linaro 4.4.4-14 - Subuntu5).

22
ct586

Warum char **lineptr anstelle von char *lineptr als Parameter der Funktion getline verwenden?

Stellen Sie sich vor, der Prototyp für getline sah folgendermaßen aus:

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

Und du hast es so genannt:

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

Vor dem Aufruf von getline ist buffer null:

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

Wenn getline aufgerufen wird, erhält line eine Kopie von buffer, da Funktionsargumente über den Wert in C übergeben werden. Innerhalb von getline haben wir keinen Zugriff mehr auf buffer:

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

getline weist etwas Speicher mit malloc zu und zeigt line an den Anfang des Blocks:

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

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

Nachdem getline zurückkehrt, haben wir keinen Zugriff mehr auf line:

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

Und wir sind genau da, wo wir angefangen haben. Wir können buffer nicht erneut auf den neu zugewiesenen Speicher in getline zeigen, da uns nur eine Kopie von buffer zur Verfügung steht.


Der Prototyp für getline lautet eigentlich:

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

Und du nennst es so:

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

&foo gibt einen Zeiger auf foo zurück, also haben wir:

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

Wenn getline aufgerufen wird, erhält lineptr eine Kopie von &buffer, da C call-by-value ist. lineptr zeigt auf dieselbe Stelle wie &buffer:

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

getline weist etwas Speicherplatz mit malloc zu und zeigt den Anfangspunkt von lineptr (d. h., worauf lineptr zeigt) auf den Anfang des Blocks:

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

Nachdem getline zurückkehrt, haben wir keinen Zugriff mehr auf lineptr, aber wir können trotzdem über buffer auf den neu zugewiesenen Speicher zugreifen:

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

Weil getline() den Speicher für Sie reserviert, wenn Sie einen Zeiger auf einen Nullzeiger übergeben.

Auf der man-Seite :

getline () liest eine ganze Zeile aus Stream, speichert die Adresse der Puffer mit dem Text in * lineptr. Der Puffer ist nullterminiert und enthält die Zeilenvorschub, wenn einer gefunden wurde.

Wenn * lineptr NULL ist, dann getline () ordnet einen Puffer zum Speichern von .__ zu. Zeile, die von der .__ freigegeben werden sollte. Anwenderprogramm. (In diesem Fall wird der Wert in * n ignoriert.)

Sie müssen einen char** übergeben (dh einen Zeiger auf einen Zeiger auf ein Zeichen), damit die Funktion den Wert des char* aktualisieren kann, auf den sie zeigt.

Sie könnten verwendet haben:

char *my_string = NULL;  // getline will alloc

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

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

Vergessen Sie nicht, dass Sie in diesem Fall für free()- den von getline() zugewiesenen Speicher verantwortlich sind.

11
John Carter

Daraus ergibt sich die Antwort auf Ihre erste Frage. Überprüfen Sie die Manpage in Zukunft, sie enthält die Informationen, die Sie benötigen.

Ihre zweite Zeile funktioniert nicht, weil der Zeiger nicht initialisiert ist. Wenn Sie das tun möchten, müssen Sie schreiben:

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

Wenn Sie erstellen eine Variable -, bedeutet * im Wesentlichen einen Zeiger, wenn Sie referenzieren eine Variable, bedeutet dies, dass Sie den Zeiger dereferenzieren (erhalten Sie das, worauf der Zeiger zeigt). & bedeutet "Der Zeiger, der darauf zeigt".

6
TartanLlama

Nachdem ich bei meinem neuen Auftritt einen alten Code übernommen habe, sollte ich mich davor hüten, calloc aufzurufen und einen Zeiger-Zeiger zurückzugeben. Es sollte funktionieren, aber es verdeckt, wie getline () funktioniert. Der Operator & macht deutlich, dass Sie die Adresse des Zeigers übergeben, den Sie von malloc (), calloc () erhalten haben. Obwohl technisch identisch, verdeckt die Deklaration von foo als char ** foo anstelle von char * foo und der anschließende Aufruf von getline (foo ,) anstelle von getline (& foo ,) diesen wichtigen Punkt.

  1. mit getline () können Sie Speicher zuweisen und getline () einen Zeiger auf den Zeiger übergeben, den malloc (), calloc () an Sie zurückgibt und den Sie Ihrem Zeiger zuweisen. Z.B:

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

  2. es ist möglich, es & foo = NULL zu übergeben. In diesem Fall wird eine blinde Speicherzuweisung für Sie durchgeführt, indem Sie im Hintergrund malloc (), calloc () aufrufen, die nicht sichtbar sind.

  3. char * foo, ** p_foo = & foo würde auch funktionieren. Rufe dann foo = calloc (size_t, size_t) auf und dann getline (p_foo ,); Ich denke, getline (& foo ,) ist besser.

Blinde Zuordnungen sind sehr schlecht, und eine Einladung zu problematischen Speicherverlusten besteht, da Sie nirgendwo in IHREM Code malloc () oder calloc () aufrufen, sodass Sie oder jemand, der später mit der Pflege Ihres Codes beauftragt wird, nicht wissen, ob Sie ihn freigeben sollen () Der Zeiger auf diesen Speicher, da eine von Ihnen aufgerufene Funktion Speicher zuweist, ohne dass Sie es wissen (außer beim Lesen der Funktionsbeschreibung und Verstehen, dass es sich um eine blinde Zuordnung handelt).

Da getline () den von Ihrem Aufruf an malloc () und calloc () bereitgestellten Speicher neu zuteilt (), sollten Sie bei einem Aufruf an calloc () einfach Ihre bestmögliche Schätzung des erforderlichen Speichers zuweisen und vornehmen klar, was der Zeiger char * foo tut. Ich glaube nicht, dass getline () irgendetwas mit dem Speicher macht, solange calloc () d ausreicht.

Denken Sie daran, dass der Wert Ihres Zeigers möglicherweise geändert wird, wenn getline () realloc () aufrufen muss, um mehr Speicher zuzuweisen, da der neue Speicher wahrscheinlich von einem anderen Speicherort auf dem Heap stammt. IE: Wenn Sie & foo übergeben und die Adresse von foo 12345 lautet und getline () realloc () Ihren Speicherplatz und an einem neuen Speicherort, lautet die neue Adresse von foo möglicherweise 45678.

Dies ist keine Argumentation gegen Ihren eigenen Aufruf von calloc (), denn wenn Sie foo = NULL setzen, müssen Sie garantiert getline () realloc () aufrufen.

Zusammenfassend Rufen Sie calloc () mit einer guten Schätzung der Größe auf, was jedem, der Ihren Code liest, klar macht, dass der Speicher IS BEING ALLOCATED which muss free () sein d, egal was getline () später macht oder nicht macht.

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

Warum verwenden Sie char ** lineptr statt char * lineptr als Parameter der Funktion getline?

char **lineptr wird verwendet, weil getline() nach der Adresse des Zeigers fragt, der auf den Speicherort der Zeichenfolge zeigt.
Sie würden char *lineptr verwenden, wenn getline() den Zeiger selbst erwarten würde (was nicht funktionieren würde, siehe in der Antwort von ThisSuitIsBlackNot). 

Warum ist es falsch, wenn ich den folgenden Code verwende:
char **my_string; bytes_read = getline(my_string, &nbytes, stdin);

Folgendes würde funktionieren: 

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

Ich bin verwechselt mit * und &.

Der * hat eine doppelte Bedeutung.
Wenn es in einer Deklaration eines Zeigers verwendet wird, z. Ein Zeiger auf Char bedeutet, dass Sie einen Zeiger auf Char anstelle von Char wünschen.
Wenn er an anderer Stelle verwendet wird, erhält er die Variable, auf die ein Zeiger zeigt. 

Der & erhält die Adresse einer Variablen im Speicher (welche Zeiger wurden erstellt, um als Wert zu speichern)

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 bedeutet, dass Sie mir die Adresse (&) der Variablen geben, auf die ptr_to_letter zeigt (*), und dieselbe Adresse wie writtingptr_to_letter
Sie können sich * als das Gegenteil von & vorstellen und sich gegenseitig aufheben. 

0
Sfou