webentwicklung-frage-antwort-db.com.de

Zeiger als Funktionsargumente in C

Wenn ich diesen Code hätte, zum Beispiel:

int num = 5;
int *ptr = #

Was ist der Unterschied zwischen den beiden folgenden Funktionen?

void func(int **foo);
void func(int *foo); 

Wo rufe ich die Funktion auf:

func(&ptr); 

Ich erkenne, dass der erste der beiden einen Zeiger auf einen Zeiger als Parameter nimmt, während der zweite nur einen Zeiger nimmt.

Wenn ich func(&ptr) übergebe, übergebe ich effektiv einen Zeiger. Welchen Unterschied macht es, dass der Zeiger auf einen anderen Zeiger zeigt? 

Ich glaube, das letztere wird eine Unvereinbarkeitswarnung auslösen, aber es scheint, dass die Details keine Rolle spielen, solange Sie wissen, was Sie tun. Es scheint, dass aus Gründen der Lesbarkeit und des Verständnisses die erstere eine bessere Option ist (2-Sterne-Zeiger), aber was ist aus logischer Sicht der Unterschied? 

25
sherrellbc

Eine vernünftige Faustregel lautet, dass Sie nicht genau ändern können, was genau übergeben wird, sodass der Anrufer die Änderung sieht. Das Übergeben von Zeigern ist die Problemumgehung.

Pass By Value: void fcn(int foo)

Bei der Übergabe des Wertes erhalten Sie eine Kopie des Wertes. Wenn Sie den Wert in Ihrer Funktion ändern, sieht der Anrufer den ursprünglichen Wert unabhängig von Ihren Änderungen.

Übergabe per Zeiger an Wert: void fcn(int* foo)

Durch Übergeben des Zeigers erhalten Sie eine Kopie des Zeigers - er zeigt auf denselben Speicherort wie das Original. In diesem Speicherort wird das Original gespeichert. Auf diese Weise können Sie den Wert ändern, auf den verwiesen wird. Sie können den tatsächlichen Zeiger jedoch nicht auf die Daten ändern, da Sie nur eine Kopie des Zeigers erhalten haben.

Zeiger an Zeiger an Wert übergeben: void fcn(int** foo)

Um dieses Problem zu umgehen, übergeben Sie einen Zeiger auf einen Zeiger auf einen Wert. Wie oben können Sie den Wert so ändern, dass der Anrufer die Änderung sieht, da dies derselbe Speicherort ist, den der Anrufercode verwendet. Aus dem gleichen Grund können Sie den Zeiger auf den Wert ändern. Auf diese Weise können Sie beispielsweise Speicher innerhalb der Funktion zuweisen und zurückgeben. &arg2 = calloc(len);. Sie können den Zeiger immer noch nicht in den Zeiger ändern, da Sie eine Kopie davon erhalten.

58
atk

Der Unterschied wird einfach in den Operationen gesagt, mit denen der Prozessor den Code bearbeiten wird. Der Wert selbst ist in beiden Fällen nur eine Adresse, das stimmt. Da die Adresse jedoch dereferenziert wird, ist es wichtig, dass der Prozessor und damit auch der Compiler nach der Dereferenzierung wissen, mit was er umgehen wird.

8
dhein

Wenn ich diesen Code hätte, zum Beispiel:

int num = 5;
int *ptr = #

Was ist der Unterschied zwischen den folgenden zwei Funktionen?

void func(int **foo);
void func(int *foo);

Der erste will einen Zeiger auf einen Zeiger auf einen int, der zweite einen Zeiger, der direkt auf einen int zeigt.

Wo rufe ich die Funktion auf:

func(&ptr);

Da ptr ein Zeiger auf ein int ist, ist &ptr eine Adresse, die mit einem int ** kompatibel ist.

Die Funktion, die einen int * nimmt, wird etwas anderes machen als mit int **. Das Ergebnis der Konversation wird völlig anders sein, was zu undefiniertem Verhalten führt und möglicherweise einen Absturz verursacht.

Wenn ich in func (& ptr) übergebe, übergebe ich effektiv einen Zeiger. Welchen Unterschied macht es, dass der Zeiger auf einen anderen Zeiger zeigt?

               +++++++++++++++++++
adr1 (ptr):    +  adr2           +
               +++++++++++++++++++

               +++++++++++++++++++
adr2 (num):    +  42             +
               +++++++++++++++++++

Bei adr2 haben wir einen int-Wert, 42.

Bei adr1 haben wir die Adresse adr2 mit der Größe eines Zeigers.

&ptr gibt uns adr1, ptr, enthält den Wert von &num, der adr2 ist.

Wenn ich adr1 als int * verwende, wird adr2 als ganze Zahl falsch behandelt, was zu einer (möglicherweise ziemlich großen) Zahl führt.

Wenn ich adr2 als int ** verwende, führt die erste Dereferenzierung zu 42, was als Adresse falsch interpretiert wird und möglicherweise zum Absturz des Programms führt.

Es ist mehr als nur eine Optik, zwischen int * und int ** zu unterscheiden.

Ich glaube, das letztere wird eine Unvereinbarkeitswarnung geben, 

... was eine Bedeutung hat ...

es scheint jedoch, dass die Details keine Rolle spielen, solange Sie wissen, was Sie tun.

Machst du?

Es scheint, dass aus Gründen der Lesbarkeit und des Verständnisses die erstere eine bessere Option ist (2-Sterne-Zeiger), aber was ist aus logischer Sicht der Unterschied?

Es hängt davon ab, was die Funktion mit dem Zeiger macht.

6
glglgl

Es gibt zwei praktische Unterschiede:

  1. Wenn Sie einen Zeiger an einen Zeiger übergeben, kann die Funktion den Inhalt dieses Zeigers auf eine Weise ändern, die der Aufrufer sehen kann. Ein klassisches Beispiel ist das zweite Argument von strtol(). Nach einem Aufruf von strtol() sollte der Inhalt dieses Zeigers auf das erste Zeichen in der Zeichenfolge zeigen, das nicht analysiert wurde, um den long-Wert zu berechnen. Wenn Sie den Zeiger einfach an strtol() übergeben haben, sind alle vorgenommenen Änderungen lokal, und es ist unmöglich, dem Anrufer den Standort mitzuteilen. Durch Übergeben der Adresse dieses Zeigers kann strtol() ihn auf eine Weise ändern, die der Anrufer sehen kann. Es ist so, als würde man die Adresse einer anderen Variablen übergeben.

  2. Grundsätzlich muss der Compiler den Typ kennen, auf den verwiesen wird, um dereferenzieren zu können. Wenn zum Beispiel ein double * dereferenziert wird, interpretiert der Compiler (bei einer Implementierung, in der double 8 Byte verbraucht) die 8 Bytes, die an der Speicherstelle beginnen, als Wert des Double. Bei einer 32-Bit-Implementierung interpretiert der Compiler beim Dereferenzieren eines double ** die 4 Bytes, die an dieser Stelle beginnen, als Adresse eines anderen Double. Beim Dereferenzieren eines Zeigers ist der Typ, auf den der Compiler zeigt, die einzige Information, die der Compiler darüber hat, wie die Daten an dieser Adresse zu interpretieren sind. Daher ist es wichtig, den genauen Typ zu kennen alles nur Zeiger, also was ist der Unterschied "?

2
Paul Griffiths

Im Allgemeinen zeigt der Unterschied an, dass die Funktion dem Zeiger zugewiesen wird und dass diese Zuweisung nicht nur lokal für die Funktion sein sollte. Zum Beispiel (und denken Sie daran, diese Beispiele dienen nur dazu, die Natur von foo zu untersuchen und nicht vollständige Funktionen, mehr als der Code in Ihrem ursprünglichen Post als echter Arbeitscode sein soll):

void func1 (int *foo) {
    foo = malloc (sizeof (int));
}

int a = 5;
func1 (&a);

Ist ähnlich wie

void func2 (int foo) {
    foo = 12;
}

int b = 5;
func2 (b);

In dem Sinne, dass foo in func2 () gleich 12 sein kann, aber wenn func2 () zurückkehrt, bleibt b immer gleich 5. In func1 () zeigt foo auf ein neues int, aber a ist noch a, wenn func1 () zurückkehrt. 

Was wäre, wenn wir den Wert von a oder b ändern wollten? WRT b, ein normales int:

void func3 (int *foo) {
    *foo = 12;
}    

int b = 5;
func2 (&b);

Funktioniert - bemerken wir, dass wir einen Zeiger auf ein int benötigen. So ändern Sie den Wert in a-Zeiger (dh die Adresse des int, auf die er verweist, und nicht nur den Wert im int, auf den er zeigt):

void func4 (int **foo) {
    *foo = malloc (sizeof (int));
}

int *a;
foo (&a);

'a' zeigt jetzt auf den Speicher, den malloc in func4 () zurückgibt. Die Adresse &a ist die Adresse von a, ein Zeiger auf ein int . Ein Int-Zeiger enthält die Adresse eines Int. func4() nimmt die Adresse eines int-Zeigers, damit er die Adresse eines int in diese Adresse einfügen kann, genauso wie func3 () die Adresse eines int nimmt, damit er einen neuen int-Wert eingeben kann. 

So werden die verschiedenen Argumentstile verwendet. 

Es ist schon eine Weile her, seit das gefragt wurde, aber hier ist meine Meinung dazu. Ich versuche jetzt, C zu lernen, und Zeiger sind endlos verwirrend ... Ich nehme mir daher die Zeit, Zeiger auf Zeiger zu klären, zumindest für mich. So denke ich darüber nach.
Ich habe ein Beispiel von hier genommen:

#include <stdlib.h>
#include <string.h>

int allocstr(int len, char **retptr)
{
    char *p = malloc(len + 1);  /* +1 for \0 */
    if(p == NULL)
        return 0;
    *retptr = p;
    return 1;
}

int main()  
{
    char *string = "Hello, world!";
    char *copystr;
    if(allocstr(strlen(string), &copystr))
        strcpy(copystr, string);
    else    fprintf(stderr, "out of memory\n");
    return 0;
}

Ich habe mich gefragt, warum allocstr einen Doppelzeiger benötigt. Wenn es sich um einen Zeiger handelt, bedeutet dies, dass Sie ihn übergeben können und er wird nach der Rückkehr geändert.
Wenn Sie dieses Beispiel ausführen, funktioniert es einwandfrei. Wenn Sie jedoch allocstr so ändern, dass nur * Pointer anstelle von ** Pointer (und copystr statt & copystr in main) angezeigt wird, erhalten Sie Segmentierungsfehler . Warum? Ich habe einige printfs in den Code eingefügt und es funktioniert gut, bis die Zeile mit strcpy. Ich vermute also, dass es keinen Speicher für copystr zugewiesen hat. Warum wieder? 
Gehen wir zurück zu dem, was es bedeutet, mit dem Zeiger vorbeizugehen. Es bedeutet, dass Sie den Speicherort übergeben und Sie können den gewünschten Wert direkt dort eingeben. Sie können den Wert ändern, da Sie auf den Speicherort Ihres Werts zugreifen können.
Wenn Sie einen Zeiger an einen Zeiger übergeben, übergeben Sie den Speicherplatz Ihres Zeigers, also den Speicherort Ihres Speicherplatzes. Und jetzt (Zeiger auf Zeiger) können Sie den Speicherort ändern, da Sie den Wert ändern könnten, wenn Sie nur einen Zeiger verwenden. 
Der Grund, warum der Code funktioniert, ist, dass Sie die Adresse eines Speicherplatzes übergeben. Die Funktion allocstr ändert die Größe dieses Speicherplatzes, damit "Hallo Welt!" und es gibt einen Zeiger auf diesen Speicherplatz zurück. 
Es ist wirklich das Gleiche wie das Übergeben eines Zeigers, aber statt eines Werts haben wir einen Speicherplatz. 

0
adi