C_Programm 1

| ### Programmieren in C von Prof. Dr. Rainer Thomas

# 2. Grundlagen

### 2.1 Aufbau eines C-Programms
2.2 Bezeichner und reservierte Worte
2.3 Datentypen (Überblick und einfache Typen)
2.4 Darstellung von Konstanten
2.5 Variable, Variablenvereinbarung
2.6 Funktionsdefinition und -deklaration
2.7 Anweisung für den Preprozessor
2.8 Erzeugung eines ausführbaren Maschinenprogramms

## 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

- Deklarationen
- Typdefinitionen
- Variablendefinitionen
- Funktionsdefinitionen
- Preprozessoranweisungen

}
}

Vereinbarungen |

- 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

- beginnen mit einem Großbuchstaben
- bestehen aus Groß- u. Kleinbuchstaben, häufig auch ganz aus Großbuchstaben
- zusätzlich können sie Ziffern (selten) und den Unterstrich enthalten
- Beispiele :

    typedef unsigned short int WORD;

    struct Datum { /* øøø */ };

- 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).

- Namens-Präfix :

- kennzeichnet den Typ der Variablen
- besteht ganz aus Kleinbuchstaben

Präfix für Grundtypen |
    c
    char
    i
    integer
    n
    integer (alternativ)
    s
    short integer
    l
    long integer
    sc
    signed char
    uc
    unsigned char
    ui
    unsigned integer
    us
    unsigned short
    ul
    unsigned long
    f
    float
    d
    double
    ld
    long double
Präfix für abgeleitete Typen |
`e` | `enum`-Typen
`st` | `struct`-Typen
`u` | `union`-Typen
`sz` | String ('`\0`'-terminiert)
`pv` | Pointer auf `void`
`pfn` | Pointer auf Funktionen

Präfix-Vorsatz

`a` | Array | (z.B.: `ad` Array von double)
`p` | Pointer | (z.B.: `pi` Pointer auf int)

Präfix für implementierungsdefinierte Typen In einigen C-Implementierungen sind für zusätzlich definierte Datentypen bzw. alternative Typbezeichnungen weitere Präfixe gebräuchlich
- Namens-Hauptteil :

- kennzeichnet i.a. Funktionalität/Verwendungszweck der Variablen
- beginnt mit einem Großbuchstaben
- besteht aus Groß- u. Kleinbuchstaben (u.U. auch Ziffern)
- weitere Großbuchstaben dienen zur Gliederung des Namens
- enthält i.a. keinen Unterstrich

- Beispiele :

    char cNext;
    unsigned uiWordCnt;
    char *pcZeile;
    int aiKoeff[10];
    void **ppvResBer;

- Anmerkung :

Abweichend von obigen Empfehlungen können für lokal eingesetzte Index-, Zähl- oder Schleifenvariable (meist Typ int oder unsigned) sehr kurze "nichtssagende" Namen (häufig einzelne Kleinbuchstaben, gegebenenfalls mit ein oder zwei Ziffern ergänzt) gewählt werden (z.B. i, j, k3 usw.)

## 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&aumlnge

- 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&aumlnge wie int bzw. short bzw. long Interpretation immer als positive Zahl, Modulo-Arithmetik -> kein &Uumlberlauf !

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&aumlngen von der jeweiligen Darstellungsl&aumlnge und dem Darstellungsformat ab. -> implementierungsabh&aumlngig
- Zur Erh&oumlhung der Portabilit&aumlt sieht ANSI-C die Definition symbolischer Konstanten f&uumlr die Grenzwerte der darstellbaren Wertebereiche vor.
Diese Konstanten sind in den Header-Dateien

  <limits.h> (f&uumlr ganzzahlige Datentypen) und
  <float.h> (f&uumlr 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

- Beispiel : 637

- 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

- Zeichenkonstante :

- 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

- Zeichenkettenkonstante :

- Zeichenangabe :
- Zeichenersatzdarstellung
- Beispiele :

    "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)

- Beispiel :

    "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

- kennzeichnet, daß sich der Wert einer Variablen durch "äußere" Ein- flüsse ändern kann.
Auch wenn der Variablen innerhalb des Programms kein neuer Wert zuge- wiesen wird, darf der Compiler - und der Optimizer - nicht annehmen, daß ihr Wert unverändert bleibt.
--> Verhinderung von sonst eventuell möglichen Optimierungen (z.B. durch Halten der Variablen in einem Register)
Anwendung z.B. für

- Variable, die durch Interrupt Service Routinen geändert werden können
- Memory-mapped I/O-Register

Beispiel :

`volatile int iTicks; /* wird durch ISR verändert */`

## 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

- Funktionsdefinition

- Parameterliste

- Parameterdeklaration

- Funktionsrumpf

- Beispiel:

    double Machen(int iAnz, float fWert)
    {
      ...
    }

### Funktiondefinition (alte Art) - vereinfachte Syntaxdiagrame

- Funktionsdefinition

- 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)

Parameterliste

- Parametertypedeklaration

- Compiler kann auf richtige Anzahl und Typ der Parameter überprüfen
- Beispiel : `double Machen(int, float);`

- Funktionsdeklaration (alte Art)

- 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

Zum Inhaltsverzeichnis | Zum nächsten Abschnitt

###### 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