On Thursday 31 May 2001 21:47, Sebastian Roth wrote:
Hallo Konrad,
So vom kurz-mal-reinschauen kann ich zumindest sagen: Der Code hat noch ein paar andere Probleme (z.B. eine recht interessante Rekursion).
Rekursion? *ins Buch guck* ahaa, bei mir rufen sich Funktionen selber auf. Gut zu wissen möcht ich dazu sagen. :-) Aber duie Funktionen rufen sich doch gegenseitig auf. also ruft main() physicmain() auf und weiter das main() main() aufruft habe ich net gefunden oder ist eben dieses aufrufen einer Funktion durch eine andere Rekursion?
Es gibt auch indirekte Rekursion: a() ruft b() auf und b() ruft ihrerseits a() auf. Das kann man auch noch komplizierter über x Ebenen und y Verzweigungen machen, aber entscheidend ist, dass irgendwann eine Funktion aufgerufen wird, die schon läuft, was immer die Gefahr birgt, dass es in einer Endlosschleife endet und damit den Speicher aufbraucht.
Ich habe die main.c mal mit hierrein geschrieben. Als Anlage wäre jene ja genau so gross oder?
genau, aber dann könnte man sie einfacher speichern. ;-)
[cut]
/*KR: warum denn config.h? Willst Du das Teil später via autoconf anpassen?*/
#ifdef HAVE_CONFIG_H #include <config.h> #endif
S: Hmm, das liegt daran, das ich das Prog mit KDevelop (1.4 en) erstellt S: habe. Da war das alles mit drin schon am Anfang. Es sah meiner Meinung S: nach nicht schlimm aus, weil ja in der config.h auch die Versionsnummer S: mit drinnesteht.
Hmm, am besten Du benutzt KDevelop nur zum editieren der C-Dateien (falls das geht) und erstellst Dir alles andere selbst. Zu viel Automagie verhindert sehr effektiv das Begreifen.
[cut]
S: Nun ja, es war so. Ich wollte nicht immer wieder in jeder Funktion, in S: der man etwas auswählen kann immer wieder von vorn eine Variable S: definieren, die das repräsentiert, was man eben ausgewählt hat (1,2,3...). S: Die Variable hatte ja nix weiters zu bedeuten, sollte nur den Eingabewert S
: darstellen. Bei den anderen (s.v,t) muss ich dir wohl Recht geben.
Noch ein Grund: je weniger Du global definierst, umso weniger kann Dir durcheinander geraten und umso unwahrscheinlicher ist es, dass Du in zwei Dateien die selbe Variable definierst (wird so ab 10000 Zeilen akut).
Merke: bei jeder globalen Variable nachdenken, ob es wirklich nötig ist.
/*KR: Funktionen, die schon genutzt aber erst später definiert (=implementiert=ausprogrammiert) werden, sollten schonmal vorher deklariert (=bekanntgegeben) werden. Man nennt das Forward-Deklaration. Und genau das tun übrigens auch die meisten Header-Dateien, wie stdio.h*/
S: Hmm, da habe ich in dem Buch gelesen, das man die Funktionen vor die main S: schreiben soll, da brauch man sie auch nicht deklarieren. Da werde ich S: aber nochmal genau gucken
Beides ist möglich. Die einen bevorzugen main() ans Ende zu schreiben, die anderen schreiben main() an den Anfang. Geschmackssache.
int main(int argc, char *argv[]) { /*KR: der viele printf und switch-Code muss wirklich nur 1x existieren, also lagern wir ihn in eine Funktion aus (main2).*/ /*KR: Hauptschleife (sowas haben alle Ereignisorientierten Programme - Du bist in bester Gesellschaft.*/ for(;;)main2();
S: Ja, das verstehe ich. Die zweite main hatte ich deswegen genommen, weil S: der User, wenn er zurück ins Hauptmenü will, nicht erst wieder den S: Begrüßungstext sehen soll. Das hat mich genervt, wusste aber nicht, wie S: man es sonst anders machen könnte.
Einfach: gib den Begrüßungstext _vor_ der Hauptschleife aus und lass ihn in main2() weg.
also eigentlich sollte er wissen, was ich will!! for(;;) main2();
S: Wenn du mich damit meinst. Ja das habe ich gerafft. :-)
Ich meinte den Rechner ;-) C hat da ein Gedanken-Lese-Feature (zumindest, wenn man den richtigen Gedanken hat) ;-)
quod erat demonstrantum!
S: Hat jemand ein Latein-Deutsch(Sächsisch) Wörterbuch. S: Vermutung q.e.d Was zu beweisen war? Aus Mathematik?
Genau. Und da ausserden gilt "de gustibus non est disputantum" (über Geschmack läßt sich (nicht) streiten) hätte ich eigentlich auch "w.z.b.w." schreiben können ;-)
[cut]
/*KR: WICHTIG: bitte bitte lies Dir die Torvalds-Styles durch: /usr/src/linux/Documentation/CodingStyle */
S: *gefunden* *zumlesenvorgemerkt*
Bitte bald. ;-)
[cut]
printf("Bitte eine Zahl auswählen!\n"); /*KR: ohne extra Aufruf kehrt es zu main() zurück das geht in eine Schleife, die wieder main2
aufruft ------ das spart uns eine Menge Stack! Der Aufruf von main2 an dieser Stelle bringt Dich in eine Endlosschleife, die jedesmal Speicher verbraucht, bis Du beendest.*/
S: *insbuchguckundbegriffstacksuch* Ist mit Stack ein Teil des Speichers S: gemeint? Sorry, Beschreibung kenn ich nicht
Stack, def.: [Vorsicht: langsam und mehrfach lesen, am besten ausprobieren]
Stapelspeicher. Der Stack funktioniert mit Werten genauso, wie ein Stapel Papier mit Blättern: Du kannst oben drauflegen und auch oben wieder wegnehmen (weil alles andere durcheinander bringt ist es bei Computern einfach nicht erlaubt). Wenn Du eine Funktion aufrufst werden die Parameter und die Rücksprungadresse (die Position wohin es zurückgeht, wenn Du die Funktion verläßt) auf den Stack gelegt, dann wird die Funktion aufgerufen, sie legt ihrerseits alle lokalen Variablen drauf und arbeitet damit. Wird von dort eine weitere Funktion aufgerufen wird wieder all das draufgelegt (deswegen verschwenden Rekursionen Speicher). Verläßt Du die Funktion werden erst die lokalen Variablen weggeworfen, der Return-Wert (siehe Stichwort return) draufgelegt und zu der Rücksprungadresse gesprungen, ab da übernimmt wieder die übergeordnete Funktion, wirft die Parameter weg und übernimmt den Rückgabewert (oder verwirft ihn, je nach dem, ob er gebraucht wird).
/*Da Rekursion (=Selbstaufruf) immer Ressourcen verbraucht ist eine Iteration (=Schleife) immer vorzuziehen, wenn der Code dadurch nicht übermäßig schwieriger wird. Erst recht, wenn er (wie hier) übersichtlicher wird.*/
S: gut
[cut]
void wegzeitgleich(void) { /*KR: Insider Tipp: Einzel-chars machen sich mit scanf echt blöd. Also: String einlesen (a'ka ganze Zeile) und dann nur den ersten Character nehmen. Noch so ein Tipp: ein Array kann auch wie ein Pointer behandelt werden (und umgekehrt) die Ausdrücke x[0] und *x sind also immer identisch.*/
S: Erklär mir bitte das x[0] nochmal! Mir Zeigern und Vektoren komme ich bis S
: jetzt noch net so recht aus. das *x ein Zeiger auf x ist, verstehe ich.
Ok, definieren wir mal ein Array:
char bsp[10];
bsp ist 10 char's gross. Ganz nebenbei bekommst Du 11 offensichtliche und legal anwendbare Ausdrücke:
bsp -> ein Zeiger auf das erste Element bsp[0] -> das erste Element .... bsp[9] -> das 10. Element
---------- Notiz am Rande: bsp[99] wird nicht vom Compiler angemeckert, führt meistens aber zu sehr seltsamem Verhalten, da es eine Speicherstelle weit ausserhalb von bsp bezeichnet (90 Byte weiter oben). ----------
jetzt einen Pointer: char *ptr;
Wir wissen, dass bsp ein Pointer auf ein char-Array ist, also ist das hier legal:
ptr=bsp;
damit zeigt ptr ebenso wie bsp auf das erste Element des Arrays bsp. Im Gegensatz zu bsp kannst Du ptr aber auch woanders hinzeigen lassen.
Um den Speicherinhalt der Variable zu nutzen, die hinter einem Pointer steht benutzt man *. Also:
*ptr='a';
weist dem Speicher hinter ptr, also dem ersten Element von bsp, ein 'a' zu. Sprich bsp[0]=='a'.
Da ptr und bsp sowieso auf den selben Speicher zeigen können wir sie also auch gleich behandeln:
*bsp ist identisch zu bsp[0], also sind diese Anweisungen:
*bsp='b'; bsp[0]='b';
absolut identisch. Das selbe gilt für ptr:
*ptr='c'; ptr[0]='c';
Das geht noch weiter:
bsp[4]='d'; ptr[4]='d';
tun (bei uns, da ptr==bsp) exakt das selbe.
Zeiger sind letztendlich nichts weiter als Zahlen, die irgendeine Stelle im Speicher bezeichnen, also kann man in C auch damit rechnen.
*(bsp+2) ist identisch zu bsp[2] und bsp+2 ist identisch zu &(bsp[2]) (& ergibt die Adresse der Variable dahinter)
Da man Pointer (im Gegensatz zu Array-Namen) auch verändern kann, kann man ihnen auch das Ergebnis dieser Pointer-Arithmetik zuweisen:
ptr=bsp+2;
Ab jetzt ist *ptr identisch zu bsp[2] ....
Oder man zählt sie schrittweise durch die Elemente durch: ptr++ - läßt ptr exakt ein Element weiter hoch zeigen (bei uns jetzt bsp[3]).
usw.....
Erfahrungsgemäß verbraucht man viele Tage mit Programmabstürzen, bis sich einem diese Logik vollkommen erschließt, also verzweifel nicht gleich ;-)
/*KR: mehr als 100 wird er wohl net tippen, auch wenn diese Schätzung noch riskant ist...*/
S: Wieso sollte er? Jemand, der das Prog richtig nutzen will, wird schon S: nicht so einen Käse machen oder was denkst du?
Normal würde er wohl net. Aber wichtig ist das, wenn Dein Programm irgendwas im System macht (auch wenn nur die Chance besteht, dass es von einem Skript aus aufgerufen wird, dass für einen Nutzer etwas mit root-Rechten macht), weil dann kann man mit gezielten Überläufen den Rücksprungpointer überschreiben und eigenen (bösen) Code ausführen, der wer weis was mit Deinem System macht.
/*KR: wie "böse", wir kommen bei Rücksprung doch sowieso dorthin zurück!*/
S: *an alle* Sorry, wenn der Quelltext tödlich für einige zu lesen ist. Bin S: C-Anfänger
Deswegen die Gänsefüßchen "".... ;-) Ich war auch mal Anfänger.
Also, ich danke dir erstmal ganz toll. Mal sehen, ob alles so funktioniert wie es soll. *1Minute später* Es klappt. Ich werd verrückt. Juhuu! Es war sehr nett von dir, das du Zeit gefunden hast für dieses Problem, jetzt bin ich um einiges schlauer geworden in Sachen C-Programmierung. Noch eine Frage: Welche Kommentare kann man drinne lassen im Quelltext? Wird das Prog zu groß mit Kommentaren?
Kommentare werden nicht einkompiliert. Sie verschwinden schon nach dem ersten von 4 Schritten (Präprozessor, Compiler, Assembler, Linker).
Konrad (der jetzt aber wirklich ins Bett geht)