Soll ich char ** argv oder char * argv [] verwenden?

125

Ich lerne gerade C und habe mich gefragt, welche davon ich in meiner Hauptmethode verwenden soll. Gibt es einen Unterschied? Welches ist häufiger?

Kredns
quelle
2
Ich bevorzuge 'char ** argv' und sehe es daher öfter, aber beide sind korrekt und ich würde eine Deklaration nicht ändern, nur weil sie 'char * argv []' geschrieben wurde.
Jonathan Leffler
+1, weil die tieferen Probleme von Array vs. Zeiger wichtig sind, um gut zu verstehen.
RBerteig
2
Wirklich, wirklich gute Frage. Danke dir.
Frank V
5
Abschnitts 6 des Lese comp.lang.c FAQ ; Es ist die beste Erklärung für die Beziehung zwischen C-Arrays und Zeigern, die ich gesehen habe. Zwei relevante Punkte: 1. char **argventspricht genau char *argv[]einer Parameterdeklaration (und nur einer Parameterdeklaration). 2. Arrays sind keine Zeiger.
Keith Thompson

Antworten:

160

Da Sie gerade C lernen, empfehle ich Ihnen, zuerst wirklich zu versuchen, die Unterschiede zwischen Arrays und Zeigern zu verstehen, anstatt die üblichen Dinge.

Im Bereich der Parameter und Arrays gibt es einige verwirrende Regeln, die klar sein sollten, bevor Sie fortfahren. Zunächst wird das, was Sie in einer Parameterliste deklarieren, als besonders behandelt. Es gibt solche Situationen, in denen Dinge als Funktionsparameter in C keinen Sinn ergeben

  • Funktioniert als Parameter
  • Arrays als Parameter

Arrays als Parameter

Der zweite ist vielleicht nicht sofort klar. Wenn Sie jedoch berücksichtigen, dass die Größe einer Array-Dimension Teil des Typs in C ist (und ein Array, dessen Dimensionsgröße nicht angegeben ist, hat einen unvollständigen Typ), wird dies deutlich. Wenn Sie also eine Funktion erstellen würden, die einen Array-Nachwert annimmt (eine Kopie erhält), könnte dies nur für eine Größe möglich sein! Außerdem können Arrays groß werden, und C versucht, so schnell wie möglich zu sein.

In C sind aus diesen Gründen keine Array-Werte vorhanden. Wenn Sie den Wert eines Arrays abrufen möchten, erhalten Sie stattdessen einen Zeiger auf das erste Element dieses Arrays. Und hier liegt eigentlich schon die Lösung. Anstatt einen ungültigen Array-Parameter im Voraus zu zeichnen, transformiert ein C-Compiler den Typ des jeweiligen Parameters in einen Zeiger. Denken Sie daran, es ist sehr wichtig. Der Parameter ist kein Array, sondern ein Zeiger auf den jeweiligen Elementtyp.

Wenn Sie nun versuchen, ein Array zu übergeben, wird stattdessen ein Zeiger auf das erste Element des Arrays übergeben.

Exkursion: Funktioniert als Parameter

Lassen Sie uns zur Vervollständigung und weil ich denke, dass dies Ihnen helfen wird, die Angelegenheit besser zu verstehen, schauen, wie der Stand der Dinge ist, wenn Sie versuchen, eine Funktion als Parameter zu haben. In der Tat wird es zunächst keinen Sinn ergeben. Wie kann ein Parameter eine Funktion sein? Huh, wir wollen natürlich eine Variable an diesem Ort! In diesem Fall wandelt der Compiler die Funktion erneut in einen Funktionszeiger um . Beim Versuch, eine Funktion zu übergeben, wird stattdessen ein Zeiger auf die jeweilige Funktion übergeben. Das Folgende ist also dasselbe (analog zum Array-Beispiel):

void f(void g(void));
void f(void (*g)(void));

Beachten Sie, dass Klammern *gerforderlich sind. Andernfalls würde eine zurückgegebene Funktion void*anstelle eines Zeigers auf eine zurückgegebene Funktion angegeben void.

Zurück zu den Arrays

Jetzt habe ich am Anfang gesagt, dass Arrays einen unvollständigen Typ haben können - was passiert, wenn Sie noch keine Größe angeben. Da wir bereits festgestellt haben, dass ein Array-Parameter nicht vorhanden ist, sondern ein Array-Parameter ein Zeiger ist, spielt die Größe des Arrays keine Rolle. Das heißt, der Compiler übersetzt alle folgenden Elemente und alle sind dasselbe:

int main(int c, char **argv);
int main(int c, char *argv[]);
int main(int c, char *argv[1]);
int main(int c, char *argv[42]);

Natürlich macht es nicht viel Sinn, eine beliebige Größe hineinstecken zu können, und es wird einfach weggeworfen. Aus diesem Grund hat C99 eine neue Bedeutung für diese Zahlen entwickelt und lässt andere Dinge in Klammern erscheinen:

// says: argv is a non-null pointer pointing to at least 5 char*'s
// allows CPU to pre-load some memory. 
int main(int c, char *argv[static 5]);

// says: argv is a constant pointer pointing to a char*
int main(int c, char *argv[const]);

// says the same as the previous one
int main(int c, char ** const argv);

Die letzten beiden Zeilen besagen, dass Sie "argv" innerhalb der Funktion nicht ändern können - es ist ein konstanter Zeiger geworden. Nur wenige C-Compiler unterstützen diese C99-Funktionen. Diese Funktionen machen jedoch deutlich, dass das "Array" eigentlich keines ist. Es ist ein Zeiger.

Ein Wort der Warnung

Beachten Sie, dass alles, was ich oben gesagt habe, nur wahr ist, wenn Sie ein Array als Parameter einer Funktion haben. Wenn Sie mit lokalen Arrays arbeiten, ist ein Array kein Zeiger. Es verhält sich wie ein Zeiger, da ein Array, wie bereits erläutert, beim Lesen seines Werts in einen Zeiger konvertiert wird. Es sollte aber nicht mit Zeigern verwechselt werden.

Ein klassisches Beispiel ist das Folgende:

char c[10]; 
char **c = &c; // does not work.

typedef char array[10];
array *pc = &c; // *does* work.

// same without typedef. Parens needed, because [...] has 
// higher precedence than '*'. Analogous to the function example above.
char (*array)[10] = &c;
Johannes Schaub - litb
quelle
2
Hatte keine Ahnung, dass es diese gibt: * argv [static 1]
superlukas
12

Sie könnten entweder verwenden. Sie sind völlig gleichwertig. Siehe litbs Kommentare und seine Antwort .

Es hängt wirklich davon ab, wie Sie es verwenden möchten (und Sie können es auf jeden Fall verwenden):

// echo-with-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char **argv)
{
  while (--argc > 0)
  {
    printf("%s ", *++argv);
  }
  printf("\n");
  return 0;
}

// echo-without-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  int i;
  for (i=1; i<argc; i++)
  {
    printf("%s ", argv[i]);
  }
  printf("\n");
  return 0;
}

Was häufiger vorkommt - spielt keine Rolle. Jeder erfahrene C-Programmierer, der Ihren Code liest, sieht beide als austauschbar an (unter den richtigen Bedingungen). Genau wie ein erfahrener englischer Sprecher "sie sind" und "sie sind" gleichermaßen leicht liest.

Wichtiger ist, dass Sie lernen, sie zu lesen und zu erkennen, wie ähnlich sie sind. Sie lesen mehr Code als Sie schreiben, und Sie müssen mit beiden gleichermaßen vertraut sein.

Rampion
quelle
7
char * argv [] entspricht zu 100% char ** argv, wenn es als Parametertyp einer Funktion verwendet wird. kein "const" beteiligt, auch nicht implizit. Beide sind Zeiger auf Zeiger auf Zeichen. Es ist anders in Bezug auf das, was Sie erklären. Der Compiler passt den Typ des Parameters jedoch so an, dass er ein Zeiger auf einen Zeiger ist, obwohl Sie sagten, dass es sich um ein Array handelt. Somit sind die folgenden Dinge alle gleich: void f (char * p [100]); void f (char * p []); void f (char ** p);
Johannes Schaub - litb
4
In C89 (das die meisten Leute verwenden) gibt es auch keine Möglichkeit, die Tatsache zu nutzen, dass Sie es als Array deklariert haben (semantisch spielt es also keine Rolle, ob Sie dort einen Zeiger oder ein Array deklariert haben - beide werden als Zeiger). Ab C99 können Sie es als Array deklarieren. Folgendes sagt: "p ist immer nicht null und zeigt auf eine Region mit mindestens 100 Bytes": void f (char p [static 100]); Beachten Sie, dass p typenmäßig jedoch immer noch ein Zeiger ist.
Johannes Schaub - litb
5
(Insbesondere & p gibt Ihnen ein Zeichen **, aber kein Zeichen ( ) [100], was der Fall wäre, wenn p * ein Array wäre). Ich bin überrascht, dass noch niemand in einer Antwort erwähnt wurde. Ich halte es für sehr wichtig zu verstehen.
Johannes Schaub - litb
Persönlich bevorzuge ich, char**weil es mich daran erinnert, dass es nicht wie ein echtes Array behandelt werden sollte sizeof[arr] / sizeof[*arr].
Raymai97
9

Es macht keinen Unterschied, aber ich benutze es, char *argv[]weil es zeigt, dass es sich um ein Array mit fester Größe von Zeichenfolgen variabler Länge handelt (was normalerweise der Fall ist char *).

Zifre
quelle
4

Es macht keinen wirklichen Unterschied, aber letzteres ist besser lesbar. Was Ihnen gegeben wird, ist ein Array von Zeichenzeigern, wie die zweite Version sagt. Es kann jedoch implizit wie in der ersten Version in einen doppelten Zeichenzeiger konvertiert werden.

jalf
quelle
2

Sie sollten es als deklarieren char *argv[], da es aufgrund all der vielen gleichwertigen Deklarationsmethoden seiner intuitiven Bedeutung am nächsten kommt: eine Reihe von Zeichenfolgen.

Andras
quelle
1

char ** → Zeiger auf Zeichenzeiger und char * argv [] bedeutet Array von Zeichenzeigern. Da wir Zeiger anstelle eines Arrays verwenden können, können beide verwendet werden.

Alphaneo
quelle
0

Ich sehe keinen besonderen Vorteil darin, einen Ansatz anstelle des anderen zu verwenden - verwenden Sie die Konvention, die am ehesten mit dem Rest Ihres Codes übereinstimmt.

Christoffer
quelle
-2

Wenn Sie eine unterschiedliche oder dynamische Anzahl von Zeichenfolgen benötigen, ist es möglicherweise einfacher, mit char ** zu arbeiten. Wenn die Anzahl der Zeichenfolgen jedoch festgelegt ist, wird char * var [] bevorzugt.

Samoz
quelle
-2

Ich weiß, dass dies veraltet ist, aber wenn Sie nur die Programmiersprache C lernen und nichts Wichtiges damit machen, verwenden Sie keine Befehlszeilenoptionen.

Wenn Sie keine Befehlszeilenargumente verwenden, verwenden Sie auch keine. Deklarieren int main() Sie einfach die Hauptfunktion als Wenn Sie

  • Möchten Sie, dass der Benutzer Ihres Programms eine Datei auf Ihr Programm ziehen kann, damit Sie das Ergebnis Ihres Programms damit oder ändern können
  • Möchten Sie Befehlszeilenoptionen (zu handhaben -help, /?oder jede andere Sache , die nach geht program nameprompt in Terminal oder Befehl)

Verwenden Sie, was für Sie sinnvoller ist. Andernfalls verwenden Sie einfach int main() . Wenn Sie am Ende Befehlszeilenoptionen hinzufügen möchten, können Sie diese später problemlos bearbeiten.

Jesse
quelle
Dies beantwortet die Frage nicht.
Lundin