*Markus: (C) Grundsatzfragen zu Zeiger

Hallo,

ich habe folgenden Test durchgeführt - ein Programm, dass mir alle Buchstaben aller Kommandozeilenargumente ausgibt (damit wollte ich den Zugriff auf die Werte von Zeigern auf Zeigern testen).

  
#include <stdio.h>  
  
int main(int argc, char *argv[0])  {  
  
int c;  
  
	printf("Alle Zeichen der Argumente einzeln ausgeben...\n");  
	while (argc-- > 0)   {  
		while (*argv)   {  
			  while ( (c = *(*(argv))++ ) != '\0')  {  
			  	printf("%c\n", c);  
			  }  
			  *argv++;  
		}  
	} 	  
  
return 0;  
}  

Das Programm funktioniert. Aufzurufen zB mit: ./ArgumenteKommandozeile test1 test2 test3

Ich schrieb intuitiv "while (*argv)". Aber was genau bedeutet das technisch, bzw warum funktioniert das so. argv ist ein Array von Zeigern, eigentlich die Adresse des ersten Elements des ersten Zeigers, wenn ich mich nicht irre. Wenn keine Kommandozeilenargumente mehr vorhanden sind, zeigt der Zeiger ja theoretisch irgendwohin. Für das Programm wäre das ja eigentlich kein Grund dort zu stoppen, denn vielleicht will ich ja auf irgend eine Speicherstelle "nach dem (Zeiger-)Array" zeigen, aber das Programm weiß trotzdem, dass es dort stoppen muss. Wieso?
Ich glaube, dass ich hier momentan nämlich ganz böse auf der Leitung stehe.

Weiters ist mir eines auch nicht ganz klar. Statt *(*(argv))++ kann ich nicht **argv++ schreiben, da ich sonst ein Segmentation fault bekomme, aber wieso ist das so? Denn schließlich müsste der Compiler ja wissen, dass ich einen Zeiger auf einen Zeiger habe (**) und somit den "tiefsten" Zeiger inkrementieren will, wenn ich **zgr++ schreibe. Aber offensichtlich ist es nicht so, nur warum?

Markus

  1. int main(int argc, char *argv[0])  {

    int main(int argc, char** argv)
    das ist nicht ein zeiger auf char-arrays der länge 0

    Ich schrieb intuitiv "while (*argv)". Aber was genau bedeutet das technisch, bzw warum funktioniert das so.

    argv[argc] ist immer NULL. das ist aber überflüssig, da du zusätzlich über argc iterierst, bzw. ist das überflüssig

    Weiters ist mir eines auch nicht ganz klar. Statt *(*(argv))++ kann ich nicht **argv++ schreiben

    http://www.google.de/search?q=operator+precedence

  2. Hallo,

    was mich noch wundert,

      	  while ( (c = \*(\*(argv))++ ) != '\0')  {  
    

    inkrementiert ja nur eine temporäre variable. das kann eigentlich nicht gehen?

      	  \*argv++;  
    

    müßte eigentlich auch argv++; heißen, bzw. sollte man eine neue variable zum iterieren nehmen, um argv nicht zu verändern

    1. Hallo,

      wieso? argv++ wäre doch nicht erlaubt, denn ein Array ist keine Variable.

      Also nur auf diese, bereits erwähnte Weise, funktioniert mein ursprüngliches Beispiel:

        
        
      	printf("Alle Zeichen der Argumente einzeln ausgeben...\n");  
      	while (argc-- > 0)   {  
      			while ( *argv && (c = *(*(argv))++) != '\0')  {  
      			  		printf("%c\n", c);  
      			}  
      			*argv++;  
      	} 	  
        
      
      
      1. wieso? argv++ wäre doch nicht erlaubt, denn ein Array ist keine Variable.

        eine array ist natürlich auch eine variable. ein pointer. und für einen pointer ist der + und damit auch der ++ operator definiert. es wird immer sizeof(*pointer) draufaddiert.

        Also nur auf diese, bereits erwähnte Weise, funktioniert mein

        und das wundert mich, weil *(*(argv))++ nur ein temporary inkrementiert.

        1. Moin.

          und das wundert mich, weil *(*(argv))++ nur ein temporary inkrementiert.

          Das kamm man auch als *((*argv)++) schreiben, was vielleicht deutlicher macht, was passiert: der Code nutzt *argv als Laufvariable der inneren Schleife - was natürlich möglich ist, allerdings den Nachteil hat, dass am Ende alle Einträge des ursprünglichen Arrays argv[] auf den Wert '\0' am Ende der entsprechenden Strings zeigen...

          Christoph

          1. Nochmal ich.

            Verzichtet man auf die nutzlose äußere Schleife (einzig beim ersten Durchlauf ist *argv kein Null-Zeiger, d.h. in allen weiteren wird der Schleifenkörper komplett ignoriert), lassen sich die Schleifen wie folgt umschreiben:

              
            for(; *argv; ++argv)  
            {  
            	for(; **argv; ++*argv)  
            		printf("%c\n", **argv);  
            }  
            
            

            Das macht die Sache hoffentlich klarer...

            Christoph

            1. Hallo,

              so sieht es zwar am übersichlichsten aus, aber dabei scheint mir eines nicht klar zu sein, denn was bedeutet nun ++argv in der 1. und ++*argv in der 2. Schleife? ++argv zu inkrementieren bedeutet von der Logik her also "zeige auf den nächsten Zeiger in meinem Array?" Für mich wäre es logisch gewesen, würde dort ++*argv inder 1. und ++**argv in der 2. Schleife stehen, aber nur so wie in deinem Beispiel funktioniert es. Ich dachte nämlich, dass ich auf die einzelnen Elemente (die die Kommandozeilenargumente enthalten) in dem Array von Zeigern mit *argv zugreife. Bei der Ausgabe von printf ist es für mich wieder logisch, wnen ich **argv ausgebe, aber warum inkrementiere ich dann nicht auch in der 2. Schleife so: (**argv)++, bzw ++**argv?

              Eines verstehe ich dabei noch weniger. Ich könnte genauso gut folgendes schreiben:

                
              for(; *argv; argv++)  
              {  
                      for(; **argv; (*argv)++)  
                              printf("%c\n", **argv);  
              }  
              
              

              (*argv)++ erhöht doch den WERT, worauf der Zeiger zeigt, d.h. würde an dieser Stelle ein "u" stehen, müsste dieses "u" auf ein "v" inkrementiert werden, da "v" das nächste Zeichen nach "u" ist. Tut es aber nicht. Es funktioniert so richtig. Logisch wäre für mich aber wieder gewesen, wenn ich *(argv++) schriebe, denn das inkrementiert den Zeiger doch auf die nächste Position. Dies funktioniert aber nicht. Wieso?

              Markus

              1. Moin,

                denn was bedeutet nun ++argv in der 1. und ++*argv in der 2. Schleife? ++argv zu inkrementieren bedeutet von der Logik her also "zeige auf den nächsten Zeiger in meinem Array?"

                ja, das ist richtig.

                Für mich wäre es logisch gewesen, würde dort ++*argv inder 1. und ++**argv in der 2. Schleife stehen

                ++*argv würde bedeuten: Nimm das Element, auf das argv zeigt, und erhöhe es. Da *argv seinerseits auf char zeigt, würde dieser Zeiger um 1 erhöht, so dass er auf das nächste Zeichen im String (Kommandozeilenargument) zeigt.
                ++**argv hieße jedoch: Nimm das Element, auf das argv zeigt (das ist selbst wieder ein Zeiger), und erhöhe das Element, auf das dieser Zeiger verweist. Da (**argv) ein char ist, würde also der Wert des ersten Zeichens im String erhöht.

                Ich dachte nämlich, dass ich auf die einzelnen Elemente (die die Kommandozeilenargumente enthalten) in dem Array von Zeigern mit *argv zugreife.

                Das ist auch so.

                Bei der Ausgabe von printf ist es für mich wieder logisch, wnen ich **argv ausgebe

                Für mich nicht - aber du gibst den String auch Zeichen für Zeichen aus (als printf-Format %c), darum ist **argv richtig. Intuitiver finde ich es, mit dem Format %s den gesamten String *argv am Stück auszugeben.

                Eines verstehe ich dabei noch weniger. Ich könnte genauso gut folgendes schreiben:

                for(; *argv; argv++)

                {
                        for(; **argv; (*argv)++)
                                printf("%c\n", **argv);
                }

                  
                Anmerkung zum Stil: Eine for-Schleife ohne Initialisierung ist zwar syntaktisch erlaubt und korrekt, wirkt immer etwas ... hmm, "defekt". Ich würde in einem solchen Fall zur while-Schleife greifen.  
                  
                
                > (\*argv)++ erhöht doch den WERT, worauf der Zeiger zeigt  
                  
                Genau, und der Zeiger zeigt auf einen Zeiger - der WERT ist in diesem Fall also nicht char, sondern char\*, ein Zeiger auf char. Also wird ein Zeiger auf char um ein Zeichen weitergezählt.  
                  
                
                > d.h. würde an dieser Stelle ein "u" stehen, müsste dieses "u" auf ein "v" inkrementiert werden, da "v" das nächste Zeichen nach "u" ist.  
                  
                Nein, das passiert erst, wenn du zweimal dereferenzierst (also \*\*argv oder \*argv[0]).  
                  
                 Ausdruck    Typ  
                 argv       char\*\*     Zeiger auf Zeiger auf ein Zeichen  
                 \*argv      char\*      Zeiger auf ein Zeichen  
                 \*\*argv     char       Zeichen  
                 argv[x]    char\*  
                 \*argv[x]   char  
                  
                Jetzt klarer?  
                  
                So long,  
                 Martin  
                
                -- 
                Zwischen Leber und Milz  
                passt immer noch'n Pils.
                
                1. Hallo,

                  »» (*argv)++ erhöht doch den WERT, worauf der Zeiger zeigt

                  Genau, und der Zeiger zeigt auf einen Zeiger - der WERT ist in diesem Fall also nicht char, sondern char*, ein Zeiger auf char. Also wird ein Zeiger auf char um ein Zeichen weitergezählt.

                  Ich glaube das war meine Verständnisschwierigkeit. Ich war auf dem Gedaken festgefahren, dass ich bei (*pointer) immer nur Werte erhöhe, wodurch ich gar nicht daran dachte, was es bedeutet, wenn char* erhöht wird.

                  Vielen Dank für eure Hilfe. Jetzt ist mir schon vieles klarer.

                  Markus.

              2. »»Bei der Ausgabe von printf ist es für mich wieder logisch, wnen ich **argv ausgebe, aber warum inkrementiere ich dann nicht auch in der 2. Schleife so: (**argv)++, bzw ++**argv?

                weil du dann die einzelnen buchstaben inkrementieren würdest.
                argv++ geht immer von einem argument zum nächsten (++ auf pointer auf pointer (array) auf Buchstaben)
                (*argv)++ geht von buchstabe zu buchstabe (++ auf pointer (array) auf Buchstaben)
                (*(*argv))++ inkrementiert den 1. buchstaben (++ auf Buchstaben)

                Eines verstehe ich dabei noch weniger. Ich könnte genauso gut folgendes schreiben

                pre- und postfix operatoren:
                a++; merkt sich erst den wert, inkrementiert diesen und kiefert den gemerkten
                ++a; inkrementiert erst und liefert dann den inkrementiertn wert

                int a = 7;
                int b = a++; // b ist 7 und a ist 8
                int c = ++b; // c ist 8 und b ist 8

                hier in der schleife ist das eigentlich egal. beim (*argv)++ wird zwar ein nicht benötigtes temporary angelegt, ein guter compiler sollte das aber bei einfachen datentypen wegoptimieren.

                (*argv)++ erhöht doch den WERT, worauf der Zeiger zeigt, d.h. würde an dieser Stelle ein "u" stehen, müsste dieses "u" auf ein "v" inkrementiert werden, da "v" das nächste Zeichen nach "u" ist. Tut es aber nicht. Es funktioniert so richtig. Logisch wäre für mich aber wieder gewesen, wenn ich *(argv++) schriebe, denn das inkrementiert den Zeiger doch auf die nächste Position.

                (*argv)++ inkrementiert den dereferenzierten pointer auf pointer auf buchstaben. also den pointer auf buchstaben.
                *(argv++) inkrementiert den pointer auf pointer auf buchstaben und dereferenzierten ihn dann. also bei ["123", "345", "678"] und argv zeigt auf den anfang, würde erst argv auf "345" gesetzt und dieses zurückgegeben. im ersten fall wurde der inhalt von argv inkrementiert (also "123" wurde zu "23")

          2. ach so, operator * liefert eine referenz und kein temporary

  3. Moin.

    Falls ich mal so direkt sein darf: dein Code ist Käse. Im einzelnen:

    #include <stdio.h>

    int main(int argc, char *argv[0])  { // 1.

    int c;

    printf("Alle Zeichen der Argumente einzeln ausgeben...\n");
    while (argc-- > 0)   { // 2.
    while (*argv)   { // 3.
      while ( (c = ((argv))++ ) != '\0')  {
       printf("%c\n", c);
      }
      *argv++; // 4.
    }
    }

    return 0;
    }

      
    1\. ISO-C verbietet Arrays der Länge 0 - nutze statt dessen die Deklaration `char \* argv[]` oder `char \*\* argv`  
      
    2\. `argv` ist ein Array von Zeigern auf Arrays von Zeichen, d.h. zum Durchlaufen aller Zeichen genügen zwei Schleifen - dein Algorithmus ist 'kaputt'  
      
    3\. die Schleife `while(\*argv)` bricht nur ab, weil der Zeiger `argv[argc]` zufällig (oder aus Compiler-Toleranz) ein Null-Zeiger ist - das ist keinesfalls garantiert, d.h. du hast hier einen potentiellen Überlauf  
      
    4\. die Dereferenz `\*argv++` ist unnötig, da du den Wert an der Stelle `argv` nie verwendest, ein `argv++` genügt  
      
    Übersichtlicher geht das ganze z.B. so:  
    ~~~c
      
    #include <stdio.h>  
      
    int main(int argc, char * argv[])  
    {  
    	printf("Alle Zeichen der Argumente einzeln ausgeben...\n");  
      
    	for(int i = 0; i < argc; ++i)  
    	{  
    		for(char * c = argv[i]; *c; ++c)  
    			printf("%c\n", *c);  
    	}  
      
    	return 0;  
    }  
    
    

    Falls du den GCC verwendest, ist der Code mit der Option std=c99 oder std=gnu99 zu kompilieren.

    Christoph

      1. die Schleife while(\*argv) bricht nur ab, weil der Zeiger argv[argc]

      zufällig (oder aus Compiler-Toleranz) ein Null-Zeiger ist - das ist keinesfalls garantiert, d.h. du hast hier einen potentiellen Überlauf

      warum zufällig? das ist garantiert!

      1. Moin.

        »» 3. die Schleife while(\*argv) bricht nur ab, weil der Zeiger argv[argc]
        zufällig (oder aus Compiler-Toleranz) ein Null-Zeiger ist - das ist keinesfalls garantiert, d.h. du hast hier einen potentiellen Überlauf

        warum zufällig? das ist garantiert!

        Stimmt, laut C99 TC3, 5.1.2.2.1 §2 ist das so vorgeschrieben. War mir nicht klar, da ich zum iterieren über die Argumente immer argc verwendet habe. Man lernt nie aus ;)

        Christoph

    1. Hallo,

      #include <stdio.h>

      int main(int argc, char * argv[])
      {
      printf("Alle Zeichen der Argumente einzeln ausgeben...\n");

      for(int i = 0; i < argc; ++i)
      {
      for(char * c = argv[i]; *c; ++c)
      printf("%c\n", *c);
      }

      return 0;
      }

        
      Ok, so sieht's wirklich übersichtlicher aus. Was kann ich mir unter der Bedingung \*c in der for-Schleife vorstellen? Ist das gleichbedeutend mit "\*c ist wahr"?  
        
      Viele Grüße,  
        
      Markus 
      
      1. Moin.

        Was kann ich mir unter der Bedingung *c in der for-Schleife vorstellen? Ist das gleichbedeutend mit "*c ist wahr"?

        In C kann jeder skalare Typ (arithmetische und Zeiger-Typen) als Wahrheitswert verwendet werden, wobei 0 als 'falsch' und alles andere als 'wahr' aufgefasst wird.

        \*c in 'boolschem Kontext' ist also gleichbedeutend mit \*c != 0 bzw., wenn man den Typ der Variable beachtet, \*c != '\0'.

        Christoph