(C) Grundsatzfragen zu Zeiger
*Markus
- sonstiges
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
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
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
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++;
}
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.
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
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
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
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.
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.
»»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")
ach so, operator * liefert eine referenz und kein temporary
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
- die Schleife
while(\*argv)
bricht nur ab, weil der Zeigerargv[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!
Moin.
»» 3. die Schleife
while(\*argv)
bricht nur ab, weil der Zeigerargv[argc]
zufällig (oder aus Compiler-Toleranz) ein Null-Zeiger ist - das ist keinesfalls garantiert, d.h. du hast hier einen potentiellen Überlaufwarum 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
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
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