On Thu, May 31, 2001 at 09:47:34PM +0200, 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?
Verallgemeinere das doch mal. nehmen wir an, wir haben n Funktionen... :) Im Ernst, stell dir vor, du hast z.B. 3 Funktionen a(), b() und c(). a ruft b auf, b ruft c auf und c wieder a. Damit sprengst du nach kurzer Zeit jeden Speicher. So etwas passiert jetzt dauernd in deinem Programm.
Ich habe die main.c mal mit hierrein geschrieben. Als Anlage wäre jene ja genau so gross oder?
Abgesehen von ein paar Headerbits, ja.
[schnipp]
/*KR: Hinweis 1: Fragen weisen nicht immer auf Fehler hin, sondern oft auch auf Stellen, über die man einfach nachdenken sollte.*/
S: Ok, das kapiere ich
/*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.
Iiiihhhh, eine IDE :). Versuch's mal auf die ganz grausame Art mit Editor und Kommandozeile. Ist am Anfag zwar abschreckender, aber wenn du das kannst, weißt du wenigstens, was deine IDE macht und bist nicht so ein <F9>-Drücker :).
#include <stdio.h> #include <stdlib.h>
/*KR: warum static?*/ //static int eingabe; /*KR: ^^^^^^ das macht sich lokal besser als global! Grund: die Funktionen können sich dann nicht gegenseitig die Variablen verhunzen.*/ //static float v,s,t; /*KR: das gilt auch für diese. Noch ein Grund: wenn sie lokal sind wird der Speicher nur solange belegt, wie nötig. Das mag bei Größenordnungen von 16Byte (wie hier) noch lächerlich erscheinen, aber bei einigen hundert Funktionen mit ihrerseits dutzenden von Variablen, die teilweise im kilobyte-Bereich liegen fällt das schon ins Gewicht.*/
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.
Da das ein hingekleckstes Trivialprogramm ist, mag das mit den globalen Variablen noch gehen, aber gwöhn dir das für halbwegs ernsthaftes Programmieren unbedingt ab. Es kommen sonst noch andere probleme ala Überschatten von variablen dazu.
/*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
Sobald du Headerfiles nutzt, wird das allerdings sowieso überflüssig.
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.
Dafür nimmst du eine Variable, die nur beim ertsen Durchlauf der Schleife einen bestimmten Wert hat.
int myflag = 1; for (...) { ... if (myflag) zeigeBegruessungstext(); myflag=0; ... }
[schnipp Endlosschleifen]
S: Wenn du mich damit meinst. Ja das habe ich gerafft. :-)
quod erat demonstrantum!
S: Hat jemand ein Latein-Deutsch(Sächsisch) Wörterbuch. S: Vermutung q.e.d Was zu beweisen war? Aus Mathematik?
richtig.
*/
}/*Ende der Hauptfunktion*/
void main2(void) { int eingabe;/*KR: was sach' ich? Ist besser! ;-) */
/*KR: netter Sprachmix. Entscheide Dich bitte für eine Sprache. ;-) */
S: Ja da hast du schon wieder Recht. Also werde ich mich für tatatataa S: Deutsch entscheiden :-)
Du darfst aber keine Umlaute benutzen, also nimm lieber Englisch :). Das hat den Vorteil, daß du später keine Probleme beim Umstellen bekommst, wenn du mal mit nicht-Deutschen programmierst (geht ziemlich schnell :))
[schnipp]
S: *insbuchguckundbegriffstacksuch* Ist mit Stack ein Teil des Speichers S: gemeint? Sorry, Beschreibung kenn ich nicht
Stack ist ein LIFO (Last In First Out) Speicher. Der Prozessor kann Werte mittels push und pop auf den Stack legen/vom Stack einlesen. Wenn du eine Funktion aufrufst, wird immer ein bißchen Stack verbraucht, nämlich für Parameter, Rücksprungaddresse, lokale Variablen etc. Ein sinnloser Funktionsaufruf verbraucht also immer Speicher. Mit gedankenlos eingesetzten rekursiven Aufrufen kannst du auch ganz hinterhältig dein System abschießen. Es wird immer mehr Stack belegt, bis einfach kein Speicher mehr da ist. Pech für die anderen Proggies.
[schnipp]
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.
Also (irgendwann hatte ich das schonmal jemandem erklärt), wenn du nicht einen einzelnen char in deinen Speicher packst, sondern einen Array, so liegen die einzelnen Elemente linear hintereinander im Speicher. Wenn also z.B. vector ein Array von float ist, so liegen die einzelnen Elemente (d.h. die einzelnen floats) hintereinander im Speicher. Auf sie kann man dann mit dem []-Operator zugreifen. Liegt also z.B. vector[0] auf der Adresse 5, so ist vector[3] auf Addresse 8 zu finden. Eine Besonderheit von C ist nun, daß der Arrayname immer ein Zeiger auf den Datentyp darstellt. Wieder das Vectorbeispiel: Nach einer Deklaration, z.B. "float vector[10]", ist "vector" (ohne eine eckige Klammer) immer der konstante Zeiger auf das erste Element des Arrays, d.h. die folgenden Codezeilen sind äquivalent:
vector[0]=5.7; *vector=5.7;
vector[1]=1.7; *(vector+1)=1.7; //ungetestet, aber es müßte klappen
Laß von Speicherarithmetik aber lieber erst mal die Finger, bis du damit vertraut bist.
/*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?
Jetzt mal was längeres zur Erklärung. Um auf den Stack zurückzukommen, ist bei i386-kompatiblem CPUs der Stack immer so ausgelegt, daß er VON OBEN NACH UNTEN gefüllt wird, d.h. wenn ich ein "push ax; push bx; push cx" (ax, bx, cx sind Register, für den Anfang also beliebige Werte), so sieht der Stack so aus (links sind niedrigere Speicherbereiche)
cx bx ax
Wenn eine Funktion aufgebaut wird, wird ein sogenannter stack frame aufgebaut, d.h. der Stack wird so gefüllt, daß die Funktion ein abgeschlossenes Stückchen Stack bekommt, aus dem sie nie herausmuß, d.h. angenommen a() ruft b() auf, sieht der Stack in etwa aus:
stack frame von b() | stack frame von a()
Alles was b jemals braucht, befindet sich in seinem Stackbereich. Ein einzelner stack frame sieht jetzt rudimentär etwa so aus
lokale Variablen| Sonstiges | Rücksprungadresse
Irgendwo in deinen lokalen Variablen steht jetzt der Puffer gvar. Was macht jetzt scanf? Es schreibt solange in gvar, bis ein newline kommt. Läuft wunderbar, solange der Benutzer bei <100 Zeichen bleibt. Was passiert aber jetzt, wenn der Benutzer mehr als 100 Zeichen eingibt? Dann wird der gesamte Stackbereich hinter gvar überschrieben (für den Prozessor ist dein Array lediglich ein großer Byteblock, also ist ihm das egal wenn er groß den Stack übermalt). Irgendwo steht dann die Rücksprungadresse. Jetzt stell dir einen ganz bösen Benutzer vor. Der nimmt dein programm im Debugger auseinander, stellt fest, wo die Rücksprungadresse liegt, überschreibt dann bei der Eingabe den stack frame so, daß die Funktion auf jeden Fall zurückkehrt, schreibt hinter die Rücksprungaddresse ein bißchen eigenen Code und überpinselt die Rücksprungaddresse, so daß sie auf seinen eigenen Code im Stack zeigt. Das Ganze nennt man dann buffer overflow oder auch stack smashing. Und so werden dann Server gehackt :). Solange dein Programm keine besonderen Privilegien hat, ist das nur halb so wild, aber wenn es sich um ein root-Proggie handelt...
[schnipp]
Noch eine Frage: Welche Kommentare kann man drinne lassen im Quelltext? Wird das Prog zu groß mit Kommentaren?
Vorm Compiler läuft noch der Präcompiler (gpp). Seine Aufgabe ist es u.a. Kommentare zu entfernen und Makros zu expandieren. Dann erst wird die Datei an den Compiler weitergereicht.
Ok, das war´s erstmal von mir. Machts gut.
Bye, Sebastian
cu, Ulf