C Lernen mit cc65 und C64

Abend 2: Variablen und Funktionen

Heute schauen wir uns die wichtigsten Dinge an, die man für ein grundlegendes Programm benötigt. Dabei behandeln wir heute zwar keins der Themen vollständig, bekommen aber einen gewissen Überblick.

Ein kleines Programm zum Aufheizen

Um mal ein Programm zu haben, was ein bisschen mehr nach C aussieht als unser Testprogramm von gestern, tippen wir mal das ein:

#include <stdio.h>
 
int main(void)
{
    int a = 12;
    int b;
 
    b = 9;
 
    printf("Die Summe von %d und %d ist %d\n",
       a, b, a + b);
}

Übersetzen und starten wir das Programm einmal:

cl65 -o summe.prg summe.c

Und das ist die Ausgabe des Programms:

Wer sich jetzt die Größe des entstandenen Programms ansieht, wird vor Schreck vom Hocker fallen: Über 2500 Bytes!?! Aber auch dieses Mysterium werden wir bald untersuchen.

Im Laufe dieses Abends werden wir das Programm auch noch mit Kommentaren versehen, wie sich das gehört. Und am Ende dieser Lektion werden wir fast alles an diesem Code verstehen.

Variablen

Variablen kennen wir schon aus BASIC. Im Prinzip ist eine Variable ein Stück Speicher, dessen Inhalt man über einen Namen (Bezeichner, Symbol) ansprechen kann.

Übrigens können Variablen prinzipiell auch in Prozessorregistern (z.B. A, X, Y) aufbewahrt werden. Bei modernen Prozessoren mit 16 und mehr Registern wird das oft gemacht. Beim cc65 werden (momentan) Variablen nicht dauerhaft in CPU-Registern gehalten. Das wäre auch etwas eng. Welche Variablen in Registern gehalten werden, kann man sich als Programmierer mit bestimmten Schlüsselwörtern „wünschen“, letztendlich entscheidet es aber der Compiler.

Wieviel Speicher für eine Variable reserviert wird und als was der Inhalt dieses Speichers interpretiert wird, hängt vom Typ der Variablen ab. Schauen wir uns das doch nochmal im C64 BASIC an:

100 A=3.1  : PRINT A
110 A%=3.1 : PRINT A%
120 A$=3.1 : PRINT A$
READY.
 
RUN
3.1
3
?TYPE MISMATCH  ERROR IN 120
READY.

An diesem Beispiel sieht man, dass der BASIC-Interpreter zwischen verschiedenen Variablentypen unterscheidet. Obwohl ich A und A% mit 3.1 initialisiert habe, wird bei A% nur der Wert 3 ausgegeben. Die Zuweisung von 3.1 nach A$ ist gleich ganz in die Hose gegangen. Die Typen passen offensichtlich nicht zusammen.

Wie der BASIC-Programmierer weiß, ist A vom Typ Fließkomma und kann deshalb den Wert 3.1 speichern. A% ist allerdings eine ganzzahlige Variable. Bei der Zuweisung sorgt der Interpreter automatisch dafür, dass der Fließkommawert 3.1 in eine Ganzzahl umgewandelt wird. Diesem Vorgang werden wir bei C auch noch begegnen, er heißt Typumwandlung (type cast).

A$ ist eine Zeichenkette (string) und kann deshalb keine nummerischen Werte speichern, sondern höchstens eine Textrepräsentation davon. Hier kennt BASIC also keine automatische Typumwandlung. C übrigens auch nicht.

Wann genau reserviert denn der BASIC-Interpreter den Speicher für diese Variablen? Wenn das erste mal auf sie geschrieben wird.

Bei C wird der Typ einer Variable nicht durch den Namen festgelegt. Der Programmierer muss ausdrücklich dafür sorgen, dass der Compiler die Variable kennt und Speicher für sie reserviert.

In C gibt es zwei Begriffe, die in diesem Zusammenhang wichtig sind: Die Deklaration eines Objekts, z.B. einer Variablen, gibt alle Eigenschaften, z.B. den Typ, bekannt. Eine Deklaration reserviert aber noch keinen Speicher. Eine Definition gibt auch die Eigenschaften des Objekts bekannt und sorgt außerdem dafür, dass der Compiler Speicher für das Objekt reserviert. Eine Definition erfüllt also auch die Aufgabe einer Deklaration.

Wenn wir von nun an vom Anlegen einer Variablen sprechen, meinen wir im hier beschriebenen Zusammenhang eine Definition. Trotzdem wird oft der Begriff Deklaration für eine Definition benutzt.

Jede Variable muss dem Compiler bekannt sein, bevor sie benutzt werden kann, anderenfalls bricht der Compiler 1) mit einer Fehlermeldung ab!

Schreiten wir endlich zur Tat:

Unser Datentyp des Tages heißt int. Das ist ein vorzeichenbehafteter Ganzzahltyp (signed integer). Im Standard ist festgelegt, das int mindestens 2 Bytes (= 16 Bits) groß sein muss. In einem int können also mindestens Werte zwischen -32768 bis 32767 gespeichert werden.

Tatsächlich ist int bei den meisten Compilern 2 oder 4 Bytes groß. Meistens entspricht int der Registerbreite, auf x86-Compilern beispielsweise 4 Bytes (= 32 Bits).

Ihr solltet Euch in portierbarem Code nicht darauf verlassen, dass int breiter als 16 Bits ist. Wenn Ihr eine ganz bestimmte Bitbreite möchtet, solltet ihr standardisierte Typen benutzen. Diese werden wir bald kennen lernen.

Also legen wir mal ein int an:

int main(void)
{
    int a;
}

Diese Programm ist absolut sinnfrei, aber vollständig. Wir können es ja mal compilieren:

$ cl65 -o inta.prg inta.c
inta.c(4): Warning: `a' is defined but never used

Wir sehen hier, dass der Compiler in unserem Programm eine Ungereimtheit festgestellt hat: Wir haben eine Variable angelegt, aber nicht benutzt. Bei Warnungen wird das Programm trotzdem erstellt.

Compiler-Warnungen solltet Ihr immer beachten und berichtigen. Oft verbirgt sich dahinter ein potentieller Fehler.

Der einfachste Aufbau einer Variablendefinition ist also folgender:

Typ Variablenname;

Jede dieser Definitionen wird mit einem Semikolon abgeschlossen. Es können auch mehrere Variablen eines Typs in einer Zeile definiert werden:

Typ Variablenname1, Variablenname2;

ist das gleiche wie:

Typ Variablenname1;
Typ Variablenname2;

Übrigens wird es als guter Stil angesehen, jede Variable in einer eigenen Zeile anzugeben. Die zweite Schreibweise wird deshalb oft bevorzugt. Ausnahmen können logisch zusammengehörige Variablen sein, wie z.B. xKoordinate, yKoordinate.

Die Bezeichner, also auch Variablennamen, dürfen in C ziemlich lang sein. Und das sollten sie auch: An ihrem Namen sollte man auch ihre Bedeutung erkennen. Und im Gegensatz zu einer interpretierten Sprache wie BASIC wirkt sich die Länge nicht negativ auf die Ausführungsgeschwindigkeit aus. Ausnahmen sind triviale Variablen wie i, was oft in Schleifen als Index benutzt wird.

In C wird Groß- und Kleinschreibung unterschieden; Summe, SuMmE und summe wären also verschiedene Namen.

Beim Anlegen einer Variablen können wir ihr auch schon einen Wert zuweisen.

int a = 12;

Spätere Zuweisungen werden mit dem Gleichheitszeichen = vorgenommen. Wie jede Anweisung in einem C-Programm, muss auch solch eine Zuweisung mit einem Semikolon abgeschlossen werden.

b = 9;

Es gibt noch viel mehr über Variablen zu sagen, wir werden also immer wieder auf sie zurückkommen.

Zeilenumbrüche im Quelltext

C-Quelltexte sind nicht zeilenorientiert aufgebaut wie BASIC oder die meisten Assembler. Überall, wo ein Leerzeichen stehen darf, kann auch ein Zeilenumbruch erfolgen. Wie schon erwähnt, werden Anweisungen in C durch ein Semikolon abgeschlossen.

Ausnahme: In Präprozessor-Anweisungen, die mit einer Raute # beginnen, dürfen nich ohne weiteres Zeilenumbrüche eingefügt werden. Es dürfen auch keine anderen Sprachelemente in der gleichen Zeile folgen. Was genau eine Präprozessoranweisung ist, sehen wir uns später an.

Im Extremfall können wir unser Programm vom Anfang so schreiben:

#include <stdio.h>
int main(void){int a=12;int b;b=9;printf("Die Summe von %d und %d ist %d\n",a,b,a+b);}

Jetzt sieht's aus wie ein perfektes BASIC-Programm :-/ Aber es ist kein Byte kürzer oder schneller geworden.

Bis auf wohlüberlegte Ausnahmen sollte in C immer nur eine Anweisung in einer Zeile stehen.

Numerische Konstanten

Eben haben wir sie schon benutzt, die numerischen Konstanten. Vielleicht versteht ihr mich besser, wenn ich sage: Zahlen.

Dezimale Zahlen sind einfach. Die schreibt man genau wie in BASIC. C versteht auch hexadezimale Zahlen, die erkennt der Compiler an dem Präfix 0x. Außerdem kann man noch Oktalzahlen angeben, die beginnen mit einer 0.

Beispiel:

int a;
a = 20;   // a ist jetzt 20
a = 0x14; // hex 14 ist auch 20
a = 024;  // oktal 24 ist auch 20

Achtet darauf, dass eine Zahl, die mit 0 beginnt, oktal und nicht dezimal ist. Sonst können sich leicht unerklärbare Phänomene einstellen.

Zeichenkettenliterale

Um einen konstanten Text in ein C-Programm einzubetten, benutzt man Zeichenkettenliterale (string literals). Sie werden von Anführungszeichen eingeschlossen. Wir benutzen hier puts als Beispiel, um das auch mal im Code zu sehen:

puts("Das ist ein Text");

Diese Literale können über mehrere Zeilen gehen, der Compiler hängt sie wieder zusammen. Beachtet, dass dort kein Semikolon oder Komma oder sonstwas steht. Nach wie vor handelt es sich hier um einen String:

puts("Das ist ein "
       "Text");

In solchen Literalen können bestimmte Sonderzeichen vorkommen, die man als Escape-Sequenz schreiben muss, die durch einen Backslash eingeleitet wird. Die wichtigsten:

Sequenz Bedeutung
\“ Anführungszeichen
\n Zeilenumbruch (New Line)
\r Wagenrücklauf :-) (Carriage Return)
\t Tabulator

Es handelt sich jeweils um ein echtes Zeichen, das im Quelltext auf diese Weise umschrieben wird.

In folgender Ausgabe finden wir mittendrin einen Zeilenumbruch. Probiert's doch mal aus:

puts("Das ist ein\nText");

Höchste Zeit für Kommentare

Damit man sein eigenes Programm auch noch nach Jahren versteht und im Idealfall sogar andere etwas mit dem Quelltext anfangen können, sollte man sein Programm kommentieren.

Ein klassischer C-Kommentar beginnt mit der Zeichenfolge /* und endet mit den Zeichen */. So ein Kommentar kann über mehrere Zeilen gehen. Mehrere Kommentare dieser Sorte können nicht geschachtelt werden.

Seit dem Standard C99 sind auch Kommentare erlaubt, die mit zwei Schrägstrichen (/ /) beginnen und bis zum Ende der Zeile gehen. Viele Compiler haben diese Kommentare auch schon vor 1999 akzeptiert.

In vielen Projekten sind C99-Kommentare nach wie vor nicht erlaubt. Der Grund kann z.B. eine gewünschte Übersetzbarkeit auf C90-Compilern sein.

Kommentieren wir also ein Stück des heutigen Testprogramms:

#include <stdio.h>
 
/*
 * Das ist ein mehrzeiliger Kommentar
 */
int main(void)
{
    // Variable a als Ganzzahlwert anlegen und initialisieren
    int a = 12;
    // Variable b ist ein Ganzzahlwert
    int b;
 
    // Jetzt bekommt b den Wert 9
    b = 9;
 
    printf("Die Summe von %d und %d ist %d\n",
       a, b, a + b);
}

Natürlich wirken sich Kommentare nicht negativ auf Größe und Geschwindigkeit des fertigen Programms aus.

Jetzt geht's richtig los: Funktionen

Früher hat man sein Programm auf eine große Steintafel gemeißelt und gehofft, dass sie sich irgendein Interpreter von vorn bis hinten durchliest. Da früher vorbei ist, teilt man seine Programme jetzt in leicht verdauliche, kalorienarme Häppchen auf.

In C heißen solche Häppchen Funktionen. Ja, sie haben tatsächlich eine gewisse Ähnlichkeit mit mathematischen Funktionen wie y = sin(x).

Schauen wir uns zur Einführung mal ein schönes BASIC-Programm an:

100 A=1
110 B=SIN(A)
120 GOSUB 150
130 END
140 REM -------------------------
150 PRINT"SIN(";A;") = ";B
160 RETURN
170 REM ------------------------
 
RUN
SIN( 1 ) =  .841470985
 
READY.

Das versteht wohl hier jeder. Schauen wir mal ein bisschen genauer auf Zeile 110. Hier ist eine klassische mathematische Funktion. Sie bekommt den Wert aus A als Futter und spuckt einen Wert aus, den wir B zuweisen. Praktisch sieht das so aus, dass sich der Interpreter den Wert aus A schnappt, in eine ROM-Routine abtaucht, die den Sinus berechnet und das Ergebnis, das diese Routine berechnet hat, in den Speicher der Variable B legt.

Diese Berechnung des Sinus, die eine Verzweigung in einen anderen Programmteil bewirkt und normalerweise auch eine Rückkehr aus dieser, heißt in C Funktion. Die Funktion zum Berechnen des Sinus heißt in C übrigens sin, ist aber im cc65 nicht enthalten, da der momentan keine Fließkommaoperationen unterstützt.

Aber schauen wir mal weiter. In Zeile 120 wird mit GOSUB ein anderer Teil des BASIC-Programms aufgerufen. Der Interpreter merkt sich, wo er herkommt, führt den Code ab Zeile 150 aus und kehrt durch RETURN wieder hinter das GOSUB zurück.

Auch für so ein Unterprogramm formuliert man in C eine Funktion. Von der Syntax her unterscheiden sich selbstgeschriebene Funktionen nicht von den „mitgelieferten“ bzw. „eingebauten“ Funktionen, wie etwa sin.

Noch ein letztes Beispiel sehen wir in dem BASIC-Programm. In Zeile 150 sehen wir PRINT. Auch hier wird ein bestimmtes Unterprogramm des Interpreters ausgeführt, was sich dann die darauffolgenden Parameter holt, auswertet und ausgibt. Auch das macht man in C mit einer Funktion.

In Assembler kennen wir diese Art von Unterprogrammen auch: Mit JSR wird zu einem Unterprogramm verzweigt. Die Rücksprungadresse wird vorher auf den Stack gelegt. Mit RTS wird die Rücksprungadresse vom Stack geholt und der aufrufende weiter ausgeführt. Wir werden bald sehen, dass der C-Compiler auch diese Instruktionen benutzt.

Wie sieht denn nun so eine Funktion in C aus?

Typ Funktionsname(Parameterliste)

Das offensichtlichste Kennzeichen einer Funktion sind also die runden Klammern. Wenn wir jetzt mal nach runden Klammern in den bisherigen Programmen dieses Kurs suchen, können wir schon drei Funktionen finden:

 .. main(..) ..

 .. puts(..) ..

 .. printf(..) ..

Wo Funktionen herkommen

Wenn man es genau nimmt, ist C eine recht kleine Sprache. Ein paar Operatoren, ein paar Klammern, eine Hand voll Datentypen. Und noch ein paar Kontrollstrukturen. Aber nichts was wirklich was tut.

Das Leben eingehaucht bekommt ein C-Programm erst durch seine Funktionen. Wo kommen die denn aber her?

Zunächst kann (und muss) man selbst Funktionen definieren, wenn man ein C-Programm schreibt. Wir haben das auch schon getan: main ist eine Funktion, die wir definiert haben. In jedem normalen C-Programm muss es genau eine Funktion mit diesem Namen geben.

Nach dem Start eines C-Programms wird unsere Funktion main ausgeführt. Bei einem BASIC-Programm startet die Ausführung nach RUN in der ersten Zeile. Bei C hingegen ist es egal, wo sich main im Programm bzw. im Quelltext befindet.

Richtig Arbeit sparen uns die Funktionen, die wir benutzen, aber nicht selbst schreiben müssen. Wir haben bisher zwei solcher Funktionen benutzt, puts und printf. Diese gehören zu einer sogenannten Bibliothek. Das ist eine Sammlung von Funktionen, die vom Compiler (oder genauer: dem Linker) zu unserem Programm hinzugebunden werden. Üblicherweise kommen Compiler mit einer Menge an Bibliotheken.

Jetzt können wir uns erklären, warum unsere Programme immer größer geworden sind, als es unsere Quelltexte vermuten ließen: Die Programme enthielten nicht nur den Code, den wir geschrieben haben, sondern auch die Bibliotheksfunktionen, die wir benutzten.

printf ist eine unglaublich mächtige und komplexe Funktion. Der Quelltext der Implementierung für den cc65 ist zum Beispiel mehr als 500 Zeilen lang! Deshalb sollte man solche mächtigen Funktionen nur dann verwenden, wenn man sie wirklich braucht. In dem Beispiel gestern haben wir deshalb auch puts statt printf verwendet. Das ist auch eine Standardfunktion, aber etwas handlicher als printf. Für „Hello world“ haben wir keine der tollen Möglichkeiten von printf benötigt.

Der Typ einer Funktion

Der Typ einer Funktion legt fest, welche Art von Daten eine Funktion uns zurückgibt. Bei sin() können wir uns schon selbst zusammenreimen, was das sein müsste: Eine Fließkommazahl.

Aber was für einen Typ hat den puts()? Schauen wir doch mal in die Beschreibung der Funktion:

http://www.rt.com/man/puts.3.html

puts() and fputs() return a non - negative number on success, or EOF on error.

Aha, wir können also am Rückgabewert erkennen, ob's geklappt hat. Das ist uns momentan egal, also ignorieren wir es einfach, in dem wir nichts damit tun. (Und was EOF für eine komische Zahl ist, brauchen wir deshalb an dieser Stelle auch nicht klären.)

Wir könnten also sowas tun:

int result;
result = puts("Wurstbrot");
printf("result is %d\n", result);

Aufgabe 1: Baue aus dem Code-Schnipsel oben ein lauffähiges Programm. Nimm das Programm vom Anfang des Kapitels als Vorlage. Poste den vollständigen Quelltext und die Ausgabe des Programms im unten verlinkten Thread im Forum64.

Es gibt übrigens auch Funktionen, die tatsächlich nichts zurückgeben. Diese haben dann den Typ void. Moment, Wörterbuch rausholen: void = die Leere, das Nichts. Klar, passt ja.

Wenn man versucht, den Rückgabewert einer Funktion, die den Typ void hat irgendwie zu verwursten, wird der Compiler das mit einer Fehlermeldung ahnden.

Wie eine Parameterliste aussieht

Eine Parameterliste kann je nach Funktion einen oder mehrere Parameter haben, sie kann auch leer sein. Wenn eine Funktion mit einer leeren Parameterliste vereinbart wird, darf/braucht man ihr keinen Parameter übergeben. Unsere main-Funktion haben wir so definiert:

int main(void)
{
  ..
}

Eine leere Parameterliste wird also mit (void) beschrieben; das heißt, dass unsere Implementierung von main keine Parameter erwartet.

Die Funktion puts erwartet genau einen Parameter, und zwar den Text, den wir ausgeben möchten.

Es gibt sogar Funktionen, die eine variable Anzahl von Parametern erwarten. Zu denen gehört printf. Genaueres sehen wir bald.

Die Anzahl und die Typen der Parameter, die wir übergeben, müssen zu dem passen, was die Funktion erwartet.

Aufgabe 2: Rufe die Funktion puts mit einer Ganzzahl statt mit einem Text auf. Was passiert? Hinweis: Dafür kannst Du eine Variable vom Typ int oder einfach eine Zahl, d.h. ein Literal, benutzen. Poste Deine Erkenntnisse im unten verlinktem Thread im Forum64

Der Funktionskörper

Der eigentliche Körper der Funktion ist ein Block, der in { geschweifte Klammern } eingeschlossen ist.

Am Beginn eines solchen Blocks können Definitionen/Deklarationen folgen, danach eine Reihe von Anweisungen.

Seit C99 ist es erlaubt, Deklarationen, Definitionen und Anweisungen beliebig zu mischen. Da solcher Code nicht mit allen Compilern (u. a. auch nicht mit cc65) übersetzbar ist, solltet Ihr darauf verzichten.

Beispiel für zwei Funktionen:

void guteFunktion(void)
{
    int a = 7;  // erst Variablendefinition
    a = a + 1;  // dann Anweisung
}
 
void boeseFunktion(void)
{
    int a = 7;  // erst Variablendefinition
    a = a + 1;  // dann Anweisung
    int b;      // noch eine Variablendefinition???!!!
}

Versucht das mal zu übersetzen:

cl65 -o gutboese.prg gutboese.c

Da geht der Compiler richtig ab!

Die Funktion puts

Die Funktion puts haben wir erstmalig in unserem Hello-World-Programm benutzt.

So sieht sie aus:

int puts(const char *s);

Die Funktion erwartet einen Parameter const char *s. Momentan ist nur wichtig, dass wir an der Stelle einen Text in Anführungszeichen, also ein Zeichenkettenliteral, angeben können. Beachtet, dass puts an die Ausgabe automatisch einen Zeilenumbruch anhängt.

Die genaue Beschreibung der Funktion findet man dort:

http://www.rt.com/man/puts.3.html.

Die Funktion printf

Die Funktion printf kann man benutzen, wenn man formatierte Ausgaben haben möchte. Dazu übergibt man printf als ersten Parameter einen Formatstring, der Platzhalter für auszugebende Daten enthält. Die Platzhalter bestimmen nicht nur die Position der jeweiligen Ausgabe innerhalb des Texts, sondern können auch Angaben zur Einrückung, Darstellung, Füllzeichen u.a. haben.

Die Funktion sieht so aus:

int printf(const char *format, ...);

Der erste Parameter const char *format kann von uns mit einem Zeichenkettenliteral („String“) versorgt werden. Die drei Punkte deuten auf etwas uns noch unbekanntes hin: Hier wird eine variable Anzahl von Argumenten erwartet. Die Anzahl und Reihenfolge der Argumente, die wir printf übergeben, müssen den Platzhaltern entsprechen, die wir im Formatstring angeben.

Eine komplette Beschreibung von printf findet ihr dort:

http://www.rt.com/man/printf.3.html

Vielleicht ist Euch schon aufgefallen, dass printf nicht automatisch einen Zeilenumbruch anfügt. Dafür könnt Ihr einfach ein '\n' im Format-String verwenden.

Schauen wir uns einmal ein paar Beispiele für Platzhalter an:

Platzhalter Bedeutung
%d Gibt eine Zahl im Dezimalformat aus, das Argument ist int.
%3d Falls die Zahl kürzer als 3 Ziffern ist, fülle links mit Leerzeichen auf
%03d dito, fülle aber mit '0' auf 2)
%x Gib eine Zahl im Hexadezimalformat aus

Um die Platzhalter wirklich zu verstehen, sollte man sich auf alle Fälle früher oder später die Dokumentation zu printf ansehen!

Aufgabe 3: Schreibe ein Programm, dass die drei Werte 7, 23 und 342 in drei Variablen ablegt und deren Summe berechnet. Die Ausgabe des Programms soll so aussehen:

        7
       23
      342
=========
      372

Eine wichtige Bedingung: Die Einrückungen dürfen nicht manuell im Quelltext durch Leerzeichen vorgenommen werden! Poste Deine Lösung im unten verlinktem Thread im Forum64.

Das war's für heute

Wenn ihr nochmal zum Programm nach ganz oben zurückblättert, werdet ihr vielleicht schon besser durchblicken als noch vor ein paar Stunden. Sicher gibt es noch viel zu tun, aber der erste Schritt ist getan.

Fragen zu dieser Lektion

In diesem Thread im Forum64 können Fragen zu dieser Lektion gestellt werden:

http://www.forum64.de/wbb3/index.php?page=Thread&threadID=27685

In diesem Thread diskutieren Experten, was hier nicht stimmt und wie der Kurs verbessert werden kann:

http://www.forum64.de/wbb3/index.php?page=Thread&threadID=27594

Hier sind ein paar interessante Fragen aus dem oben genannten Forum:

Ich wollte folgende Zuweisung durchführen:

result = puts("Wurstbrot");

Warum wird hier eigentlich beim ausführen das Wort „Wurstbrot“ ausgegeben ?

Stehen auf der rechten Seite einer Zuweisung Funktionen, werden diese vor der Zuweisung ausgeführt. Denn nur so kann deren Rückgabewert ermittelt werden. Da eine Funktion einfach nur ein Unterprogramm ist, wird sie alle ihre üblichen Effekte (sog. Seiteneffekte) haben. Es ist also egal, ob sie „einfach nur so“ aufgerufen wird oder ob ihr Rückgabewert irgendwie weiterverwurstet wird.

Warum schreibt folgender Code wirre Zeichen auf den Bildschirm, nicht aber die Zahl 365?

int result;
int a = 365;
result = puts(a);

Die Funktion puts ist dazu gedacht, Zeichenketten auszugeben. Wie wir später noch sehen werden, wird ihr dazu die Adresse übergeben, an der der Text im Speicher steht. Übergeben wir ihr die „Adresse“ 365, gibt sie alle Zeichen aus, die ab dieser Adresse stehen. Die Ausgabe wird bei der nächsten 0 im Speicher beendet.

Schreiben wir hingegen einen String in Anführungszeichen, legt der Compiler diese Zeichen irgendwo in den Speicher und übergibt der Funktion die entsprechende Adresse.


So, das war's für heute, weiter geht's mit 03-abend3

1) oder spätestens der Linker
2) Das geht nicht mit anderen Füllzeichen anstelle der 0
ckurs/02-abend2.txt · Zuletzt geändert: 11/05/2009 12:59 (Externe Bearbeitung)
www.chimeric.de Creative Commons License Driven by DokuWiki Recent changes RSS feed