Informatik für Ingenieure Vorlesungsskript

Werbung
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-0
6.
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Funktionen ................................................................................................................................................ 1
6.1
Standardfunktionen und benutzerdefinierte Funktionen .................................................................. 1
6.2
Ein einfaches Einführungsbeispiel mit allgemeinen Erklärungen .................................................... 2
6.3
Erweitertes Einführungsbeispiel....................................................................................................... 8
6.4
Parameterübergabe: Wertübergabe, Referenzübergabe und C++ - Referenztypen ...................... 9
6.5
Modular aufgebaute Programme ................................................................................................... 11
6.6
Speicherklassen: Gültigkeitsbereich und Lebensdauer von Variablen ......................................... 12
6.6.1
Globale Variablen und externe Deklaration .......................................................................... 13
6.6.2
Lokale Variablen mit automatischer oder statischer Gültigkeit ............................................. 15
6.6.3
Registerbasierte Variablen .................................................................................................... 18
6.6.4
Veränderbarkeit von Variablen.............................................................................................. 19
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-1
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
6. Funktionen
6.1
Standardfunktionen und benutzerdefinierte Funktionen
Programme in C und C++ kommen von Anfang an nicht ohne den Gebrauch von Standardfunktionen aus.
Hersteller der gängigen C-Entwicklungssysteme (Editor, Compiler, Linker, Lader, mit integrierter Entwicklungsumgebung) liefern diese Funktionen in Form sog. Bibliotheken. Das sind Dateien, die schon in Maschinensprache übersetzte Funktionen enthalten. So sind z.B. alle ANSI-Standardfunktionen des Microsoft Compilers in diesem Ordner:
...\Microsoft Visual Studio\VC98\Lib\
Für C/C++ - Entwicklungssysteme unter UNIX findet man die Bibliotheken meistens in einem der Ordner
/usr/lib/ oder /usr/<Name des Compilers>/lib/
Standardfunktionen führen z.B. Ein- und Ausgabeoperationen aus wie die von Anfang an unverzichtbare formatierte Bildschirmein- und -ausgabe. Ganz allgemein stellen sie Operationen zur Verfügung, die in der
Sprache C nicht so vorhanden sind, dass sie sich produktiv genug für Anwendungsprobleme nutzen lassen
sind.
In Form der hersteller- und maschinenneutralen Standards ANSI und POSIX für C stellen Standardfunktionen eine mächtige und auf praktisch jedem Rechnertyp einheitlich verwendbare Erweiterung der Programmiersprache C dar.
Man darf bei ihrem Einsatz freilich nie vergessen:
⇒ Standardfunktionen sind keine Sprachoperationen ! Der Compiler prüft nicht, ob ihr Aufruf im Programmzusammenhang Sinn ergibt. Er generiert auch keine Maschinenbefehle.
⇒ Standardfunktionen sind vielmehr fertige Maschinenprogrammbausteine, die anlässlich ihres Aufrufs
nur noch in das Anwendungsprogramm eingebunden werden und die sich der Prüfung durch den
Compiler auf korrekten Gebrauch fast gänzlich entziehen!
Über den bisherigen Gebrauch von Standardfunktionen hinaus führt dies Kapitel Sie in die Programmierung
benutzerdefinierter Funktionen ein. Deren Quellcode wird als Teil des Benutzerprogramms entwickelt und
mit diesem übersetzt, anstatt wie die Standardfunktionen für einen Compiler quasi unsichtbar nur im Linkprozess dem Programm hinzugefügt zu werden.
Einteilung eines Programmtexts in mehrere Funktionen bringt einerseits unmittelbar die Übergabe von Parametern mit sich. Mittelbar kommen andererseits Ordnungsgesichtspunkte zur Programmaufteilung zum Tragen. Beides wird in diesem Abschnitt behandelt.
Warum benutzt man überhaupt Funktionen (Prozeduren), wenn sie offensichtlich den Lernaufwand erhöhen? Man könnte doch alles Nötige in der main –Funktion formulieren.
Es gibt mehrere Gründe, warum Programme in Funktionen gegliedert werden sollten:
► Überschaubarkeit: je größer ein Programm ist, desto schwieriger wird es, seinen Aufbau und sein
Laufzeitverhalten zu verstehen, wenn alles in einer einzigen anwenderdefinierten Funktion main
zusammengefasst ist. Konkret hat es u.a. diese Nachteile:
Die Kontrolle der Daten ist schwer durchschaubar. Alle Variablen gelten im ganzen Programm und
können überall geändert werden. Hingegen können Funktionen die nur in ihnen benötigten Variablen
"einkapseln" und gegen unbeabsichtigte oder undisziplinierte Zugriffe schützen.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-2
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Ein weiterer schwerer Nachteil in Programmen ohne funktionelle Untergliederung ist, dass Diagnose
und Behebung der unvermeidlichen Entwicklungsfehler nur noch schwer oder praktisch nicht möglich
ist. Für die Weiterentwicklung zum Zweck von Verbesserungen gilt dasselbe. Das Produkt "lebt" nicht.
Da Änderungen und Erweiterungen kaum möglich sind, muss man im Bedarfsfall immer wieder von
vorn anfangen. Unter technischen und wirtschaftlichen Gesichtspunkten ist das sehr unbefriedigend.
Letztes Argument zum Thema Überschaubarkeit an dieser Stelle: Oft wiederholen sich Abschnitte
derselben Anweisungen an mehreren Stellen des Programms. Anstatt ihre Formulierung im Programmtext zu wiederholen, kann man sie in Funktionen auslagern. Der Programmtext wird kürzer (=
übersichtlicher); das Risiko von Fehlern wird geringer.
► Wiederverwendbarkeit: Funktionen ermöglichen außer der Erhaltung einer Übersicht durch das Prinzip
der Gliederung auch die Einteilung eines Programms in einmal verwendete und wieder verwendbare
Teile. Diese Strukturierung kann sowohl innerhalb eines Programms zum Tragen kommen als auch dadurch, dass mehrere Programme dieselben allgemein nutzbaren Funktionen einbinden.
Eine mehrmals verwendete Funktion wird je nach Anwendung mit unterschiedlichen Parametern
aufgerufen. Sie kann dann, grob betrachtet, dieselbe Arbeit an verschiedenen Stellen einer Anwendung oder in unterschiedlichen Anwendungen leisten, aber mit verschiedenen Daten für jeden einzelnen Fall.
6.2
Ein einfaches Einführungsbeispiel mit allgemeinen Erklärungen
Dieser kleine Abschnitt soll Funktionen anhand eines Beispiels einführen. Er gliedert sich in drei Betrachtungsebenen:
a) Programmtext: wie formuliert man Funktionen in C oder C++?
b) Programmfluss: was bewirkt ein Funktionsaufruf im Ablauf eines Programms?
c) Datenfluss: auf welche Art tauschen aufrufende und aufgerufene Funktionen Daten aus?
Zu a): Programmtext
Funktionen sind separat übersetzbare und separat ausführbare Teile eines Programms. Ein Programm in
C/C++ kann mehrere Funktionsdefinitionen enthalten, mindestens aber die der Funktion main . Diese wird
"aufgerufen", indem man das betreffende Programm startet. Der Programmcode von main kann je nach
Gestaltung in seinem Ablauf weitere Funktionen aufrufen, sofern diese zum Programm gehören.
► Zum ausführbaren Programm gehören einerseits die im Linkprozess hinzu gebundenen externen
Funktionen, wie z.B. die Funktionen der Standardbibliothek. Sie werden zwar aufgerufen und ausgeführt, erscheinen aber nicht im Programmtext.
► Andererseits können zum Programm auch benutzerdefinierte Funktionen gehören. Diese erscheinen
in einfachen Programmen im Programmtext und werden gemeinsam mit main compiliert.
Das folgende Bild zeigt den struktografischen Grobentwurf eines einfachen nur zur Demonstration gedachten aus den zwei Funktionen main und testfunktion bestehenden Programms.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-3
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Der Programmtext gliedert sich grob betrachtet in zwei Abschnitte:
►
In einem global gültigen ersten Teil werden Headerdateien eingefügt und Makros definiert. Außerdem
stehen hier die sog. Prototypen der benutzerdefinierten Funktionen, welche diese im Voraus deklarieren. In den eingefügten Headerdateien sind auf ähnliche Weise die Prototypen der Standardfunktionen enthalten. Die Vorausdeklaration macht dem Compiler die Funktionen bekannt, damit er deren
Aufruf verarbeiten kann, bevor er dazu kommt, ihre Funktionsdefinition zu übersetzen. Die aufgerufenen Standardfunktionen übersetzt ja er in der Tat nie.
Der Prototyp einer Funktion hat folgendes Format:
<Rückgabewert> <Name> <Argument>;
Dabei sind
<Rückgabewert>
ein gültiger Datentyp für das Funktionsergebnis
<Name>
der Name der Funktion. ACHTUNG: Funktionsnamen sind in C immer
global! Man darf also keine Namen benutzen, die z.B. schon für Standardbibliotheksfunktionen vergeben sind. Problem dabei: wer kennt alle Funktionen, mit Namen? Ausweg für C-Programmierer: möglichst projektspezifische Namensvergabe. Weiterer Ausweg für C++ – Programmierer: Definition eigener Namensräume und/oder objektorientierte "Verpackung" der
Funktionen in Klassen.
Falls eine benutzerdefinierten Funktion denselben Namen bekommen hat
wie eine Standardfunktion, die durch #include <Headerdatei> angemeldet wurde, warnt der Compiler vor einer "duplicate declaration",
vor unpassenden Parametern und vor gefährlichen Typumwandlungen des
Rückgabewerts. Achten Sie bei der Programmübersetzung auf diese Anzeichen. In C++ ist der Prototyp zwingend nötig, in C nicht. Ohne Prototypen sind solche Probleme manchmal nur indirekt erkennbar, z.B. daran,
dass eine selbstgeschriebene Funktion etwas anderes tut als erwartet, weil
nämlich der Linker an ihrer Stelle die gleichnamige Bibliotheksfunktion eingebunden hat.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-4
<Argument>
►
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
in runde Klammern eingeschlossene Aufzählung der formalen Parameter:
( <Datentyp><Name> [,<Datentyp> <Name>[,...]] ) , ähnlich
wie bei einer Variablendeklaration. Tatsächlich sind die formalen
Parameter innerhalb der Funktion wie die lokal deklarierten Variablen
nutzbar. Anders als die Variablen haben aber die formalen Parameter
immer Anfangswerte! Sie stammen aus den jeweiligen Funktionsaufruf
und heißen aktuelle Parameter.
In einen zweiten Teil des Programmtexts sind die Funktionsdefinitionen (im folgenden auch nur kurz
Funktion(en) genannt) nutzerdefinierter Funktionen enthalten. Die Reihenfolge der Funktionen im Programmtext spielt dabei keine Rolle, solange deren Prototypen alle im globalen Teil im oberen Teil des
Programmtexts enthalten sind. Jede Funktionsdefinition hat das Format
<Rückgabewert> <Name> <Argument> { <Verbundanweisung> }
Der Kopf der Funktionsdefinition, vor der Verbundanweisung, gleicht dem Funktionsprototypen. Dann
folgt hier aber statt der leeren Anweisung ; die Verbundanweisung {...} mit den Operationen
und Anweisungen der Funktion, auch Rumpf der Funktion genannt.
Wann und wie oft eine Funktion ausgeführt wird, hängt davon ab, wann und wie oft das laufende Programm
auf Anweisung mit deren Aufruf trifft, also den Details einer jeweiligen Programmgestaltung.
Dazu soll konkret hier folgende Anforderung gelten:
Das Programm liest in seiner main - Funktion einen unsigned int - Wert ein und ruft dann die
testfunktion auf. Dabei übergibt ihr main den Eingabewert als aktuellen Parameter. Wie das in
C praktisch geschieht, wird im Anschluss gezeigt. testfunktion hat die Aufgabe, festzustellen, ob
der übergebene Parameter eine Primzahl ist oder nicht. Dazu gibt sie einen Wert zurück: ist dieser 0,
ist der Parameter keine Primzahl, umgekehrt bedeutet die Rückgabe >0: es der Parameter ist eine
Primzahl. Den Typ dieses Rückgabewerts zeigt der Funktionsprototyp vor dem Namen der Funktion
an. Die aufrufende Funktion main gibt das Ergebnis am Bildschirm aus.
Die testfunktion übernimmt den Wert des aktuellen (übergebenen) Parameters durch ihren formalen Parameter im eingeklammerten Argument der Funktion. Sie führt dann die Primzahlsuchebestimmung aus. Dabei verwendet sie den Algorithmus "Sieb der Eratosthenes": Der Parameter wird beginnend mit durch 2, 3, 4, usw. solange modulo-dividiert, bis entweder der Divisor die Grenze
parameter erreicht oder die Modulodivision den Wert 0 ergibt. Im letzteren Fall ist der Parameter
keine Primzahl: er lässt sich ohne Rest durch mindestens eine Zahl außer 1 und sich selber teilen
(Definition der Primzahl). Im ersteren ist der Parameter eine Primzahl, denn durch alle größeren Divisoren entsteht grundsätzlich ein Rest > 0.
Auf Basis dieser Anforderung kann der Entwurf des Programms verfeinert werden wie folgt:
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-5
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Diesem Entwurf entspricht folgendes Programm in C++:
/////////////////////////////////////////////////////////////////////////
// 1. Teil: Globale Vereinbarungen
//
#include <iostream.h>
unsigned testfunktion ( unsigned testzahl ); // Funktionsprototyp
//
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-6
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
//////////////////////////////////////////////////////
// 2. Teil, 2a: eine Anwendung mit Bedarf an Primzahlbestimmung
//
void main ( void )
{
unsigned
zahl, ergebnis;
cout << "Gib bitte eine ganze Zahl ein: " << flush;
cin >> zahl;
ergebnis = testfunktion ( zahl);
if ( ergebnis > 0 )
cout << zahl << " ist eine Primzahl" << endl;
else
cout << zahl << " ist keine Primzahl" << endl;
}
//
//////////////////////////////////////////////////////
///////// Test auf Primzahl : ////////////////////////
// Teil 2b: Funktion für die Primzahlbestimmung
//
unsigned testfunktion ( unsigned testzahl )
{
unsigned
divisor, rest;
divisor = 2;
while ( divisor*divisor <= testzahl )
{
if ( (rest = testzahl % divisor) != 0 )
divisor++;
else
break;
}
return (rest);
}
//
/////////////////////////////////////////////////////////////////////////
Der Abschnitt mit den Funktionsdefinitionen macht praktisch immer den überwiegenden Teil des Programmtexts aus. Beachten Sie dabei die nicht formal begründete sondern an der Wiederverwendbarkeit bestimmter
Teil des Programms orientierte Einteilung in einmalig entwickelte Funktionen und in solche, die in mehreren
Projekten wieder verwendet werden können! Hier könnte sie wie folgt gelten:
ƒ Die Hauptfunktion main ist gewissermaßen einmalig; es ist die hier zu programmierende Anwendung.
ƒ Die Funktion testfunktion zur Primzahlbestimmung hingegen könnte ein (projektspezifischer)
Standardbaustein sein, der nicht nur hier, sondern auch in anderen Programmen, also für andere Anwendungen, zum Einsatz kommt. Ihr ist es "egal", von wo aus sie aufgerufen wird, Hauptsache ist,
das Argument wird dabei richtig formuliert: richtige Anzahl, richtiger Typ der aktuellen Parameter, bei
Feldern und Zeigern: keine absturzgefährlichen Adressen!
Zu b): Programmfluss
Den Programmfluss des fertig übersetzten und gestarteten Programms soll folgendes Bild deutlich machen.
Er zeigt die Sprünge, die die Aufrufe der anwenderdefinierten Funktionen veranlassen. Die Sprünge in Standardfunktionen, die deren Aufrufe in diesem Programm bewirken, sind hier im Interessen besserer Übersicht
nicht dargestellt.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-7
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Sprung in die Funktion
Aufrufen: testfunktion
Start der Funktion
Verarbeitung des Parameters:
Test auf Primzahl
nächste Operation: Wertzuweisung
Rückkehr aus der Funktion
main
Beendigung der Funktion mitreturn
testfunktion
Das Programm wird durch den Aufruf der testfunktion an deren Beginn fortgesetzt: es springt an den
Anfang der Funktion und führt aus, was dort formuliert ist. Mit Erreichen des Befehls return springt der
Programmfluss in die aufrufende Funktion zurück, und zwar mit dem Argument des return - Befehls,
dem Rückgabewert. Die aufrufende Funktion setzt den Programmfluss mit der Operation fort, die dem Funktionsaufruf folgt. In unserem Beispiel ist das in beiden Fällen die Wertzuweisung des Rückgabewerts an eine
Variable der aufrufenden Funktion. Funktionsaufruf und Wertzuweisung bilden hier eine Anweisung. Nach
der Rückkehr des Programms aus der Demofunktion steht der Funktionsaufruf in main für den von der
Demofunktion zurückgegebenen Wert, den sog Rückgabewert.
Zu c): Datenfluss
Die Demofunktion verarbeitet einen Parameter. Er wird im Prototyp und im gleichlautenden Kopf der Funktionsdefinition wie die Deklaration einer Variablen mit Datentyp und Name formuliert und als formaler Parameter bezeichnet:
unsigned testfunktion (unsigned parameter)
Die Klammern mit dem Parameter nennt man das Argument der Funktion. Es kann auch mehrere Parameter
enthalten.
Dem formalen Parameter wird im Aufruf ein Wert übergeben. Einer der Aufrufe des letzten Beispiels lautet
ergebnis = testfunktion ( zahl);
zahl ist eine Variable, die in der aufrufenden Funktion gültig ist. Man nennt sie hier den aktuellen Parameter. Ihr momentaner Wert wird zum Zeitpunkt der Ausführung des Funktionsaufrufs in den formalen Parameter kopiert. Mit diesem Wert startet die testfunktion ihre Ausführung.
Die Ausführung einer aufgerufenen Funktion endet, wenn der Programmfluss die geschweifte Abschlussklammer des Rumpfs der Funktion oder die Operation return erreicht. Falls, wie hier in der Demofunktion,
return die Funktion beendet und ein Argument hat gibt die aufgerufene Funktion der aufrufenden eine Kopie des Wert im Argument von return zurück.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-8
(5) Zuweisung
ergebnis =
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
aktueller
Parameter
Funktionsaufruf
testfunktion (zahl);
(4) Kopie des return - Arguments
(1) Kopie von zahl
unsigned testfunktion (unsigned parameter)
(3) return - Argument
(Rückgabewert)
Funktionsdefinition
formaler
Parameter
(2) Verarbeitung
Dieser Rückgabewert ist in der aufrufenden Funktion nach der Rückkehr der Wert des Funktionsaufrufs
nach dessen Ausführung, so ähnlich, wie der Wert eines Summenausdrucks nach dessen Ausführung die
Summe ist.
6.3 Erweitertes Einführungsbeispiel
In einer anderen Anwendung könnte die Primzahlbestimmung auch mehrmals aufgerufen werden. So könnte eine Anwendung in einer Quasiendlosschleife ständig neue Zahlen einlesen und auf Primzahlzugehörigkeit testen lassen, bis irgendwann eine Zahl eingegeben wird, die vereinbarungsgemäß das Ende der
Schleife zur Folge haben soll, z.B. -1 . Dazu müsste ein neues main formuliert werden, das die unveränderte testfunktion in der Schleife aufruft.
Ein anderer Fall wäre eine Anwendung, die eine Unter- und eine Obergrenze aus der Eingabe liest und alle
Primzahlen bestimmt, die in diesem Bereich liegen. Der Programmfluss dazu lässt sich so darstellen:
Aufrufen: testfunktion
nächste Operation: Wertzuweisung
...
Aufrufen: testfunktion
nächste Operation: Wertzuweisung
Start der Funktion
Verarbeitung des Parameters:
Test auf Primzahl
Beendigung der Funktion mitreturn
...
Aufrufen: testfunktion
nächste Operation: Wertzuweisung
...
main
testfunktion
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-9
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Das Programm zu diesem Beispiel unterscheidet sich nur in der main – Funktion vom ersten. Sein Struktogramm folgt:
6.4
Parameterübergabe: Wertübergabe, Referenzübergabe und C++ - Referenztypen
Man unterscheidet in C bei der Übergabe von Parametern
− Wertparameter: ein Wert der aufrufenden Funktionen geht an eine Variable der aufgerufenen Funktion
− Referenzparameter: eine Adresse der aufrufenden Funktionen geht an einen Zeiger der aufgerufenen
Funktion.
(a) Beispiel zur Wertübergabe ("call by value"):
aufrufende Funktion
// ...
float a, b;
// ...
square_a (a, b); // Funktionsaufruf
/*
übergebene Werte von a und b
sind aktuelle Parameter dieses
Aufrufs. a und b werden aber durch
die aufgerufene Funktion nicht
verändert, sondern ..................
*/
// ...
►
aufgerufene Funktion
//
// Funktionsdefinition
//
void square_a (float c, float d)
{
c = c * c;
d = d * d;
/*
......... nur die formalen Parameter
c und d in dieser Funktion
werden quadriert!
*/
}
Die Variablen a und b gelten nur in der aufrufenden Funktion; diese übergibt sie im Aufruf als aktuelle
Parameter.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-10
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Die momentanen Werte zur Zeit des Aufrufs werden in die formalen Parameter c und d kopiert. Die
aufgerufene Funktion bearbeit nur die formalen Parameter. Die aktuellen Parameter der aufrufenden
Funktion werden dadurch nicht verändert.
(b) Beispiel zur Referenzübergabe ("call by reference"):
aufrufende Funktion
// ...
float a, b;
// ...
square_b(&a, &b); // Funktionsaufruf
/* aktuelle Adressen von a und b
gehen an formale Zeiger in square_b.
aufgerufene Funktion
//
// Funktionsdefinition
//
void square_b (float *c, float *d)
{
/* c und d übernehmen Adressen */
*c = *c * *c;
*d = *d * *d;
Nach Rückkehr aus der Funktion
sind a und b hier quadriert.
// zeigt momentan auf a
// zeigt momentan auf b
/* c und d quadrieren die am
Zeigeziel gespeicherten Werte.
*/
*/
// ...
}
Dies soll illustrieren, wie statt der Werte der Variablen hier die Variablenadressen übergeben werden.
► Die Quadrate stehen nach Beendigung der Funktion square_b in den Variablen a und b der aufrufenden Funktion.
► square_b hat keine eigenen Werte quadriert, sondern seine Zeiger dafür verwendet, auf Werte der
aufrufenden Funktion zu zeigen und diese damit zu bearbeiten.
In der praktischen Programmierung ist die Referenzübergabe schon deshalb unverzichtbar, weil es ja
nur einen Rückgabewert einer Funktion gibt. Falls eine Funktion mehrere Werte bearbeitet zurückgeben soll, werden Referenzparameter nötig.
Grundsätzlich besteht beim Aufruf von Funktionen das Problem, dass die aufrufende Funktion ihre aktuellen
Parameter
• in der richtigen Anzahl
• in der richtigen Reihenfolge
• mit den richtigen Typen
an die aufgerufene Funktion übergeben muss, damit sie zu den formalen Parametern im Argument passen.
Nur dann ist ein Funktionsaufruf korrekt.
(c) Verwendung von Referenztypen (nur in C++ erlaubt):
Weil C eine Untermenge von C++ ist, gelten die obigen Arten der Parameterübergabe ohne Änderung auch
in C++ . Darüber hinaus bietet aber C++ alternativ zur Referenzübergabe noch folgende dritte Möglichkeit
an:
► Der Funktionsaufruf wird mit einer ganz normalen Wertübergabe programmiert.
► Die formalen Parameter der aufgerufenen Funktion sind sogenannte Referenztypen. Das bedeutet
praktisch: das formale Argument der Funktionsdefinition enthält eine Deklaration der formalen
Parameter als Referenztypen. Die dazu nötige Syntax zeigt das folgende Beispiel.
► Der Funktionsrumpf der Funktionsdefinition behandelt die formalen Parameter, als seien sie
Wertparameter. Es gibt also keine Zeigeroperationen.
Beispiel:
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-11
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
aufrufende Funktion
//
// dies geht nur in C++:
//
void square_c (float & c, float & d);
aufgerufene Funktion
//
// dies geht nur in C++
//
// Funktionsdefinition
//
void square_c (float & c, float & d)
{
c = c*c;
d = d*d;
}
//
//
//
//
//
///////////////////////////////////
void main (void)
{
float a, b;
cin >> a >> b;
square_c (a, b); // Funktionsaufruf
cout<< "a=" <<a<< " b=" <<b<<endl;
}
//
//////////////////////////////////////
Sie sehen: nur die beiden Zeilen mit Funktionsprototyp und mit dem Kopf der Funktionsdefinition zeigen an,
dass es sich hier um Parameter mit Referenztyp handelt. Alles andere sieht aus wie die Verarbeitung von
Werten. Dennoch sind nach Rückkehr aus square_c die Werte von a und b quadriert.
6.5
Modular aufgebaute Programme
Module eines Programms sind Dateien mit Programmcode, die zusammen das ganze Programm ausmachen. In gewisser Weise nutzen die bisherigen Beispiele und Übungen Module in Form der Standardbibliotheken schon von Anfang an: die externen Objektmodule der Standardfunktionen aus der ANSI-Bibliothek.
In Abschnitt 1.3.2 "Der Ablauf einer Programmübersetzung" ist der Datenfluss bei einem Übersetzungsvorgang dargestellt. Sie erkennen dort, dass der Linker diese schon übersetzten Objektfunktionen nach der
Compilierung in das ausführbare Programm einfügt. Das Anwendungsprogramm ruft die externen Funktionen nur auf; es enthält nicht deren Programmcode in C oder C++ .
In industriellen Softwareprojekten ist es in der Regel nötig, die Arbeit an einem Programm auf mehrere Personen oder Gruppen von Personen zu verteilen. Es versteht sich, dass ein solches Programm aus Funktionen besteht. Es ist aber dazu außerdem notwendig, dass die Funktionen oder Gruppen von Funktionen in
jeweils eigenen Dateien gespeichert werden. Dateien mit Quellcode von Funktionen, die Beiträge zu einem
Programm darstellen, heißen Module. In C/C++ sind einzelne Module durchaus einzeln compilierbar, d.h.
ohne main und die anderen zum Programm gehörenden Funktionen. Im Linkprozess aber, also bei der
Erzeugung eines ausführbaren Programms, benötigt der Linker alle aufrufenden und aufgerufenen Funktionen.
Ein Fließschema zur Erstellung eines modularen Programms stellt das folgende Bild dar.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-12
Struktogrammentwurf
zu main
main Header
main - Funktion
Editor
Editor
Anwendung.h
Anwendung.cpp
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Struktogrammentwürfe
Entwürfe
weiterer Funktionen
Struktogrammentwürfe
Horner-Funktionen
weiterer Funktionen
Funktionen
Horner-Funktionen
weitere Funktionen
Editor
Präprozessor
Compiler
Anwendung.obj
HornerFunktionen.c
Horner-Funktionen.c
FunktionX.cpp
Systemheader
Systemheader
Systemheader
Systemheader
Systemheader
Andere Header
Präprozessor
Compiler
HornerFunktionen.c
FunktionenX.obj
FunktionX.obj
Funktionen
der Systembibliotheken
Funktionen
Funktionen
anderer
Funktionen
anderer
Bibliotheken
anderer
Bibliotheken
Bibliotheken
Linker
Ausführbare Programmdatei
Anwendung.exe
Beispiele zur Erstellung von Programmen, die aus mehreren Quellcodedateien bestehen, werden in der
Vorlesung vorgestellt.
6.6
Speicherklassen: Gültigkeitsbereich und Lebensdauer von Variablen
Variablen, formale Funktionsparameter und andere Speicherobjekte eines C-Programms zeichnen sich
durch zwei Gültigkeitsbereiche aus:
∗
∗
einen im Programmcode
einen über der Laufzeit
Zum Programmcode:
Wenn ein Speicherobjekt lokal vereinbart ist, also innerhalb einer Funktion, dann gilt es genau in dieser Funktion, und außerhalb nicht. - Formale Funktionsparameter und Variablen einer Funktion wie in
den bisherigen Beispielen haben diese lokale Gültigkeit.
Bei globaler Vereinbarung, also im Programmtext oberhalb der Funktionsdefinitionen, gilt ein Speicherobjekt überall, d.h. innerhalb jeder Funktion des Programms. Jede Funktion kann den momenta-
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-13
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
nen Wert verändern oder in ihren Ausdrücken verarbeiten. Ersteres stellt ein ernstes Problem bei der
Eingrenzung von Fehlerursachen dar, weswegen es vermieden werden sollte, globale Speicherobjekte zu verwenden. Nur unter sehr speziellen Umständen sind globale Variable oder andere Speicherobjekte gerechtfertigt. Solche Umstände kamen im bisherigen Kolleg nicht vor.
Zur Laufzeit:
Ein lokales Speicherobjekt beginnt zu gelten, wenn die Funktion aufgerufen ist, in der es vereinbart
ist. Es hört auf zu gelten, nachdem die Funktion in die aufrufenden zurückkehrt.
Globale Speicherobjekte gelten solange, wie das Programm läuft.
6.6.1
Globale Variablen und externe Deklaration
Zum Gebrauch globaler Variabler zunächst ein wichtiger Hinweis:
Globale Variablen stehen allen Funktionen eines Programms zur Verfügung. Dadurch wird es besonders in größeren Programmen schwer bis praktisch unmöglich, Änderung der Werte nachzuvollziehen. Fehlersuche und funktionelle Erweiterungen werden außerordentlich schwer oder praktisch unmöglich. "Anfänger" benutzen gern globale Variable, um Funktionsparameter zu umgehen. Das ist ein
extrem schlechter und unproduktiver Programmierstil!
Globale Variable sollen nur in begründeten Ausnahmefällen der "höheren" Programmierung, z.B. in
der Treiberentwicklung oder der Systemprogrammierung, oder als Ersatz für bestimmte globale Makros in C++ benutzt werden.
Die folgenden Beispiele sind daher nicht als Vorbild zu betrachten! Sie sollen vielmehr den Gebrauch
globaler Variabler in einfach verständlicher Weise zeigen.
Beispiel 1.: Die folgende Datei tausch.c enthält ein Programm zum Vertauschen der Werte zweier Variablen mit den Funktionen main und tausch:
#include <stdio.h>
int zahl_1, zahl_2; /* globale Variablen */
void tausch (void);
void main (void)
{
printf ("\nGib zwei ganze Zahlen im int-Bereich ein: ");
scanf ("%d %d", &zahl_1, &zahl_2);
tausch ();
printf ("\n%d %d", zahl_1, zahl_2);
}
void tausch (void)
/* auch hier gelten zahl_1 und zahl_2 !*/
{
int hilf;
hilf = zahl_1;
zahl_1 = zahl_2;
zahl_2 = hilf;
}
Die Variablen zahl_1 und zahl_2 sind global, d.h. sie gelten in allen Funktionen des Programms während
der gesamten Laufdauer des Programms.
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-14
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Die Variable hilf ist lokal zur Funktion tausch. Sie gilt nur innerhalb von tausch und nur, solange
tausch läuft. Sie gilt also nicht vor dem Aufruf von tausch und nach seiner Rückkehr in die aufrufende
Funktion, hier: in main. Konkret heißt das, dass das Maschinenprogramm den Speicherplatz für hilf anderweitig nutzt, während das Programm tausch nicht durchläuft.
Es ist besonders bei größeren Programmen möglich und sinnvoll, die Funktionen eines Programms auf
mehrere Dateien zu verteilen. Dann gelten globale Variable, wie oben zahl_1 und zahl_2, ohne weiteres
nur für die Funktionen, die innerhalb derselben Datei definiert sind wie diese globalen Variablen.
Beispiel 2: Die erste Datei m_tausch.c enthält das Hauptprogramm und die globalen Variablen.
/* m_tausch - Anfang: *********************************************/
/*
**/
void tausch (void);
int zahl_1, zahl_2; /* globale Variablen */
void main (void)
{
printf ("\nGib zwei ganze Zahlen im int-Bereich ein: ");
scanf ("%d %d", &zahl_1, &zahl_2);
tausch ();
printf ("\n%d %d", zahl_1, zahl_2);
}
/*
**/
/* m_tausch – Ende ***********************************************/
Die zweite Datei t_tausch.c enthält die Funktion tausch.
/* t_tausch - Anfang: **********************************************/
/*
**/
extern int zahl_1, zahl_2; /* externe Deklaration: die globalen
Variablen sind in einer getrennt
übersetzten Programmdatei angelegt
sollen aber auch hier gelten
! **/
void tausch (void)
{
int hilf;
hilf = zahl_1;
zahl_1 = zahl_2;
zahl_2 = hilf;
}
/*
**/
/* t-tausch - Ende ************************************************/
Der Modifizierer extern bewirkt, dass beim Compilieren von t_tausch.c kein Speicherplatz reserviert
wird, sondern nur ein Verweis auf die im nachfolgenden Link-Prozess gelieferte Adresse eines extern reservierten Speicherplatzes für zwei int - Variablen hergestellt wird. Die Variablen haben in diesem Modul die
Speicherklasse extern. Folglich können extern deklarierte Variable auch nicht initialisiert werden.
► Ohne die Zeile extern int zahl_1, zahl_2;
würde der Compilierprozess von t_tausch.c mit einer Fehlermeldung abbrechen
("... unbekannnte Symbole zahl_1, zahl_2 ..."),
da die in der Funktion benutzten Variablen dort nicht vereinbart sind.
► Mit
int zahl_1, zahl_2;
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-15
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
ohne extern würde doppelte Speicherreservierung mit denselben Symbolen (Namen) in ein und
demselben Gültigkeitsbereich angefordert werden. Das ist bei getrennter Übersetzung mehrerer Programmdateien für den Compiler noch kein Problem, aber für den Linker, der die Speicherreservierung
steuert.
Bei mehreren getrennt übersetzten Programmdateien muss also genau eine Programmdatei die Deklaration
betrachteter globaler Variabler ohne extern ausführen. Alle anderen Programmdateien desselben Programms müssen dieselben globalen Variablen für ihren Teil mit extern deklarieren.
6.6.2
Lokale Variablen mit automatischer oder statischer Gültigkeit
Die Deklaration von Variablen innerhalb von Funktionen bewirkt implizit, dass diese nur im Programmbereich der Funktion während der Laufdauer der Funktion gültig sind. Die Reservierung der Speicherplätze
erfolgt auf dem Stapelspeicher (Stack) des laufenden Programms.
Die Speicherklasse heißt auto und steht für "automatische Speicherreservierung". Sie kann auch explizit
durch Voranstellen des Modifizierers auto vor den Datentyp einer zu deklarierenden Variablen gefordert
werden:
Die Deklaration
...
void tausch (void)
{
int hilf;
...
}
bewirkt das selbe wie
...
void tausch (void)
{
auto int hilf;
...
}
Die Variable hilf gilt bei nach Rückkehr in die aufrufende Funktion nicht mehr; ihr Speicherplatz ist vom
Stack "abgeräumt" und wird beim nächsten Aufruf einer Funktion dieser zur Verfügung gestellt. Der Stack, in
dem die auto - Variablen stehen, wird also nach Bedarf zur Programmlaufzeit umgewidmet.
Soll eine Variable einerseits zwar lokal zu einer Funktion gelten, andererseits aber auch nach Rückkehr in
die aufrufende Funktion "weiterleben", muss sie static deklariert werden. Mit der Speicherklasse static
wird die Gültigkeitsdauer der Variablen über die Laufdauer der Funktion hinaus auf die Laufdauer der ganzen Programms ausgedehnt. Ihre Adresse wird nach der Deklaration nicht mehr freigegeben. Dort gespeicherte Werte bleiben garantiert erhalten.
Notwendig ist das immer dann, wenn eine Funktion über mehrere Aufrufe lokale Variable schrittweise verändern soll. Die Variablen müssen dann nämlich bei jedem neuen Aufruf mit dem Wert des vorigen Aufrufs zur
Verfügung stehen. Ein Beispiel soll das erläutern.
Anforderung: Eine Funktion soll einen Wert auf die Weise hoch zählen, dass jeder neue Funktionsaufruf
ein Inkrementieren des bis dahin erreichten Werts zur Folge hat. Dazu gehört auch die Möglichkeit, zu
Beginn des Zählprozesses einen Anfangswert zu definieren.
Nicht notwendig nur im Zusammenhang mit der Speicherklasse static, aber als sehr effektives
allgemeines Strukturkonzept, das schon in die objektorientierte Programmierung voraus weist, soll
in diesem Beispiel eine Einteilung in Anwendung, öffentliches Anwendungsinterface und geschützte bzw. hier nur versteckte Datenmanipulation unterschieden werden:
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-16
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Anwendung:
beliebig gestaltbar
Funktion main
Funktion init_counter
(Zähler initialisieren
Funktion call_counter
(Zähleraufruf)
Funktion counter : Zählwert initialisieren oder
erhöhen
Funktionsinterface für
Anwendungen:
reglementierter Zugang
geschützte interne
Datenmanipulation
Diese Funktionen init_counter , call_counter und counter könnte man sich als Teil
einer wiederverwendbaren Projektbibliothek vorstellen. Irgendwelche Anwendungen erteilen über
init_counter den Auftrag zur Initialisierung des Zählwerts oder mit call_counter den Auftrag zum Hochzählen. Die vor der Anwendung versteckte Funktion counter führt die Manipulationen des Zählerstands geschützt vor direktem Zugriff der jeweiligen Anwendungen aus.
Das folgende Sequenzdiagramm zeigt die Lebensdauer der Zählvariablen, die nur in der eigentlichen
Zählfunktion gilt, in einer beispielhaften Folge von Aufrufen.
Funktion main
(eine Anwendung)
Funktion
init_counter
Funktion
call_counter
Funktion
counter
Lebensdauer der static - deklarierten Zählvariablen
Zeit
Das Programm gliedert sich
a) in eine wieder verwendbare Programmbibliothek der drei Zählfunktionen:
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-17
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
/*
* Modul 1: counter.c *********************************************
*/
#define TRUE 1u
long
counter (unsigned initialize, long initvalue)
{
static long
value;
/* Zählerstand mit Vorgeschichte */
if (initialize == TRUE)
/* Init ! */
value = initvalue;
else
/* Increment! */
++value;
return (value);
/* Rückgabe des Zählerstands */
}
/*
* Modul 2: init_counter.c *****************************************
*/
long
counter (unsigned initialize, long initvalue); /* Prototyp der
* internen Zählfunktion
*/
#define INIT 1u
void
init_counter (long startvalue)
{
counter(INIT, startvalue );
/* Aufruf im Modus Init
*
* Rückgabewert wird ignoriert */
}
/*******************************************************************/
/*
* Modul 3: call_counter.c ****************************************
*/
long
counter (unsigned initialize, long initvalue); /* Prototyp der
* internen Zählfunktion
*/
#define COUNT 0u
long
call_counter (void)
{
long
x = 0L;
return ( counter(COUNT, x) ); /* Aufruf im Modus Zählen
* Parameter 2 hier ohne Bedeutung
* Rückgabewert von counter ist
* neuer Zählerstand und wird an
* die Anwendung durchgereicht
*
*
*
*
*/
}
/*******************************************************************/
b) für jede neue Aufgabenstellung mit Bedarf nach den obigen Funktionen neu formulierte Anwendung,
hier nur in Form eines sehr kleinen Beispiels:
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-18
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
/** Anwendung: Modul Application.c *********************************/
/**/
#include <windows.h>
#include <iostream.h>
#include <conio.h>
#include "counterfunctions.h" /* enthält in dieser Anwendung benötigte
* Deklarationen des Zählers
*/
/******************************************************************/
void main (void)
{
long
a, last;
int
junk;
cout << "Geben Sie einen Startwert ein: " << flush;
cin >> a;
init_counter (a);
// <---- hier entsteht value mit Startwert a
while ( ! _kbhit() )
{
last = call_counter (); // <--- und hier wird value erhöht
cout << " " << last << flush;
Sleep (1000);
}
junk = _getch();
last = call_counter ();
cout << endl << "letzter Wert: " << last << endl;
}
// hier endet das Leben von value
/******************************************************************/
Die Headerdatei enthält als "Formalbeschreibung" der Bibliothek die Prototypen der "öffentlichen" Inkrementierfunktionen:
/*
* counterfunctions.h **********************************************
*/
void
init_counter (long); // Anwendung setzt Startwert
long
call_counter (void); // Anwendung fordert an: 1x Zähler erhöhen
/*
*******************************************************************
*/
Nach einem ähnlichen Muster "lebt" übrigens die interne Zufallsvariable des Standard-C-Zufallsgenerators
rand: der Aufruf von srand initialisiert sie, und jeder neue Aufruf von rand greift auf sie zurück, um mit ihr
den nächsten Pseudozufallswert zu erzeugen.
6.6.3
Registerbasierte Variablen
Lokal gültige Variable mit automatischer Lebensdauer lassen sich als Registervariablen deklarieren: durch
Voranstellen des Modifizierers register wird der Compiler aufgefordert, die Variable möglichst in einem
CPU - Register zu halten, solange die darauf zugreifende Funktion ausgeführt wird.
Fortgesetztes Holen des Werts aus dem Speicher und Schreiben dorthin kann so vermieden werden. Die
Ausführungsgeschwindigkeit des Programms steigt damit. Die "Befolgung" der Modifikation durch den Com-
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-19
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
piler ist maschinen- und compilerabhängig und hängt in erster Linie von der Anzahl verfügbarer Register einer CPU, in zweiter von der "Intelligenz" eines Compilers ab.
Sinnvoll ist diese Speicherklasse z.B. für Laufvariablen von Wiederholschleifen und für Variablen, deren
Werte iterativ verändert werden.
6.6.4
Veränderbarkeit von Variablen
(a) Einschränkung auf nur lesende Verarbeitung ("Read Only", Schreibschutz)
Der Modifizierer const sagt dem Compiler, dass eine Variable oder ein formaler Funktionsparameter nicht
verändert werden darf.
Als Beispiel betrachte man den Prototyp einer Funktion statistik, die eine gleitende Berechnung von Mittelwert und Standardabweichung für einen neuen Messwert ausführt:
void statistik (const int messwert,
double *mittelwert, double *standardabweichung);
Der Modifizierer const in der Deklaration des Parameters messwert verhindert, dass in der Funktion
statistik der Wert dieses Parameters vom Programm verändert wird.
Allgemein führt jeder Versuch eines Programms, als const deklarierte Variablen oder Funktionsparameter
innerhalb ihres Gültigkeitsbereichs zu verändern, zu einer Fehlermeldung und zum Abbruch des Compilierprozesses. Eine aufrufende Funktion kann selbstverständlich ihren aktuellen Parameter, der an den formalen Parameter übergeben wird, verändern. Die Forderung nach Konstanz des Werts gilt nur in der aufgerufenen Funktion, da nur dort der Formalparameter mit der const - Modifikation gültig ist.
Beachten Sie aber:
Falls Werte formaler const – Parameter oder const -Variablen als aktuelle Parameter an andere
Funktionen weitergegeben werden, können diese sehr wohl für Änderungen sorgen. Besonders tückisch sind dabei Referenzparameter. Betrachten Sie dazu folgendes Beispiel:
/*******************************************************************/
/**/
#include <stdio.h>
int take (const int that);
void main (void)
{
int what, whatsnew;
what = 8;
printf ("what=%d\n", what);
whatsnew = take (what);
printf ("take returns what=%d\n", whatsnew);
}
int take (const int that)
{
//
that= 9;
// ohne Kommentar Fehler "keine Zuweisung erlaubt"
scanf ("%d", &that);
return (that);
}
/**/
/*******************************************************************/
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-20
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Die auskommentierte Zuweisung an that würde bei der Übersetzung (MSVC++ 6.0) folgendes bewirken:
... : error C2166: L-Wert gibt ein konstantes Objekt an
In der oben formulierten Weise liefert das Programm aber folgende Ausgabe:
Die von take aufgerufene Funktion scanf hat per Zeigerreferenz den Wert des
const int deklarierten that durch die Eingabe ersetzt!
Anders wird es, wenn das Programm in C++ formuliert und übersetzt wird. Das folgende Programm verursacht in der Eingabeoperation cin >> that; einen Übersetzungsfehler:
//////////////////////////////////////////////////////////////////////
//
#include <iostream.h>
int take (const int that);
void main (void)
{
int what, whatsnew;
what = 8;
cout << "what = " << what << endl;
whatsnew = take (what);
cout << "whatsnew = " << whatsnew << endl;
}
int take (const int that)
{
//
that= 9;
cout << "Eingabe: ";
cin >> that;
// hier ist der Fehler
return (that);
}
//
///////////////////////////////////////////////////////////////////////
...: error C2679: Binaerer Operator '>>' : Kein Operator definiert, der einen
rechtsseitigen Operator vom Typ 'const int' akzeptiert (oder keine geeignete
Konvertierung moeglich)
(b) Ausdrückliche Kennzeichnung von Veränderlichkeit
Informatik für Ingenieure
Vorlesungsskript
Sommersemester 2006
Seite 6-21
Fachhochschule Braunschweig/Wolfenbüttel
Fachbereich Elektrotechnik
Prof. Dr.-Ing. M. Haas
Das "Gegenteil" zu const stellt der Modifizierer volatile dar. "Volatile" ist englisch und heißt "flüchtig"
oder "flugtauglich". Durch ihn kann ein Compiler daran gehindert werden, scheinbar überflüssige Variablen
im Zielcode zu elimieren.
Wichtig ist dies in Fällen, in denen sich Variable auf eine vom Compiler nicht erkennbare Art verändern, z.B.
bei der Behandlung von Programmunterbrechungen in sog. Interrupt-Serviceroutinen. Sie gehören als Funktion zwar in den Speicheradressraum eines Programms mit main() und weiteren Funktionen. Keine der
Funktionen ruft aber die Servicefunktion auf, sondern diese wird durch Eintreten eines externen Interruptsignals von einer Hardwarekomponente quasi aufgerufen. Die Serviceroutine kann den weiteren Ablauf der
"normalen" Funktionen des Programms über globale Variable beeinflussen.
Herunterladen