 |
Programmieren in C
von Prof. Dr. Rainer Thomas |
2. Grundlagen
2.1 Aufbau eines C-Programms
Programmbeispiel in C
/* -------------------------------------------------------------- */
/* Programm ZLAENGE */
/* -------------------------------------------------------------- */
/* Ermittlung der Laenge von Eingabezeilen */
/* Programmende bei Eingabe von End-of-File zu Beginn einer Zeile */
/* -------------------------------------------------------------- */
#include <stdio.h> /* Preprozessoranweisung */
#define NL '\n' /* Preprozessoranweisung */
int cZeich; /* Variablendefinition */
int ZeileEin(int); /* Funktionsdeklaration */
void main(void) /* Funktionsdefinition */
{
int iLaenge;
while ((cZeich=getchar())!=EOF)
{ iLaenge=ZeileEin(cZeich);
printf("Laenge der Zeile = %-d\n",iLaenge);
}
}
int ZeileEin(int iErstzeich) /* Funktionsdefinition */
/* Ermittlung der Laenge einer Zeile durch
Einlesen der restlichen Zeichen der Zeile.
Zeilenabschluss mit NL.
Parameter erstzeich ist erstes Zeichen
der Zeile */
{
int iZeich, iZaehl;
iZaehl=0;
for (iZeich=iErstzeich; iZeich!=NL; iZaehl++,iZeich=getchar());
return (iZaehl);
}
Struktur eines C-Programm-Moduls
-
Ein C-Programm besteht aus einem oder mehreren getrennt zu übersetzenden
- Modulen.
-
Ein C-Programm-Modul besteht aus einer beliebigen Folge von
|
|
| - Typdefinitionen |
| - Variablendefinitionen |
| - Funktionsdefinitionen |
| - Preprozessoranweisungen |

-
Vereinbarung :
-
Deklaration :
-
Festlegung von Namen und Eigenschaften (Typ, Größe, Speicherklasse
usw) eines Objekts
-
Definition :
-
Wie Deklaration, zusätzlich Reservierung von Speicher- platz (-->
Erzeugung eines konkreten Objekts) (Ausnahme : Typdefinition, entspricht
Deklaration)
-
Abschluß jeder Vereinbarung - mit Ausnahme der Funktionsdefinition
- mit einem Semikolon (;)
-
Die Anweisungen, die die vom Programm auszuführenden Aktionen
beschrei- ben, sind im Rumpf der Funktionsdefinitionen (Funktionsrumpf)
enthalten. Ein Funktionsrumpf ( = Block) ist in geschweifte Klammern {
und } ein- zuschließen.
Abschluß jeder Anweisung - außer der Verbundanweisung
- mit einem Semikolon (;)
Neben Anweisungen kann ein Block auch Deklarationen, Typdefinitionen,
Variablendefinitionen und Preprozessoranweisungen enthalten, jedoch keine
Funktionsdefinitionen --> nur eine Funktionsdefinitionsebene, keine
Funktionsschachtelung.
-
Es existiert kein "Hauptprogramm" im Sinne von PASCAL.
Aber genau ein Modul eines C-Programms muß die Definition
einer Funktion mit dem Namen main enthalten. (--> Start des
Programms)
-
Preprozessoranweisung :
-
jede Zeile, die mit dem Zeichen # beginnt Spezielle Schlüsselworte
und Syntax, Bearbeitung durch den Preprozessor
-
Kommentar :
-
jede Zeichenfolge zwischen /* und */; kann an jeder Stelle,
an der ein Blank, ein Tabulatorzeichen oder ein Zeilenwechsel stehen darf,
stehen;
-
Kein festes Darstellungsformat (--> formatfreie Darstellung)
2.2 Bezeichner und reservierte Worte
Bezeichner und Wortsysmbole in C
-
Bezeichner (Namen, identifier)
-
dienen zur Bezeichnung von Konstanten, Makros, Typen, Variablen und Funktionen
-
Syntax :
-

-
Groß- und Klein-Buchstaben sind verschieden
-
ANSI-C sieht vor, daß
-
bei Bezeichnern mit rein internem Bezug mindestens 31 Zeichen
-
und bei Bezeichnern mit externem Bezug (Linker !) mindestens 6 Zeichen
signifikant sind
-
Beispiele :
a
|
_marke
|
wert14
|
count_st
|
b_1_2_d
|
farbe
|
Farbe
|
FARBE
|
-
Wortsymbole (reservierte Worte, keywords)
auto
|
enum *)
|
signed *)
|
break
|
extern
|
sizeof
|
case
|
float
|
static
|
char
|
for
|
struct
|
const *)
|
goto
|
switch
|
continue
|
if
|
typedef
|
default
|
int
|
union
|
do
|
long
|
unsigned
|
double
|
register
|
void *)
|
else
|
return
|
volatile *)
|
entry **)
|
short
|
while
|
*) Erweiterung in ANSI-C gegenüber KuR-C
**) in KuR-C reserviert, aber nicht verwendet, in ANSI-C nicht mehr
reserviert
In einigen C-Systemen existieren weitere Wortsymbole, z.B.
asm
|
pascal
|
cdecl
|
near
|
far
|
Empfehlungen zur Vergabe von Namen (Bezeichnern)
-
Vorbemerkungen :
-
Namen sollen selbstdokumentierend sein, d.h. die Bedeutung und Funktionalität
eines Elements (Konstante, Makro, Variable, Funktion, Typ) soll bereits
möglichst weitgehend aus seinem Namen erkennbar sein.
-
Benutzerdefinierte Namen sollen nicht mit einem Unterstrich (Underscore)
beginnen, da derartige Namen für implementierungsspezifische Verwendungen
vorgesehen sind.
-
Konstante und Makros
Namen für Konstante und Makros
-
bestehen ganz aus Großbuchstaben
-
zusätzlich können sie Ziffern und den Unterstrich enthalten
-
Beispiele :
EOF
PATH_LEN
-
Typen
Namen für selbstdefinierte Datentypen
-
Funktionen
Funktionsnamen
-
beginnen mit einem Großbuchstaben
-
bestehen aus Groß- u. Kleinbuchstaben (u.U. auch Ziffern)
-
Großbuchstaben innerhalb des Namens dienen zur Gliederung
-
enthalten i.a. keinen Unterstrich
-
Beispiel :
int GetName(/* øøø */);
-
Anmerkung :
Die Namen der Standard-Bibliotheksfunktionen bestehen nur aus Kleinbuchstaben.
-
Variable
Variablennamen bestehen aus einem Namens-Präfix und einem
Namens-Hauptteil
(--> Ungarische Namenskonvention).
2.3 Datentypen (Überblick und einfache Typen)
-
Datentyp void:
-
Datentyp ohne Werte
-
eingeführt in ANSI-C
-
Typ für Funktionen ohne Funktionswert
-
Objekttyp der geberischen Pointer (void *)
-
Kennzeichnung einer leeren Parameterliste von Funktionen
-
in logischer Datentyp existiert nicht !
Nachbildung durch den Datentyp int :
| 0 |
entspricht |
FALSE |
| < > 0 |
entspricht |
TRUE |
Arithmetische Datentypen in C
-
Arithmetische Grund-Datentypen
-
ganzzahlige Grundtypen
| char |
Zeichen (meist Byte) |
| int |
ganze Zahl (konegativ)
(natürliche Maschinenwortlänge |
-
Gleitpunkt-Grundtypen
| float |
Gleitpunktzahlen einfacher Genauigkeit |
| double |
Gleitpunktzahlen doppelter Genauigkeit
(kann mit float zusammenfallen) |
-
Modifizierte arithmetische Datentypen
| short int |
= |
short*) |
| long int |
= |
long*) |
|
} |
Unterscheidung durchDarstellungslänge
(Interpretation immer als konegtive Zahl) |
-
*) short oder long können mit int zusammenfallen
| unsigned int |
= |
unsigned |
| unsigned short int |
= |
unsigned short |
| unsigned long int |
= |
unsigned long |
|
} |
gleiche Darstellungslänge wie int bzw. short bzw. long
Interpretation immer als positive Zahl,
Modulo-Arithmetik -> kein Überlauf ! |
| unsigned char |
| signed char |
| long double **) |
|
} |
in ANSI-C neu eingeführt |
-
**) Gleitpunktzahl erweiterter Genauigkeit (kann mit double zusammenfallen)
-
Darstellungslängen
-
die Darstellungslängen der verschiedenen Datentypen sind implementierungsabhängig
-
Beispiel : Darstellungslängen in TURBO-C (MS-DOS)
| char |
1 Byte |
| short / int |
2 Byte |
| long |
4 Byte |
| float |
4 Byte |
| double |
8 Byte |
| long double |
10 Byte |
|
} |
IEEE-Formate |
Wertebereiche der arithmetischen Datentypen in C
-
Die darstellbaren Wertebereiche der verschiedenen Datentypen hängen
von der jeweiligen Darstellungslänge und dem Darstellungsformat
ab. -> implementierungsabhängig
-
Zur Erhöhung der Portabilität sieht ANSI-C die Definition
symbolischer
Konstanten für die Grenzwerte der darstellbaren Wertebereiche
vor.
Diese Konstanten sind in den Header-Dateien
-
<limits.h> (für ganzzahlige Datentypen) und
-
<float.h> (für Gleitpunkt-Typen)
definiert.
Gleichzeitig werden in ANSI-C Minimalwerte für die einzelnen
Grenzwerte vorgeschrieben.
-
Die wichtigsten in <limits.h> definierten Konstanten :
CHAR_BIT Darstellungslänge des Typs char in Bits (>= 8)
SCHAR_MAX Maximalwert des Typs signed char (>= 127)
SCHAR_MIN Minimalwert des Typs signed char (<=-127)
INT_MAX Maximalwert des Typs int (>= 32767)
INT_MIN Minimalwert des Typs int (<=-32767)
LONG_MAX Maximalwert des Typs long int (>= 2147483647)
LONG_MIN Minimalwert des Typs long int (<=-2147483647)
SHRT_MAX Maximalwert des Typs short int (>= 32767)
SHRT_MIN Minimalwert des Typs short int (<=-32767)
UCHAR_MAX Maximalwert des Typs unsigned char (>= 255)
UINT_MAX Maximalwert des Typs unsigned int (>= 65535)
ULONG_MAX Maximalwert des Typs unsigned long int (>= 4294967295)
USHRT_MAX Maximalwert des Typs unsigned short int (>= 65535)
CHAR_MAX Maximalwert des Typs char (= UCHAR_MAX b.
SCHAR_MAX)
CHAR_MIN Minimalwert des Typs char (= 0 bzw.
SCHAR_MIN)
Die wichtigsten in <float.h> definierten Konstanten :
FLT_MAX Maximalwert (normalisiert) des Typs float (>= 1E+37)
FLT_MIN min. pos. Wert (normalisiert) des Typs float (<= 1E-37)
DBL_MAX Maximalwert (normaliisert) des Typs double (>= 1E+37)
DBL_MIN min. pos. Wert (normalisiert) des Typs double (<= 1E-37)
LDBL_MAX Maximalwert (normalisiert) des Typs long double (>= 1E+37)
LDBL_MIN min. pos. Wert (normalis.) des Typs long double (<= 1E-37)
FLT_EPSILON minimale relative Genauigkeit (Typ float) (<= 1E-5)
DBL_EPSILON minimale relative Genauigkeit (Typ double) (<= 1E-9)
LDBL_EPSILON minimale rel. Genauigkeit (Typ long double) (<= 1E-9)
FLT_DIG Anz. genauer Dezimalstellen (float) (>= 6)
DBL_DIG Anz. genauer Dezimalstellen (double) (>= 10)
LDBL_DIG Anz. genauer Dezimalstellen (long double) (>= 10)
FLT_RADIX Basis der (internen) Exponentialdarstellung (>= 2)
FLT_MANT_DIG Anz. der Mantissenstellen (int. Darst.) (float)
DBL_MANT_DIG Anz. der Mantissenstellen (int. Darst.) (double)
LDBL_MANT_DIG Anz. der Mantissenstellen (int. Darst.) (long double)
FLT_MAX_EXP Max. Exponent (Basis der int. Darstellung) (float)
DBL_MAX_EXP Max. Exponent (Basis der int. Darstellung) (double)
LDBL_MAX_EXP Max. Exponent (Basis der int. Darstellung) (long double)
Beispiel : TURBO-C
-
Einige Ganzzahl-Grenzwerte (in <limits.h> definiert) :
CHAR_BIT = 8
SCHAR_MIN = -128 SCHAR_MAX = 127
INT_MIN = -32768 INT_MAX = 32767
LONG_MIN = -2147483648 LONG_MAX = 2147483647
SHRT_MIN = -32768 SHRT_MAX = 32767
UCHAR_MAX = 255
UINT_MAX = 65535
ULONG_MAX = 4294967295
USHRT_MAX = 65535
CHAR_MIN = 0 CHAR_MAX = 255
Einige Gleitpunktzahl-Grenzwerte (in <float.h> definiert)
:
FLT_RADIX = 2
Datentyp float :
FLT_MAX_EXP = 128
FLT_MANT_DIG = 24
FLT_MIN = 1.17549e-38 FLT_MAX = 3.40282e+38
FLT_DIG = 6 FLT_EPSILON = 1.19209e-07
Datentyp double :
DBL_MAX_EXP = 1024
DBL_MANT_DIG = 53
DBL_MIN = 2.22507e-308 DBL_MAX = 1.79769e+308
DBL_DIG = 15 DBL_EPSILON = 2.22045e-16
Datentyp long double :
LDBL_MAX_EXP = 16384
LDBL_MANT_DIG = 64
LDBL_MIN = 3.362103e-4932 *) LDBL_MAX = 1.18973e+4932
LDBL_DIG = 19 LDBL_EPSILON = 1.08420e-19
*) Anmerkung :
Offensichtlich Fehler in TURBO-C beim Typ long double :
angegebener Wert stammt von BORLAND-C++ 3.1
TURBO-C 2.0 erzeugt : Floating point error: Overflow
2.4 Darstellung von Konstanten
Integer- und Gleitpunkt-Konstante in C
-
Integer-Konstante
-
Dezimalkonstante
-
Folge von Dezimalziffern
-
erste Ziffer muß < > '0' sein
-
Oktalkonstante
-
Folge von Oktalziffern ('0' .. '7')
-
erste Ziffer muß '0' sein
-
Beispiel : 01175
-
Sedezimalkonstante
-
Beginn mit 0x oder 0X
-
Anschließend Folge von Sedezimalziffern
('0' .. '9', 'a' .. 'f', 'A' .. 'F')
-
Beispiel : 0x27d, 0X27D
-
Anhängen eines Suffix an die Ziffernfolge :
-
Suffix 'l' oder 'L' : Long-Konstante
Beispiel : 15l, 15L, 017L, 0xfl
-
Suffix 'u' oder 'U' : Unsigned-Konstante
Beispiel : 55U, 067u
-
Typ einer Integer-Konstanten :
-
abhängig von Zahlensystem, Wert und Suffix
-
jeweils der erste Typ in den folgenden Listen, in dem der Wert dargestellt
werden kann :
| kein Suffix, dezimal |
int, long int,
unsigned long int |
kein Suffix, oktal
oder sedezimal |
int, unsigned int, long int,
unsigned long int |
| Suffix 'u' oder 'U' |
unsigned int,
unsigned long int |
| Suffix 'l' oder 'L' |
long int,
unsigned long int |
-
Beispiele (TURBO-C) :
| 32769 |
-> |
long int |
| 0x8001 |
-> |
unsigned int |
| 32769U |
-> |
unsigned int |
| 0x8001l |
-> |
long int |
-
Gleitpunkt-Konstante
-
nur Dezimalkonstante
-
Aufbau :
-
- ganzzahliger Teil
-
- Dezimalpunkt
-
- gebrochener Anteil
-
- Zeichen 'e' oder 'E'
-
- ganzzahliger Exponent, der vorzeichenbehaftet sein darf
-
- Suffix 'f', 'F', 'l' oder 'L' (ANSI-C)
-
ganzzahliger Teil, gebrochener Teil und Exponent bestehen aus Dezimalziffern
-
entweder gebrochener Teil oder ganzzahliger Teil dürfen fehlen, nicht
jedoch beide
-
entweder Dezimalpunkt oder 'e' (bzw 'E') und Exponent dürfen fehlen,
nicht jedoch beides
Suffix darf fehlen
Syntaxdiagramm :
-
Beispiele :
| 15.75 |
1.575e1 |
1575E-2F |
1.0 |
| 0.1575e+2l |
.1575e+2 |
1. |
1.0f |
-
Typ einer Gleitpunktkonstanten :
- durch Suffix bestimmt:
| - ohne Suffix |
: |
double, |
z.B. |
15.75 |
| - Suffix 'f' oder 'F' |
: |
float, |
z.B. |
1575E-2F, 1.0f |
| - Suffix 'l' oder 'L' |
: |
long double, |
z.B. |
0.1575e+2l |
-
konstanter Ausdruck
-
Ausdruck, der nur aus Konstanten und Operatoren zusammengesetzt ist
-
wird zur Compilezeit ausgewertet
-
darf überall stehen, wo ein konstanter Wert stehen darf bzw. benötigt
wird
-
z.B. sind -2 oder -3.45 konstante Ausdrücke (keine negat.
Konst.!)
Zeichenkonstante in C
-
Zeichenangabe :
-
Zeichenersatzdarstellung
:
Zeichen Darstellung ASCII-Code
--------------------------------------------------------
Newline (Line Feed) (LF) \n $0A
Carriage Return (CR) \r $0D
Form Feed (FF) \f $0C
Backspace (BS) \b $08
Horizontal Tab (HT) \t $09
Vertical Tab (VT) \v *) $0B *)
Bell (alert) (BEL) \a *) $07 *)
Backslash ( \ ) \\ $5C
Question Mark ( ? ) \? *) $3F *)
Single Quote ( ' ) \' $27
Double Quote ( " ) \" $22
Bit pattern (okt. Codewort) \zzz (zzz: 1 bis 3 Oktalziff.)
Bit pattern (sed. Codewort) \xss *) (ss : 1 bis 2 Sedez.ziff.)
Anmerkungen :
-
*) ANSI-Ergänzungen gegenüber KuR-C, nicht bei allen
C-Systemen realisiert
-
bei einigen C-Systemen können weitere Zeichenersatzdarstellungen definiert
sein
Beispiele :
'A' = '\101' = '\x41'
'0' = '\60' = '\x30'
'\n' = '\12' = '\x0a'
'\0' (NUL)
Der Wert einer Zeichenkonstanten ist der Wert des entsprechenden
Codeworts
im in der Rechenanlage verwendeten Zeichensatz. Der Typ einer Zeichenkonstanten
ist int.
Zeichenketten- (String) Konstante in C
"Hello\n"
"\"Ja, ich mache es\", sagte Herma"
"" /* leerer String, dh. Länge = 0 */
Hintereinanderstehende Stringkonstanten, die nur durch White-Space-Character
(Blank, TAB, Newline) oder Kommentare getrennt sind, werden zur Compilezeit
zu einem einzigen String zusammengefügt (konkateniert)
"Hello," "World !" ---> "Hello,World !"
-> Einfache Möglichkeit zur Formulierung von Stringkonstanten, die
sich über mehrere Zeilen erstrecken
(der zwei Teilstring-Konstanten trennende Newline-Character wird nicht
in den konkatenierten String aufgenommen)
Abspeicherung von Stringkonstanten
-
als char-Array
-
Markierung des Stringendes durch den hinzugefügten NUL-Character
('\0')
-
Beispiel :

-
Achtung !

Aufzählungstypen und Aufzählungskonstante in C
-
Aufzählungskonstante (enumeration constants) bilden e i n e
Möglichkeit für symbolische Konstante in "C".
(Eine a l t e r n a t i v e Möglichkeit ist durch die Preprozessor-
Anweisung #define gegeben).
-
Sie sind immer vom Typ int und werden mittels einer Aufzählungstyp-
Definition definiert.
-
Aufzählungstyp-Definition (ohne Verwendung von 'typedef')
Aufzählungsliste
-
Werden nur Konstantennamen (ohne Gleichsetzung mit einem konstanten
Ausdruck mittels '=' ) in der Aufzählungsliste angegeben, so wird
dem ersten Namen der Wert 0, dem zweiten Namen der
Wert
1 usw zugeordnet.
Wird ein Konstantenname mittels '=' mit einem konstanten Ausdruck gleichgesetzt,
so wird ihm der Wert dieses Ausdrucks zugeordnet, eventuelle weitere
Konstantennamen bekommen dann in der Reihenfolge ihrer Auflistung den jeweils
nächsten int-Wert zugeordnet, es sei denn, ihnen wird explizit mittels
'=' ein anderer Wert zugeordnet.
-
Einem Aufzählungstyp kann ein Typname gegeben werden, wenn
nicht : Definition eines namenlosen Typs ---> nur Definition der
Aufzählungs-Konstanten
-
Beispiele :
enum { MON, DIE, MIT, DON, FRE, SAM, SON };
enum MONAT { JAN=1, FEB, MAR, APR, MAI,
JUN, JUL, AUG, SEP, OKT, NOV, DEZ } ;
enum STEUERZEICHEN { BEL='\a', BS='\b', FF='\f', LF='\n', CR='\r' };
Es können auch Variable eines Aufzählungstyps vereinbart
werden
(gleichzeitig mit Typ-Definition oder getrennt davon)
2.5 Variablen, Variablenvereinbarung
-
Variable sind Daten, die ihren Wert während des Programmablaufs
verändern
können.
Eine Variable wird durch einen Namen repräsentiert, der
auf einen bestimmten Speicherbereich weist, in dem sich der jeweils
aktuelle
Wert der Variablen befindet.
Jede Variable ist von einem bestimmten Typ. Der Typ legt die
Größe
des mit der Variablen verbundenen Speicherbereichs und die Interpretation
seines Inhalts fest. --> Eine Variable kann nur Werte ihres Typs
annehmen.
-
Die Festlegung des Namens und des Typs (und der Speicherklasse,
s. später) einer Variablen geschieht in einer Variablenvereinbarung.
Man unterscheidet 2 Arten von Vereinbarungen :
| - Deklaration |
Festlegung des Namens und der Attribute(Eigenschaften) |
| - Definition |
wie Deklaration,zusätzlich Reservierung von Speicherplatz |
-
In C müssen alle Variablen v o r ihrer Verwendung vereinbart
werden.
-
Vereinfachtes Syntaxdiagramm der Variablenvereinbarung
| Typangabe |
: |
Standard-Datentypbezeichnung (einschließlich eventu- eller Qualifier)
(z.B. float, unsigned int) oder eine mittels einer Typdefinition eingeführte
Typbezeichnung (z.B. enum MONAT) |
| Variablenangabe |
: |
Variablenname (Syntax für Bezeichner !) gegebenenfalls ergänzt
um Zusätze zur genaueren Typ- spezifikation (s. später) |
| Beispiele |
: |
int iZaehler;
long int lAw, lBw, lCw; /* long lAw, lBw, lCw; */
double dSumme, dSollZins;
enum MONAT eMonth; |
-
Variable können bei der Definition initialisiert werden. Zur
Angabe des Anfangswertes ist ein konstanter Ausdruck zulässig.
Beispiele :
char cZeichen = 'A';
double dWert, dZaehl = 3.0+2.5; /* nur dZaehl ist initialisiert */
int iStart = 100, iStop = 10, iStep =-5;
Die Qualifier "const" und "volatile" in C
-
Die in ANSI-C eingeführten Qualifier const und volatile
dienen zur Festlegung bestimmter Eigenschaften von Variablen.
Sie sind in einer Variablenvereinbarung vor der Typangabe bzw vor dem
Variablennamen anzugeben.
-
Der Qualifier const
-
kennzeichnet, daß der Wert der Variablen nicht durch das Programm
geändert werden darf.
Die Variable darf lediglich bei der Definition initialisiert
werden.
Jede weitere Wertzweisung an diese Variable innerhalb des Programms
ist unzulässig.
Anwendung z.B. für - Datenobjekte, die in einen
ROM-Bereich
plaziert werden sollen.
-
Beispiele :
-
const double dPi = 3.1415926;
-
const char acMeldung[] = "Vorsicht !";
-
Bei Zeigervariablen ist durch die Position des Qualifiers const
zu unterscheiden, ob die Zeigervariable selbst oder das Zeigerobjekt nicht
geändert werden darf:
-
Zeigerobjekt darf nicht geändert werden (wohl aber die
Zeigervariable selbst) :
z.B.
const char *pStr1 = "Haus";
*pStr1 = 'M'; /* unzulässig */
pStr1 = "Hello World"; /* zulässig */
Zeigervariable darf nicht geändert werden (wohl aber
das Zeigerobjekt) :
z.B.
char *const pStr2 = "Haus";
*pStr2 = 'M'; /* zulässig */
pStr2 = "Hello World"; /* unzulässig */
Anwendung auch bei Funktionsparametern, die Zeiger sind :
Hinweis, daß das entsprechende Zeigerobjekt durch die Funktion
nicht geändert werden darf (bei Funktionsdefinitionen) bzw nicht
geändert wird (bei Funktionsdeklarationen)
Beispiel :
int strlen(const char *pStr);
Mit const qualifizierte Variable sind keine Konstanten.
const int iAnz = 80;
char acZeile[iAnz]; /* unzulässig */
Der Qualifier volatile
2.6 Funktionsdefinition und -deklaration
Funktionen in C - Allgemeines
-
Funktionen sind die einzige Art von Unterprogrammen in C.
Sie stellen die Grundbausteine jedes C-Programms dar.
Jeglicher in C-Programmen enthaltener Code befindet sich in Funktionen.
Selbst das "Hauptprogramm" ist in Wirklichkeit eine Funktion (main()),
die vom - durch den Linker hinzugebundenen - Startup-Code aufgerufen wird.
--> Jedes C-Programm besteht aus mindestens einer Funktion
(main())
-
Alle in einem C-Programm verwendeten Funktionen sind statisch nebeneinander
-in einer Ebene- angeordnet.
--> Die Definition von Funktionen innerhalb von Funktionen ist in C
nicht möglich
--> Es existiert in C keine statische Blockstruktur bezüglich
Funktionen.
Da jedoch jede Funktion jede andere Funktion in beliebiger Reihenfolge
aufrufen kann, entsteht eine dynamische Blockstruktur.
Diese spiegelt die Struktur, d.h. die formale Zerlegung, einer Problemlösung
(Algorithmus) wieder.
-
In einer Funktion sind jeweils die zusammengehörigen zur Lösung
einer bestimmten Teilaufgabe dienenden Anweisungen zusammenzufassen.
Dabei sollte der Codeumfang der einzelnen Funktionen nicht zu groß
werden.
Gegebenenfalls sollte eine sich als zu umfangreich herausstellende
Funktion weiter zerlegt werden.
In der sich ergebenden dynamischen Blockstruktur können und sollten
sich sinnvollerweise dabei die einzelnen Stufen einer schrittweisen Verfeinerung
der Zerlegung des Lösungsalgorithmus wiederspiegeln. D.h. Funktionen
(einer übergeordneten Stufe) können durchaus im wesent- lichen
nur aus weiteren Funktionsaufrufen (der nächstniedrigeren Stufe) bestehen.
(Ausnahme : zeitkritische Anwendungen)
Ein typisches C-Programm besteht daher eher aus zahlreichen "kleinen"
Funktionen denn aus wenigen "großen" Funktionen.
-
C unterstützt die modulare Programmierung.
D.h. die in einem C-Programm verwendeten Funktionen können auf
mehrere -getrennt übersetzbare- Module aufgeteilt werden.
Daneben lassen sich Bibliotheksfunktionen verwenden.
-
Eine Funktion kann in einem C-Programm in drei möglichen Formen auftreten
:
-
als Funktionsdefinition (Function Definition)
-
als Funktionsdeklaration (Function Allusion)
-
als Funktionsaufruf (Function Call)
-
Bevor eine Funktion verwendet (d.h. aufgerufen) werden kann, muß
sie vereinbart, d.h. entweder definiert oder deklariert werden.
Funktionen in C - Funktionsdefinition
-
Jede in einem C-Programm verwendete Funktion, die keine Bibliotheksfunktion
ist, muß genau einmal in einem Modul definiert werden.
Bei der Definition wird festgelegt:
-
der Name der Funktion
-
die formalen Parameter (Anzahl, Namen, Typ, Reihenfolge)
-
der Rumpf der Funktion (lokale Vereinbarungen, Anweisungen)
-
der Typ des Funktionswertes
-
die Speicherklasse der Funktion
Durch die Funktionsdefinition wird Speicherplatz belegt (für
bestimmte lokale Variable und den Funktionscode)
-
Der Typ des Funktionswertes kann sein :
-
jeder einfache Datentyp
-
jeder Pointer-Typ
-
ein structure-Typ
-
ein union-typ
-
ein enum-Typ
-
der Typ void (in ANSI-C eingeführt)
Bei fehlender Datentyp-Angabe wird als Default-Datentyp int
angenommen.
Der Datentyp void ist in ANSI-C für Funktionen, die keinen
Funktionswert liefern, eingeführt worden.
In KuR-C ist für derartige Funktionen der Datentyp int verwendet
worden.
Die aufrufende Funktion kann natürlich immer den Rückgabewert
einer auf- gerufenen Funktion -unabhängig von dessen Typ- ignorieren.
-
Als Speicherklassenangaben sind zulässig :
| - |
extern |
: |
die Funktion kann auch in anderen Modulen verwendet werden (default) |
| - |
static |
: |
die Funktion kann nur in dem Modul, in dem sie definiert ist, verwendet
werden (modulglobal) |
-
Bezüglich der Angabe der formalen Parameter existieren 2
Syntax-Formen :
| - |
neue Art |
(ANSI-C) |
: |
Die Parameterliste enthält auch die Deklaration der Parametertypen.
Eine leere Parameterliste wird durch das Wortsymbol void gekennzeichnet. |
| - |
alte Art |
(KuR-C) |
: |
In der im Funktionskopf angegebenen Parameterliste sind nur die Parameternamen
angegeben.
Die Typdeklaration der Parameter erfolgt getrennt hiervon.
Eine leere Parameterliste besteht nur aus öffnender und schließender
runder Klammer |
Die neue Art korrespondiert mit der in ANSI-C eingeführten neuen
Art der Funktionsdeklaration (Function Prototype)
ANSI-C läßt beide Arten der Funktionsdefinition (u. -deklaration)
zu.
-
Es ist möglich, Funktionen mit einer variablen Anzahl von Parametern
zu definieren (...).
Funktiondefinition (neue Art, ANSI-C) - vereinfachte Syntaxdiagrame
-
Parameterliste
-
Parameterdeklaration
-
Funktionsrumpf
-
Beispiel:
double Machen(int iAnz, float fWert)
{
...
}
Funktiondefinition (alte Art) - vereinfachte Syntaxdiagrame
-
Parameterliste
-
Parameterdeklaration
-
Funktionsrumpf
-
Beispiel:
double Machen(iAnz, fWert)
int iAnz;
float fWert;
{
...
}
Funktionen in C - Funktionsdeklaration
-
Es ist zulässig, in einem C-Modul Funktionen zu verwenden, die in
einem anderen Modul oder im gleichen Modul erst nach ihrer Verwendung
definiert werden.
In einem solchen Fall muß die Funktion aber vor ihrer Verwendung
deklariert werden :
| - |
Extern-Deklaration |
für Funktionen, die in einem anderen Modul definiert oder Bibliotheksfunktionen
sind |
| - |
Vorwärts-Deklaration |
für Funktionen, die in dem gleichen Modul aber erst nach ihrer
erstmaligen Verwendung definiert sind |
Bei der Funktionsdeklaration wird festgelegt :
-
der Name der Funktion
-
die formalen Parameter (Anzahl, Typ, Reihenfolge) (optional, in
ANSI-C eingeführt)
-
der Typ des Funktionswertes
-
die Speicherklasse der Funktion
Durch eine Funktionsdeklaration wird keinerlei Speicherplatz belegt.
-
Die einzige zulässige Speicherklassenangabe bei einer Funktionsdeklaration
ist extern, die aber gleichzeitig default ist.
-
Für Funktionen, deren Funktionswert vom Typ int ist, kann eine
eigentlich notwendige Deklaration auch weggelassen werden.
D.h. für alle Funktionen, die zum Zeitpunkt ihrer Verwendung weder
definiert noch deklariert sind, wird defaultmäßig der Funktionstyp
int angenommen. --> implizite Funktionsdeklaration --> Fehlerquelle
!
Wird jedoch für eine int-Funktion eine Deklaration vorgenommen,
so muß diese eine Datentypangabe enthalten. --> eine Default-Datentyp-
angabe für Funktionsdeklarationen gibt es nicht.
-
Mit ANSI-C wurde die Möglichkeit eingeführt, bei der Funktionsdeklaration
auch Anzahl, Typ und Reihenfolge der formalen Parameter anzugeben
(neue Syntax --> Function Prototype).
Dies hat zwei Vorteile :
-
Der Compiler kann die beim Funktionsaufruf angegebenen aktuellen Parameter
auf Übereinstimmung mit den formalen Parametern überprüfen.
-
Der Compiler kann zulässige implizite Typwandlungen zwischen aktuellen
und formalen Parametern vornehmen (--> Anpassung des Typs der aktuellen
Parameter an den Typ der formalen Parameter).
Dies bedeutet auch, daß bei Verwendung von Function Prototypes
-
float-Parameter auch tatsächlich als float-Werte übergeben
und nicht automatisch in double-Werte umgewandelt werden,
-
char- und short-Parameter auch tatsächlich als char- bzw short-
Werte übergeben und nicht automatisch in int-Werte umgewandelt
werden.
Ohne Function Prototypes werden - wie in KuR-C - die o.a. automatischen
Typkonvertierungen durchgeführt.
Funktionsdeklaration - vereinfachte Syntaxdiagramme
-
Funktionsdeklaration (neue Art, ANSI-C) (Function Prototype)
-
keinerlei Überprüfung durch Compiler auf richtige Anzahl und
Typ der Parameter möglich
-
Beispiel : double Machen();
Beispiel zur Funktionsvereinbarung (neue Art, ANSI-C)
/*--------------------------------------------------------------------*/
/* Programm FKTVERN */
/* */
/* Beispiel zur Funktionsdefinition und -deklaration (neue Art) */
/*--------------------------------------------------------------------*/
#include <stdio.h>
/*--------------------------------------------------------------------*/
void main(void)
{ int a=3, b=10;
float x=3.0, y=10.0;
float realadd(float,float); /* Vorwärtsdeklaration */
int intadd(int,int); /* Vorwaertsdeklaration fuer intadd zwar
nicht erforderlich, da Ergebnis vom
Typ int,
aber ohne Vorwärtsdeklaration wird
keine Parameterüberprüfung durch den
Compiler vorgenommen */
a=intadd(a,b);
x=realadd(x,y);
printf("%d\n%f\n",a,x);
}
/*--------------------------------------------------------------------*/
int intadd(int a,int b) /* Typangabe nicht erforderlich, da int */
{
return(a+b);
}
/*--------------------------------------------------------------------*/
float realadd(float s,float t)
{
return(s+t);
}
/*--------------------------------------------------------------------*/
Bildschirmausgabe:
D:\RT\C>fktvern
13
13.000000
Beispiel zur Funktionsvereinbarung (alte Art
/*---------------------------------------------------------------------*/
/* Programm FKTVERA */
/* */
/* Beispiel zur Funktionsdefinition und -deklaration (alte Art) */
/*---------------------------------------------------------------------*/
#include <stdio.h>
/*---------------------------------------------------------------------*/
main()
{ int a=3, b=10;
float x=3.0, y=10.0;
float realadd(); /* Vorwaertsdeklaration */
a=intadd(a,b); /* Vorwaertsdeklaration fuer intadd nicht
erforderlich, da Ergebnis vom Typ int */
x=realadd(x,y);
printf("%d\n%f\n",a,x);
}
/*---------------------------------------------------------------------*/
int intadd(a,b) /* Typangabe nicht erforderlich, da int */
int a,b; /* Parameterdeklaration */
{
return(a+b);
}
/*---------------------------------------------------------------------*/
float realadd(s,t)
float s,t; /* Parameterdeklaration */
{
return(s+t);
}
/*---------------------------------------------------------------------*/
Bildschirmausgabe:
D:\RT\C>fktvera
13
13.000000
2.7 Anweisungen für den Preprozessor
-
Allgemeines
Vor der eigentlichen Übersetzung wird ein C-Quellmodul von einem
Preprozessor bearbeitet.
Dieser nimmt Manipulationen am Quelltext vor.
Seine Tätigkeit wird mittels spezieller Preprozessoranweisungen
(Direktiven) gesteuert.
Eine Preprozessoranweisung beginnt mit # als erstem Nicht-Whitespace-
Zeichen einer Zeile. Sie kann mit \ als letztem Zeichen einer Zeile
in der Folgezeile fortgesetzt werden.
-
Einbinden von Textdateien
-
#include "dateibezeichnung"
-
oder
-
#include <dateibezeichnung>
-
Die durch dateibezeichnung bezeichnete Datei wird am Ort der Anweisung
im Quelltext eingefügt.
-
Steht <dateibezeichnung> in spitzen Klammern, so wird die Datei in implementierungsabhängigem
voreingestelltem Directory gesucht.
-
#include-Dateien dürfen wiederum #include-Anweisungen enthalten
-
Anwendung :
-
Zusammenfassung von Vereinbarungen und weiteren Preprozessor -Anweisungen,
die von mehreren Quelldateien benötigt werden, in einer eigenen Datei.
-
Zu jedem C-System gehören eine Reihe Standard-Include- Dateien ("header
files", <... .h>), die u.a. Vereinbarungen für die Verwendung von
Bibliotheksfunktionen enthalten.
-
z.B. #include <stdio.h>
-
Definition eines Namens für eine Zeichenfolge
-
#define NAME zeichenfolge
-
Jeder Auftritt von NAME im Quelltext nach der Anweisung wird vom Preprozessor
durch zeichenfolge ersetzt.
Ausnahme: Innerhalb von Strings erfolgt keine Ersetzung
-
NAME muß den Syntaxregeln für C-Namen entsprechen
-
Als zeichenfolge wird die gesamte restliche Zeile gewertet, Zeichenfolge
kann also auch Blanks enthalten (führende und abschließende
Blanks werden allerdings ignoriert)
-
Anwendung :
-
vor allem zur Definition symbolischer Konstanten
(die üblicherweise mit Großbuchstaben bezeichnet werden)
-
z.B. #define EOF (-1)
-
Namen können nur dann neu definiert werden, wenn eine vorhergehende
Definition mittels
#undef NAME
aufgehoben wurde.
-
Definition parameterisierter Makros
-
#define NAME(parameterliste) zeichenfolge
-
parameterliste enthält durch Kommata getrennt die formalen Parameter,
die sinnvollerweise in zeichenfolge vorkommen sollten.
-
Den "Aufruf" des Makros name(akt_parliste) ersetzt der Preprozessor
durch zeichenfolge, wobei für die in zeichenfolge vorkommenden formalen
Parameter die entsprechenden aktuellen Parameter aus akt_parliste
eingesetzt werden.
-
Anwendung : vor allem Ersatz kurzer einfacher Funktionen
-
z.B. #define SQR(x) ((x)*(x))
-
für den "Aufruf" SQR(a+b) setzt der Preprozesor ((a+b)*(a+b))
ein.
-
Eine Reihe von Standard-"Funktionen" sind tatsächlich als Makros (in
den entsprechenden Header-Files) definiert
-
Steuerung bedingten Preprocessings und bedingter Compilierung
#if konst_ausdruck
#ifdef name
#ifndef name
#elif konst_ausdruck
#else
-
der nach einer der obigen Anweisungen stehende Quelltext wird für
das weitere Preprocessing und die Compilierung nur dann berücksichtigt,
wenn konst_ausdruck einen Wert != 0 ergibt bzw die entsprechende
Bedingung erfüllt ist.
-
Ende des so behandelten Quelltextes bei der nächsten entsprechenden
alternativen Anweisung bzw. bei
#endif
-
#ifdef name ist identisch mit #if defined name
-
#ifndef name ist identisch mit #if ! defined name
Weitere Preprozessor-Anweisungen
#line konstante "dateiname"
bzw.
#line konstante
#pragma zeichenfolge [parameter]
#error zeichenfolge
2.8 Erzeugung eines ausführbaren Maschinenprogramms
Vom C-Quell-Modul zum ausführbaren Maschinenprogramm
Copyright © FH München, FB 04, Prof. Dr. Rainer Thomas
V - PC - 211 - 00 - TH - 06
V - PC - 212 - 00 - TH - 02
V - PC - 221 - 00 - TH - 07
V - PC - 222 - 01 - TH - 01
V - PC - 230 - 00 - TH - 02
V - PC - 231 - 00 - TH - 07
V - PC - 232 - 00 - TH - 02
V - PC - 233 - 00 - TH - 03
V - PC - 241 - 01 - TH - 01
V - PC - 241 - 02 - TH - 03
V - PC - 242 - 00 - TH - 07
V - PC - 243 - 00 - TH - 01
V - PC - 244 - 00 - TH - 03
V - PC - 251 - 00 - TH - 04
V - PC - 253 - 00 - TH - 02
V - PC - 261 - 00 - TH - 02
V - PC - 263 - 00 - TH - 03
V - PC - 264 - 01 - TH - 05
V - PC - 264 - 02 - TH - 05
V - PC - 265 - 00 - TH - 02
V - PC - 266 - 00 - TH - 06
V - PC - 267 - 01 - TH - 04
V - PC - 267 - 02 - TH - 04
V - PC - 271 - 01 - TH - 05
V - PC - 271 - 02 - TH - 03
V - PC - 281 - 00 - TH - 02