Die Programmiersprache C

Tutorial / Referenz / Lehrgang

Copyright © 1999,2000  Helmut Schellong

(mit C99 -- Neuer ANSI-Standard; mit C++)

Mittlerweile gibt es ein Buch, das auf dieser beliebten Webseite basiert.

1.Auflage: Dieses Buch ist mit 290 Seiten etwa 4-fach umfangreicher, besser gegliedert, hat ein buch-typisches Inhaltsverzeichnis und ein Sachverzeichnis, das den normalen Text und die Kodeabschnitte separat behandelt.

SpringerVieweg-Verlag, Heidelberg, Berlin

ISBN-10  3-642-54436-3       (3. Auflage)
ISBN-13  978-3-642-54436-1
ISBN-10  3-642-40057-4       (2. Auflage)
ISBN-13  978-3-642-40057-5
ISBN-10  3-540-23785-2       (1. Auflage)
ISBN-13  978-3-540-23785-3

Ladenpreis (Hardcover)  49,99 .. 59,99 €       Probeseiten

www.amazon.de  (3. Auflage, in Farbe!)
www.amazon.de  (2. Auflage)
Ergänzungen zum Buch
Rezension io-port.net      Rezension webcritics.de

autor@schellong.de           Luftfahrt (mil+zivil): Abkehr von ADA -> C

Register

28.Jan.2000/v37
09.Feb.2004/v38

C-Module, C-Quellkode, C-Compiler
C-Programm-Schema
C-Zeichensatz, Konstanten, Kommentare
Schlüsselwörter
Elementare Datentypen
Operatoren
Speicherklassen
Adressen (Pointer, Zeiger)
Arrays (Vektoren, Felder)
Strukturen, Unionen, Bitfelder, Enumerationen
Initialisierungen
Funktionen
Steuerung des Programmablaufes:  if-else, Schleifen, etc.
Präprozessor
Präprozessor Extrem
Komplexe Typen
Sequenz-Punkte
Der NULL-Pointer
Neuer C-Standard C99
C++  (ein wenig)
Die ANSI-Standard-Bibliothek
Die POSIX-Standard-Bibliothek
Modul-Konzepte
Hinweise / Tips

Zur Hauptseite

Nachfolgend sind auch Textabschnitte für Erklärzwecke vorhanden, die nicht allgemeingültig sind.
Diese beziehen sich auf typische 32Bit-Plattformen mit Intel-iX86-Prozessor,
wenn nicht anders angegeben.
Beispielsweise sind Byte- und Bit-Anzahlen und Wertbereiche von Datentypen nicht allgemeingültig.
Bitweise Zahlendarstellungen erfolgen im 2er-Komplement (z.B. iX86).

Siehe auch: Kapitel "Hinweise/Tips"



^

C-Programm, C-Module/Dateien, C-Quellkode, Compiler

Ein C-Programm besteht aus einer oder mehreren Datei(en) mit der Endung .c  , aus der/denen
ein C-Compiler eine ausführbare Datei (oftmals mit der Endung .exe) erzeugt.

Eine C-Datei (oder auch C-Modul) wird mit einem beliebigen Text-Editor erzeugt
und muß gültigen C-Quellkode enthalten.

Der Aufruf-Name eines C-Compilers lautet vom Unix-Ursprung her: cc
'cc' ist aber nicht der eigentliche Compiler, sondern nur ein Manager-Programm (Frontend),
das die angegebenen Argumente prüft und die diversen Programme des Entwicklungssystems
(oft mit vielen zusätzlichen Argumenten) jeweils in der richtigen Reihenfolge korrekt aufruft.

Verschiedene Aufruf-Namen:
cc (Unix-Standard),  bcc (Borland/DOS),  cl (Microsoft/DOS),  gcc (Gnu/Unix),  CC (C++/Unix),  ...

Beispielsweise ein Aufruf eines Borland-Compilers:

    bcc datei.c

erzeugt eine ausführbare Datei:  datei.exe

Unter Unix:

    cc -odatei datei.c

erzeugt eine ausführbare Datei:  datei

Obiger Aufruf veranlaßt vier Arbeitsschritte:

    Programm                     Datei-Endung
    C-Präprozessor           --> .i
    C-Compiler(+Optimierer) --> .s/.asm
    Assembler --> .o/.obj
    Linker --> .../.exe/.com

cc  kann mittels Optionen so gesteuert werden, daß von hinten gesehen (Linker)
ein oder zwei oder drei oder alle vier Arbeitsschritte unterlassen werden.
Dadurch kann sehr flexibel mit dem Entwicklungssystem gearbeitet werden.
Insbesondere funktioniert auch folgender Aufruf:

    cc -O1 -oabc abc1.o abc2.s abc.c -lm

Hier wird abc.c optimiert, eine Exe (abc) erzeugt, abc1.o wird nur vom Linker verarbeitet,
abc2.s nur vom Assembler und danach vom Linker,
und nur abc.c wird von allen Programmen bearbeitet.
Mit -lm wird die Mathe-Bibliothek (z.B. /lib/libm.a) dazugelinkt.

Die Compiler-Optionen  -P oder -E, -S, -c  bewirken, daß jeweils nur der Präprozessor (.i),
Präprozessor + Compiler (.s)  oder  Präprozessor + Compiler + Assembler (.o)  arbeiten.
Eine Option  -syntax  (oder ähnlich) ermöglicht bei manchen Compilern, daß nur
eine Syntax-Prüfung der C-Quelle (datei.c) vorgenommen wird.



^

Ein schematisches C-Programm

Die folgende einzelne Zeile ist bereits ein gültiges C-Programm!:

    int main(void) { return 0; }

Hier wird der Startkode ausgeführt, der nach erfolgreicher Kompilierung stets automatisch vorhanden ist.
Dieser ruft zum Schluß die Hauptfunktion 'main' auf.
Und 'main' gibt '0' zurück, wodurch das aufgerufene Programm (z.B. xyz.exe)
den Programm-Exit-Wert '0' an das Betriebssystem zurückgibt.
(main  ist die Start-Funktion des Anwenders, des C-Programmierers.)

C-Programme sehen im Prinzip immer so ähnlich aus wie das nachfolgende Programm:

    /*               xyz.c                           sc/14.12.99 */



    # include <stdlib.h>
    # include <stdio.h>
    # include <sys/types.h>
    #if defined(UNIX)
    # include <unistd.h>
    #endif
    #if defined(DOS)
    # include <io.h>
    # include <dos.h>
    # include <conio.h>
    #endif
    # include <fcntl.h>

    /*# include "xyz.h"*/



    int main(int, char *[]);
    static int xyz(int, char **);
    static void Funktion1(void);
    static int *Funktion2(char *);


    static int Err;



    int main(int count, char *argv[])
    {
    register int r=1;
    if (count>=2) Funktion1(),
    r= xyz(count-1, argv+1);
    return (r¦Err);
    }


    static int xyz(int C, char **A)
    {
    int *ip;
    ip= Funktion2(A[0]);
    return ( ip?*ip:2 );
    }


    static int *Funktion2(char *name)
    {
    static int ia[8];
    if (!name[0]¦¦name[1]!=0) return (0);
    ia[2]= (unsigned char)name[0] + 1;
    return ( ia+2 );
    }


    static void Funktion1(void)
    {
    /* Kommentar */
    // Zeilenkommentar, neu ab C99-ANSI
    if (!isatty(0)) ++Err;
    return;
    }

Das obenstehende C-Programm funktioniert zwar, hat aber keine sinnvolle Funktion.

C-Programme bestehen also im wesentlichen (fast) immer aus beliebig vielen Funktionen,
die einfach hintereinander in eine Text-Datei geschrieben werden.
Eine Funktion namens 'main' muß unbedingt vorhanden sein, wenn die Kompilierung
eine aufrufbare Datei ergeben soll.
Weiterhin muß 'main' extern sichtbar (public) sein; das Schlüsselwort 'static' darf also nicht
davor stehen, damit der Linker das Symbol '_main' finden und mit dem Startkode verknüpfen kann.

Außerhalb von Funktionen dürfen keine Algorithmen programmiert werden,
mit if-else, Schleifen, etc. .
Die Steuerung des Programmablaufes wird innerhalb von Funktionskörpern vorgenommen.

Vor der ersten Funktion im Quellkode sollte stets eine Liste aus Funktions-Prototypen stehen,
damit der Compiler vor einem ersten Aufruf einer Funktion deren Aufrufargumente und
den Rückgabetyp kennt!
Mit einer Prototypen-Liste darf die Reihenfolge aller Funktionen beliebig sein.
Ohne Liste müßte jeder Funktionskörper vor dem jeweils ersten Aufruf stehen.

Ein Prototyp auch für 'main' ist durchaus nicht unsinnig, denn 'main' darf auch im sichtbaren
C-Quellkode (rekursiv) aufgerufen werden - also nicht nur vom hinzugelinkten Startkode aus.

Die Header-Dateien <header.h> sind nicht zwingend notwendig.
Falls man aber Bibliotheks-Funktionen (Library) verwenden will, müssen zugehörige Header
angegeben werden,
da darin die notwendigen Definitionen, Deklarationen -und Prototypen- angegeben sind.

    int main(int argc, char **argv, char **envp)

argc enthält in aller Regel die Anzahl aller in der Kommandozeile angegebenen Argumente.
argc ist in der Regel mindestens 1, da der Kommando/Programm-Name selbst das erste Argument ist.
argv[argc]  enthält die abschließende NULL-Adresse.

    for (;  argc>0;  --argc,++argv)
       printf("%s\n", argv[0]);

Der Parameter  envp  ist nicht ANSI-konform, wird aber im Standard erwähnt und ist fast überall
anzutreffen.  Er enthält das Environment, die Umgebungsvariablen.
Gemäß ANSI/ISO-Standard gibt es nur die beiden folgenden main-Typen:

    int main(void);
    int main(int, char *[]);

Die Schreibweisen  *argv[]  und  **argv  sind in der Argumentliste beide korrekt,
jedoch  char *av[]= argv;  (an anderer Stelle) ist falsch,
richtig ist:  char **av= argv;
Die Namen sind selbstverständlich frei wählbar:

    int main(int C, unsigned char **A, char **E)

argv  und  envp  sind Arrays aus Adressen auf Zeichenketten.



^

C-Zeichensatz, Konstanten, Kommentare

    abcdefghijklmnopqrstuvwxyz
    ABCDEFGHIJKLMNOPQRSTUVWXYZ
    _0123456789

Obenstehend diejenigen Zeichen, die für Namen (und Konstanten) verwendet werden dürfen.
Der Unterstrich  _  gilt als Buchstabe.
Namen müssen mit einem Buchstaben beginnen.
Die ersten 31 Zeichen eines Namens sind signifikant.
Nur bei "0123456789" müssen die Zahlenwerte um je +1 auseinander sein!

    !"#%&'()*+,-./:;<=>?[\]^{¦}~

Diese Zeichen sind Operatorenzeichen und Punktuatoren.

Leerzeichen, Tabs, Zeilenvorschübe, Wagenrücklauf, etc. sind Zwischenraumzeichen, die völlig
beliebig in einen Quelltext geschrieben werden können, solange sie nicht Schlüsselworte oder
Operatoren (aus zwei oder mehr Zeichen) oder sonstige Syntaxeinheiten zerteilen.
An manchen Stellen müssen Zwischenraumzeichen stehen, um eine Trennwirkung zu erzielen:
unsigned long  kann nicht so:  unsignedlong  geschrieben werden.

    $@`

Dies sind die einzigen (sichtbaren) in C nicht verwendeten ASCII-Zeichen.

Konstanten:

    Wert            Typ             Zahlenbasis     Ziffernmenge
    12345           int             dezimal:10      0...9
    04567 int oktal:8 0...7
    0x12af int hexadezimal/sedezimal:16
    0...9a...f, 0...9A...F
    12345L long
    12345LL long long
    C99
    12345u unsigned
    12345ul unsigned long
    0x1234ul unsigned long hexadezimal
    12345ull unsigned long long
    C99

    1.2345 double
    1.2345f float
    1.2345L long double
    1.2345e-6 double
    mit Exponent

    uUlLfFeE sind als Suffix/Exp. gültig.

    Oktal-Darstellung beginnt mit 0
    Hex-Darstellung beginnt mit 0x

Oktal- und Hexzahlen haben eine Besonderheit, da deren Zahlenbasen  8,16
Potenzen von 2 sind:  2^3, 2^4 :
Jede Ziffer steht für genau  3  bzw.  4  Bit.
Die jeweilige Ziffernmenge nutzt diese Bit-Felder voll aus:
0777  ==    111111111
0x777 ==  11101110111
0xfff == 111111111111

Zeichenkonstanten:

    'A'  '#'  'ä'  '\''  '\x41'  '\0'     L'x'(wchar_t,Unicode)

Zeichenkonstanten haben den Typ  int und den Zahlenbereich  -128...+127 .
Mit entsprechender Compiler-Option:  (int)(unsigned char)'x'  und  0...255 .
Manche Compiler akzeptieren keine Zeichen >127, wie z.B.  'ü'.
Es müssen dann andere Darstellungen des gewünschten Wertes verwendet werden:
'\x81'  oder  '\201'  oder  0201  oder  129  oder  0x81 .

Zeichenkettenkonstanten:

    "Die Programmiersprache \"C\" hat nur wenige Schlüsselwörter."
    L"Die Programmiersprache \"C++\" hat mehr davon."   /*wchar_t*/

Zeichenkettenkonstanten werden von einem Paar Doppelapostroph eingefaßt:  "..."
und haben (im Pointer-Kontext) den Typ:  Adresse auf char:   const char*
Beispielsweise ein Ausdruck:  
"abcdef"+2  ist möglich, um auf die Zeichen ab  c  zuzugreifen.
char c= "abcdef"[2];   ist ebenfalls möglich.
Der Compiler fügt beim Speichern ein Null-Zeichen  '\0'  an,
"abc"  belegt also 4 Bytes im Speicher.
In Zeichenkettenkonstanten soll nicht hineingeschrieben werden!
Einige Compiler ermöglichen das zwar, aber es ist nicht voll portabel.
(Bei manchen ist das sogar die Standardeinstellung. -- also aufpassen!)

    printf("aaaaaaa"   "bbb"
    "ccccc" "\n" );
    printf("aaaaaaabbbccccc\n");

Die beiden Funktionsaufrufe ergeben die gleiche Ausgabe, da der Compiler die erste, in Teilstücken
angegebene Zeichenkette automatisch zusammenfügt.
Es gibt eine weitere -schlechtere- Möglichkeit, mit einem  \  direkt vor dem Zeilenvorschub:

    printf("aaaaaaa\        /* Der Preprocessor macht dies */
    bbb\
    ccccc\
    \n"  );

Die folgenden Zeichen-Ersatzdarstellungen können bei Zeichen- und Zeichenkettenkonstanten
verwendet werden.
Sie ermöglichen das Schreiben von unsichtbaren Steuerzeichen und dienen der Aufhebung der
Spezialbedeutung von Zeichen (Maskierung):

    \n      Zeilenvorschub (LF,NL)
    \r Wagenrücklauf (CR)
    \t Tabulator
    \b Rückschritt (Backspace)
    \a Klingelzeichen (Bell,Beep)
    \f Seitenvorschub (Formfeed)
    \v Vertikal Tab
    \\ \
    \' '
    \" "
    \0 Null-Zeichen, Wert==0
    \ooo Oktal
    \xhh Hex

Mit  \000...\377  oder  \x00...\xff  kann der gesamte Zeichensatz angegeben werden.
Direkte Angabe von Zeichen mit  wert>127 ('ä' "öü" ...) ist nicht voll portabel!

Enumerationen (siehe weiter unten) gehören ebenfalls zu den Konstanten.

Kommentare:

    /* Dies ist ein Kommentar */
    // Dies ist ein Zeilenkommentar

Alles zwischen  /*  und  */  wird ignoriert, auch Zeilenvorschübe.
Sobald  /*  gesichtet wurde, wird nach  */  gesucht;
es ist auf mögliche Verschachtelung zu achten, die zu Fehlerhaftigkeit führt.

Zeilenkommentare gelten bis zum Zeilenende, ausschließlich des Zeilenvorschubs.
In C gültig ab C99.

Achtung!:  a= b + c - d //*70*/ 80;



^

C-Schlüsselwörter (Keywords)

    Typen von Datenobjekten:
    char Integer
    short Integer
    int Integer (default)
    long Integer
    float Gleitkomma
    double Gleitkomma
    void leer/unbestimmt
    unsigned Integer ohne Vorzeichen
    signed Integer (explizit) mit Vorzeichen

    struct Beliebig zusammengesetzter Typ (Struktur)
    union Auswahltyp/Vereinigungstyp
    enum Aufzählungstyp (Enumeration; integer)
    typedef Individuelle Typvereinbarung

    Byte-Anzahl von Daten-Objekten und -Typen:
    sizeof z.B.: sizeof(char), sizeof(i), sizeof(*array)

    Speicherklassen von (Daten-)Objekten:
    auto default in Funktionen
    register (Möglichst) ein Prozessor-Register verwenden
    static Name/Symbol wird nicht exportiert; statisch
    extern Objekt (public-Kode/-Daten), von außen hereingeholt

    Typqualifizierer von Daten-Objekten:
    const Nach Initialisierung read-only
    volatile Keine Optimierung/Stets Direktzugriff

    Programmierung des Ablaufes:
    if Bedingte Verzweigung (falls ja)
    else Bedingte Verzweigung (falls nein)
    while Schleife
    for Schleife
    do Schleife
    switch Fallunterscheidung (case)
    case Fallunterscheidung (Fallzweig)
    default Fallunterscheidung (default-case)
    break Hinaussprung: Schleife/switch
    continue Fortsetzungssprung in Schleifen
    goto Unbedingter Sprung
    return Beenden einer Funktion

    Neuer C-Standard C9X/C99:
    inline
    restrict
    _Bool
    _Complex
    _Imaginary


const
const int ci= 16;
Hiernach ist  ci= x;  ein Fehler!
const int *cip;
Hiernach ist  *cip= x;  ein Fehler!
int * const ipc= &ci;
Hiernach wäre ipc++;  ein Fehler!
Aber, diese Vereinbarung läßt der Compiler gar nicht zu, denn (*ipc)++; wäre ja erlaubt,
was aber ci ändern würde, ci ist jedoch constant !
const int * const ccip= &ci;
int const * const ccip= &ci;
Hiernach sind  *ccip= x;  und  ccip= a;  Fehler!
extern int atoi(const char *);
Der Compiler geht davon aus, daß die Funktion 'atoi' das per Adresse übergebene Objekt
nur liest und keinerlei Schreibzugriffe darauf vornimmt - eventuell Optimierung möglich.

volatile
static void setj(void)
{
   int volatile rf=0;
   if (setjmp(J)==1 && rf>0)  return;
   /* ... */
   if (/*...*/)  rf=2, ++a;
   if (a)  longjmp(J, 1);
   /* cc */
   ErrV= rf;
   return;
}
Ohne volatile:  Der Compiler hält die Variable rf zeitweilig in einem Register und ändert sie nicht
an der Stelle rf=2, sondern erst an der Stelle 'cc', weil sie bis dahin gar nicht gebraucht wird.
Sie wird aber doch gebraucht, falls longjmp() aufgerufen wird, denn longjmp() kehrt nicht zurück,
sondern springt nach setjmp(), wo rf>0 steht.
Und genau das kann der Compiler nicht wissen !!!
Mit volatile wird die Variable rf bei jedem Vorkommnis sofort und direkt gelesen/geschrieben.
Falls beispielsweise eine globale Variable durch einen Parallel-Prozeß/Thread laufend geändert wird,
ist volatile ebenfalls hilfreich, denn auch so etwas kann der Compiler nicht durchschauen.

typedef
gestattet die Definition eigener Typen mit beliebiger Komplexität.

    typedef  unsigned char  byte;
    typedef int
    ptrdiff_t;
    byte a, b=2, c, *bp=0;
    typedef struct _FILE_
    {
    int __cnt;
    unsigned char *__ptr,
    *__base,
    __flag,
    __file,
    __buf[2];
    }
    FILE;

    extern FILE __iob[];

    #define stdin (&__iob[0])
    #define stdout (&__iob[1])
    #define stderr (&__iob[2])
    FILE *fp;
    fp= fopen("COM2", "rb");
    if (!fp) Perr(E_OPENF, "COM2"), exit(2);

Hier wurde zuletzt der Typ  FILE  definiert. (<stdio.h>)
Anschließend ein Array '__iob[]', aus Strukturen vom FILE-Typ als Elemente, bekannt gemacht,
das außerhalb angelegt ist, weshalb es als 'unvollständiger Typ' angegeben wurde ([ohne Angabe]),
weil die Elementanzahl im aktuellen C-Modul unbekannt ist.
Man sieht auch, daß die vordefinierten FILE-Pointer stdin,stdout,stderr, die Adressen
der ersten drei Array-Elemente (FILE-Strukturen) sind:  Typ:  (FILE*)

    typedef  int A[10][2];
    A a, b;
    int a[10][2], b[10][2];

Es wurde ein Typ  'A'  definiert.
Die beiden letzten Zeilen haben gleiche Wirkung.



^

Elementare Datentypen

Bitanzahl und Wertbereich sind nachfolgend für typische Intel-Plattformen gezeigt:
(limits.h, float.h)

    Typ                     Bits    Wertbereich/Signifikanz
    char                     8      -128...+127
    signed char 8 -128...+127
    unsigned char 8 0... 255
    short 16 -32768...+32767
    unsigned short 16 0... 65535
    int 32 -2147483648...+2147483647
    unsigned 32 0... 4294967295
    long 32 siehe int
    unsigned long 32 siehe unsigned
    long long 64 -9223372036854775808...
    +9223372036854775807
    unsigned long long 64 0...18446744073709551615
    int                     16      DOS: siehe short
    unsigned 16 DOS: siehe unsigned short

    float 32 7 Stellen
    double 64 15 Stellen
    long double 80 19 Stellen (Intel iX86)
    long double 128 33 Stellen (z.B. HP-UX)

    sizeof(long double) 10 DOS
    sizeof(long double) 12 32Bit (10+2FüllByte)
    sizeof(long double) 16 HP-UX

Die Integer-Typen wurden in Kurzschreibweise angegeben;
Hinter  short, unsigned, long, long long  kann jeweils noch  int  stehen:
z.B.:  unsigned int

'signed' kann auch mit anderen Integer-Typen als char verwendet werden, was aber redundant wäre.
Die meisten Compiler haben eine -sehr sinnvolle- Option, die global 'char' als 'unsigned char' bewertet.
Wenn diese Option gegeben wurde, hat 'signed char' einen Sinn.
Diese Option macht fast immer die Programme kleiner und schneller und beseitigt eine Reihe von
potentiellen Problemen.  Auch Zeichenkonstanten 'x' sind dabei '(int)(unsigned char)'.

1er-Komplement-Prozessoren können in 8 Bit nur -127..+127 darstellen, nicht -128..+127.
Dafür haben sie eine negative  0 und eine positive  0 .
Der ANSI-Standard nennt -127..+127 als Mindestwertebereich für  char .

Die Wertebereiche in der obenstehenden Tabelle entsprechen den ANSI-Mindestforderungen,
mit der Ausnahme, daß die negativen Werte um 1 positiver sind
und daß für  (unsigned) int  der Wertbereich eines  (unsigned) short  ausreicht.

long long  ist ganz neuer ANSI-Standard C99.
gcc  kennt  long long ,
Borland Builder 4 kennt  __int64  (und -12345i64, 12345ui64).
Der neue ANSI-Standard C99 enthält:  long long, <stdint.h>: int64_t , etc.

Achtung, es gibt Plattformen, auf denen ein Byte 32 Bit hat !
Und die Typen  char, int, long, ...  haben dort alle 32 Bit!,
und  sizeof(char)==1, was korrekt ist.

void

void funktion(void)  { /*...*/  return; }
Die Funktion  funktion  hat keinen Rückgabewert und erhält keine Argumente beim Aufruf:
funktion();

(void)printf("...");
Der Rückgabewert der Funktion  printf  (die einen solchen hat: int) soll -explizit- ignoriert werden.
Dies wird aber meist implizit gemacht, indem ein Rückgabewert einfach gänzlich unbenutzt bleibt
und auch nirgendwohin zugewiesen wird.

void *memset_F(void *d0, int i, register unsigned n)
{
   register uchar *d= (uchar*)d0;
   register uchar  c= (uchar )i;
   while (n > 0) *d++ = c, --n;
   return (d0);
}
Der Funktion memset_F kann die Adresse von Zielobjekten beliebigen Typs übergeben werden,
weil hier mittels  void*  ein unbestimmter Adressentyp vereinbart wurde.
Innerhalb der Funktion wird in eine uchar-Adresse umgewandelt.
Bei Variablen, die eine void-Adresse enthalten (oben: d0), können weder  *d0  noch  d0++  durchgeführt
werden, weil der Compiler nicht weiß, wie er auf das Objekt zugreifen soll und um wieviel er die Adresse
erhöhen soll.  Es können damit nur Zuweisungen vorgenommen werden.
(uchar==unsigned char)

int iarr[64];
struct dirent dea[100];
memset_F(iarr, 0, sizeof(iarr));
memset_F(dea, 0, sizeof(dea));
memset_F(&dea[8], -1, sizeof(*dea));
memset_F(&dea[8], -1, sizeof(struct dirent));
/* sizeof(dea)/sizeof(*dea) ist gleich 100 */



^

Operatoren

    Operatoren,fallender Rang   Zusammenfassung von links/rechts her
    ()   []   ->   .                                L
    * + - ! ~ ++ -- & (typ) sizeof R (unär)
    * / % L
    + - L
    << >> L
    < <= > >= L
    == != L
    & L
    ^ L
    ¦ L
    && L
    ¦¦ L
    ?: R
    = += -= *= /= %= &= ^= ¦= <<= >>= R
    , L

unär, monadisch:  Operator hat einen Operanden.
binär, dyadisch:    Operator hat zwei Operanden.
ternär, triadisch:   Operator hat drei Operanden.

Die Operatoren  * + - &  werden  unär  als auch  binär  verwendet!
Das Komma wird als Komma-Operator und auch als Trenner/Punktuator verwendet!
Beim Programmieren immer an Rang und Zusammenfassungsrichtung denken!


Operatoren, kurz erklärt:

Zu jedem Operator wird nachstehend nur eine kurze Erklärung gegeben,
und zwar, wie die Operatoren schwerpunktmäßig verwendet werden.
Die Operatoren werden hier nicht erschöpfend erklärt, sondern andere Kapitel
ergänzen dies alles noch, implizit/explizit, von jeweils einem anderen Hauptthema ausgehend.

( )
Mit runden Klammern kann eine gewünschte Verknüpfungsreihenfolge erzwungen werden, falls der
vorgegebene Rang von Operatoren eine unerwünschte Verarbeitung zur Folge hätte.
Beispiel:  a= (b + c) * d;
Außerdem werden runde Klammern zur Abgrenzung, Einfassung, Zusammenfassung eingesetzt,
beispielsweise bei Bedingungen und Funktions-Aufrufen/-Argumentlisten.

[ ]
Eckige Klammern stehen hinter Array-Namen und geben das Array-Element an, auf das Zugriff
erfolgen soll:  array_name[index_ausdruck] .
Das Resultat eines Index-Ausdrucks muß ein Integer-Wert sein.
Auf das erste Array-Element wird mit Index 0  (array_name[0])  zugegriffen.
Der Name eines Arrays repräsentiert die Adresse des ersten Array-Elements;
ein Array-Name kann nicht Ziel einer Zuweisung sein, er kann nur lesend verwendet werden!
Eckige Klammern können desweiteren auch hinter einer Adressen-Variablen stehen
und können dann auch einen negativen Index enthalten:
int ia[10];
int *iap, i;
iap= ia+5;
i= iap[-2];   
/* entspricht: i= ia[3]; */
Bei Adressen-Ausdrücken haben [0] und  * die gleiche dereferenzierende Wirkung.
Bei  
[char==signed char]  auf negative Werte bzw.  >127  achten!
(unsigned char)  beseitigt solche Probleme.

->
Dies ist ein Zugriffsoperator, um von einer Struktur-Adresse ausgehend auf ein Mitglied
der Struktur zuzugreifen:
long l;
struct test { int i; char c; long l; };
struct test  T;
struct test *TP= &T;
l= TP->l;
l= (*TP).l;            
/*  *TP ist die Struktur T als Ganzes */
l= TP[0].l;
l= T.l;
l= (&T)->l;
Die letzten fünf Zeilen haben die gleiche Wirkung!
Man erkennt, daß es diesen Operator nur aus Komfortgründen gibt.
Der Name einer Struktur ist nicht deren Adresse, wie bei Arrays, sondern die Struktur als ganzes!

.
Der Punkt-Operator dient dem Zugriff auf Struktur-Mitglieder,
von einer Struktur ausgehend.
Siehe oben.

*
Dies ist der allgemeine Zugriffsoperator, um von einer Adresse ausgehend auf den Inhalt
des adressierten Objektes zuzugreifen (Dereferenzierung):
int i, *ip;
ip= &i;
*ip= 5;
i= 5;
Die beiden letzten Zeilen haben gleiche Wirkung.

&
Dies ist der Adressen-Operator, der die Adresse eines Objektes liefert.
Siehe oben.
int *ip, **ipp;
ipp= &ip;
register
-Variablen und Bitfelder haben keine &Adressen!
Man kann auch Teiladressen größerer Objekte erhalten,
beispielsweise Adressen von Array-Elementen und Struktur-Mitgliedern.
Letzlich hat jedes einzelne Byte von Objekten eine Adresse.

-  +
Bei unärer Verwendung wird ein Wert negiert:
i= -i;
Hierdurch wechselt der Wert in 'i' sein Vorzeichen.
Der Operator '+' dient hier nur Symmetriezwecken.

~
Komplementierung aller Bits eines Integer-Wertes (NOT/Einer-Komplement).
~00000000 == 11111111
~11111111 == 00000000
~11001101 == 00110010
unsigned long ul= ~(0UL);
Achtung, die hier gezeigte Zahlendarstellung zur Basis 2 (Dual-Zahlen) ist in C nicht möglich!

!
Logisches NOT/NICHT.
Aus einem Wert  0  oder  0.0  resultiert  1 .
Aus einem Wert ungleich  0 bzw. 0.0  resultiert  0 .
if (!a)  a= b+2;
i= !!25;
i= !!(1-3);
i
 erhält in beiden Fällen den int-Wert 1 .
(a) entspricht (a!=0), (!a) entspricht (a==0)
1 ist TRUE, 0 ist FALSE
Dieser Operator kann auch auf Adressen angewandt werden (Prüfung auf NULL-Pointer).

++   --
Inkrement / Dekrement:
Ein Variableninhalt wird um 1 oder 1.0 erhöht bzw. reduziert.
(Addition / Subtraktion von 1 oder 1.0)
Es gibt einen Unterschied, je nach dem, ob diese Operatoren vor oder nach einem
Variablennamen stehen:  Preinkrement/Predekrement  oder  Postinkrement/Postdekrement.
++i; i++; i+=1; i=i+1;
--i; i--; i-=1; i=i-1;
Diese jeweils vier Anweisungen haben den gleichen Effekt.
a= b + ++i + c;
Der Wert des Ausdrucks  ++i  ist der alte Wert des Inhalts von  i  plus 1.
a= b + i++ + c;
Der Wert des Ausdrucks  i++  ist der alte Wert des Inhalts von  i .
Achtung: der Inhalt von  i  wird (in beiden Fällen) möglicherweise erst beim Semikolon (;),
dem nächsten Sequenzpunkt, inkrementiert!
Man soll niemals den Inhalt ein und derselben Variable zwischen zwei Sequenzpunkten
mehr als einmal ändern!
a= b + i+1 + c, ++i;
a= b + i   + c, ++i;
Diese beiden Anweisungen zeigen das Sequenzpunkt-Verhalten der obigen Varianten.
Im Zusammenhang mit  ++ --  gibt es sehr erhebliche Unsicherheiten!
i-- = 2;
i++ = 2;
Das sind Fehler!  Schon allein von den Sequenzpunkt-Regeln her!
*a++ = *b++;
Das ist korrekt, da nur die Adressen geändert werden, nicht aber der dereferenzierte Zielinhalt.
Es wird ja an  a  nichts zugewiesen per  = , sondern an  *a .

(typ)
Typ-cast:  Umwandlung des Typs eines Objektes/Ausdrucks in einen anderen Typ.
Es können alle elementaren Typen und alle Adressen-Typen
untereinander umgewandelt werden.
unsigned u;  int i=100;  char c;  int *ipa, *ipb;
c= (char)i;
c=-2;             
/* c == 11111110 */
i= c;             /* i == -2 == 11111111111111111111111111111110 */
u= c;             /* u == 4294967294U */
u= (unsigned)c;                   /* u == 4294967294U */
u= (unsigned)(unsigned char)c;    /* u == 254 == 11111110 */
u= (unsigned char)c;              /* u == 254 */
c= 127;
u=i=c;            
/* u == i == c == 127 == 01111111 */
ipb= ipa + 1;
i= (int)(ipb-ipa);                        
/* i == 1 */
i= ( (unsigned)ipb - (unsigned)ipa );     /* i == 4 */
i= ( (char*)ipb - (char*)ipa );           /* i == 4 */
struct kfl { char a; unsigned char b; };
struct kfl *sp= (struct kfl*) &i;
++sp->b;
Die drei letzten Zeilen sind sicher 'bemerkenswert' - sie sind aber korrekt, weil  i  groß genug ist,
um die Struktur vom Typ kfl aufnehmen zu können und weil kein Misalignment auftreten kann.
char Buf[256];
for (i=0;  i<sizeof(Buf)/sizeof(long);  ++i)  ((long*)Buf)[i]= 0L;
Die letzte Zeile funktioniert fein mit Intel-x86-Prozessoren, ist aber generell gewagt!
Hier werden 64 longs auf 0 gesetzt, anstatt 256 Byte.
Mit anderen Prozessoren kann long-Misalignment auftreten und  256/sizeof(long) kann einen
Divisionsrest ergeben!
Aber, mit  union  ist sowas auch portabel lösbar.

sizeof
Dieser Operator ermittelt die Anzahl der Bytes von Typen
und von Objekten, Objekt-Teilen, Objekt-Elementen.
Bei Strukturen und Struktur-Typen können Füll-Bytes dabei sein - zwecks Alignment.
struct kfs { int i; char rel; } Kfs[2];
int Array[10];
sizeof(int)         
/* 4 */
sizeof(char)        /* 1 (ist immer 1) */
sizeof(char*)       /* 4 */
sizeof(i)
sizeof(struct kfs)  
/* 8 */
sizeof(*Kfs)        /* 8 */
sizeof(Kfs)         /* 16 */
sizeof(Kfs+0)       /* 4 */    /* Pointer-Kontext erzwungen: +0 */
sizeof(Kfs[0].rel)  /* 1 */
sizeof(Array)       /* 40 */
sizeof(*Array)      /* 4 */
sizeof(Array[0])    /* 4 */
sizeof(long[2][3])  /* 24 */
sizeof("")          /* 1 */
sizeof("abc")       /* 4 */
sizeof('A')         /* 4 */
sizeof((char)'A')   /* 1 */
Der Typ des Resultats von  sizeof  ist  size_t .
Fast immer 'unsigned':  typedef  unsigned  size_t;

*  /  %
Multiplikation, Division und Restwert-Division.
(% nur mit Integer.)
8/4 == 2
7/4 == 1
5/4 == 1
4/4 == 1
3/4 == 0
3.0/4.0 == 0.75
8%4 == 0
7%4 == 3        
/* 7/4 == 1 + 3/4 */
5%4 == 1
4%4 == 0

+  -
Addition und Subtraktion.
int  y, a=j, b=k, c=50, d=80;
y= a - b + c + d;
y= (int)( a - (long)b + c + d );
Achtung, die Reihenfolge, mit der sich der Compiler den Teilausdrücken/Operanden
zuwendet, ist beliebig!
(Zumindest bei älteren Compilern (<=1990) und bei je zwei Operanden nacheinander.)
Daraus folgt:
Es muß sichergestellt sein, daß keine denkbare Berechnungskombination
(zwischendurch) zu einem Überlauf des Zahlenbereichs führt.
Die letzte Zeile berechnet zur Sicherheit im long-Bereich.  (Ein Typ-cast reicht hier aus.)
Auf int-Breite erweitert der Compiler automatisch, und auf den Zuweisungs-Typ ebenfalls.
Die obenstehende Berechnung ist beispielsweise portabel, falls int nur 16 Bit hat.

<<   >>
Bitweises Schieben von Integer-Typen nach links und nach rechts.
(  signed char)11110000 >> 2        ==>11111100 !
(unsigned char)11110000 >> 2        ==>00111100
Beim Linksschieben braucht nicht auf das Vorzeichen geachtet werden.
Streng nach ANSI sollte man bei signed-Typen nicht einen Überlauf nach links erschieben.
Die in der ersten Zeile gezeigte Erhaltung des Vorzeichens bei vorzeichenbehafteten Typen
mit negativen Werten ist plattformabhängig!
Durch Links/Rechts-Schieben wird mit Potenzen von 2 (2^n) multipliziert/dividiert,
und zwar sehr viel schneller als per  * / .
3 Bits nach rechts ist eine Division durch 8,  wobei der Divisions-Rest vorher den niederwertigen
3 Bits entnommen werden kann!:   rest= i&7; i>>=3;    /* i>=0 */

<   <=   >   >=   ==   !=
Vergleichsoperatoren.
Kleiner, Kleiner_oder_Gleich, Größer, Größer_oder_Gleich, Gleich, Ungleich.
if (i==(int)u)  /*...*/;
while (ap != NULL)  { /*...*/ ++ap; }

&   ^   ¦
Bitweise:  UND(AND)-,  XODER(XOR)-,  ODER(OR)-Verknüpfung.
i= 32;                   00100000
i ¦=  (1 ¦ 4 ¦ 8);       00101101
i &= ~(1 ¦ 4 ¦ 8);       00100000
10000001 ^ 11111111  ==  01111110
if (i & (4¦8))  ...
if ((i&(4¦8))==(4¦8))  ...
Man erkennt, daß der Rang dieser drei Operatoren unglücklich festgelegt wurde.
Ein höherer Rang als der der Vergleichsoperatoren wäre besser.
Siehe auch:  ~

&&    ¦ ¦
Logisch:  UND-,  ODER-Verknüpfung.
if (!a&&b>=4&&(c¦¦d))  ...
if (r=0,  !a&&(r=1, b>6)&&(r=2, c))  ...
Bei diesen Operatoren wird gemäß der KO-Logik verfahren:
Sobald eine Bedingung vor  && 0 ist oder sobald eine Bedingung vor  ¦¦ 1 ist, wird der Rest
der &&- bzw. ¦¦-Verknüpfung ignoriert, denn dann steht fest, daß die jeweilige Bedingungskette
FALSE(0) bzw. TRUE(1) ist.
!a^!b  entspricht einem logischen XOR:  ^^ .
Siehe auch:  !

?:
Dieser ternäre Operator hat Ähnlichkeit mit  if()-else, ist jedoch weniger aufwendig zu schreiben
und kann mitten hinein in Ausdrücke gesetzt werden.
y= a>5 ? 1 : 0;
z= a + b + (a>2 ? a&12 : (++k, b-1)) - 24;
k ? printf("k!=0") : printf("k==0");
Man beachte in der vorletzten Zeile die Klammern, wegen des geringen Rangs von:  > ?:  ,

=   +=  -=  *=  /=  %=  &=  ^=  ¦=  <<=  >>=
Zuweisungs-Operator und zusammengesetzte Zuweisungs-Operatoren.
a= 1;
b += 2;    entspricht  b= b+2;
b -= 3+a;  entspricht  b= b-(3+a);
b *= a+1;  entspricht  b= b*(a+1);
s <<= 4;   entspricht  s= s<<4;
Wegen des geringen Rangs entfallen außerdem Klammern, wie oben zu sehen ist.

,
Mit dem Komma-Operator können kürzere Schreibweisen und kompakte
Syntax-Konstruktionen realisiert werden.
Es können Ketten von Quasi-Anweisungen innerhalb einer Anweisung gebildet werden,
ohne Blockbildung mittels geschweifter Klammern:
if (a>2&&b!=10)  a+=4, b=Fu(1), ++c;
else             a=b=0, --c;
if (k&8)  { a=2, b¦=k¦1, ++Err; break; }
Schlüsselwort-Anweisungen dürfen allerdings nicht Bestandteil einer Kommaliste sein!,
wie an der letzten Zeile zu sehen ist.
if (f>1)  i+= Fuu(a, b, (f+=2,c=a-f, c), d=D+1, e), i*=4;
Nur das dritte, vierte und das letzte Komma sind hier Komma-Operatoren!,
die anderen Kommata sind Trenner/Punktuatoren.
i= A[f=++k+4];
i= A[f=++k+4,  f];
Bewertet (hier als Array-Index) wird stets der letzte Ausdruck einer Komma-Liste.
(Insofern überhaupt ein Wert gebraucht wird.)
Die beiden Zeilen haben gleiche Wirkung.
Weitere Beispiele sind an anderen Stellen dieses C-Tutorials zu finden.

Bei Variablen-Definitionen können mehrere Variablen-Namen
hinter einer Typ-Angabe aufgelistet werden. (siehe weiter oben)
Hierbei fungiert das Komma aber wieder als Trenner, nicht als Operator.

Das Semikolon (;) ist kein Operator, sondern das Abschlußzeichen einer jeden Anweisung.
Ein Semikolon ohne Anweisung davor ist eine Leer-Anweisung.



^

Speicherklassen

Objekte, die außerhalb von Funktionen definiert werden, werden statisch im Speicher angelegt.
Sie sind im aktuellen C-Modul ab ihrer Definitionsstelle überall (global) sichtbar.
Ohne das Schlüsselwort  static  sind sie zusätzlich publik, nach außen hin sichtbar und können
in anderen C-Modulen mittels des Schlüsselwortes  extern  'hereingeholt' und
bekannt gemacht werden.

Alle statischen Objekte sind/werden beim Programmstart automatisch und typgerecht mit 0 initialisiert,
sofern sie nicht explizit initialisiert wurden.
Typgerecht:  es wird mit  0, +0, 0.0, +0.0, NULL  initialisiert - je nach Typ.
Es wird also nicht unbedingt nur mit 0-Bits initialisiert!
Das ist plattformabhängig.

static

Globale Objekte sind nur im aktuellen C-Modul bekannt:  Verkapselung der Namen/Bezeichner.
Innerhalb von Funktionen:  Es wird ein statisches Objekt angelegt. Der Name ist nur in der jeweiligen
Funktion bekannt, oder in einem darin verschachtelten Block.
static int i;
static int ii= 32;
Ein static-Objekt auch innerhalb einer Funktion wird
unbedingt nur einmal beim Programmstart initialisiert!

extern

extern long timezone;
extern char **environ;
extern char *envarr[];
extern unsigned __brkval;
extern int printf(const char *, ...);
Es werden externe Objekte, die sich in der Library oder anderen C-Modulen befinden,
im aktuellen C-Modul bekannt gemacht.
Der Compiler kennt dann diese Objekte und kann sie korrekt verwenden,
und der Linker verknüpft zum Schluß diese EXTRN-Symbole mit den zugehörigen
PUBLIC-Symbolen von außerhalb.
(Um die printf-Funktion bekannt zu machen, muß <stdio.h> inkludiert werden.)
Wenn ein externes Array bekannt gemacht werden soll, so muß es auch als Array
mit Dimensions[]-Klammern deklariert werden:
extern int  eia[];
und nicht als Adressen-Variable:
extern int *eia;

register

register int i, r;
Bei modernen Compilern ist dieses Schlüsselwort praktisch nicht mehr notwendig, denn diese
verwenden alle Prozessor-Register standardmäßig von selbst.
Man kann diese Automatik aber auch abschalten und sogar eine Register-Verwendung
ausschließen, wenn  register  nicht angegeben wurde!
Das ist natürlich compiler-abhängig.
Schaden kann die Angabe von  register  nicht, denn es verdeutlicht, welche Variablen
besonders intensiv benutzt werden.
Ältere Compiler für Intel-Prozessoren:
esi   edi   ebx/bl
 si    di
(Maximale Berücksichtigung von  register .)
register-Variablen haben keine Adresse:  &r
Bei zukünftigen Compilern könnte  register  erneut wichtige Bedeutung erhalten,
denn er kann damit hemmungslos parallelisieren, weil ohne Adresse == ohne Alias:  &r

auto

auto int i;
     int i;
Diese Zeilen haben gleiche Wirkung, denn  auto  ist Standard innerhalb von Funktionen.
Solchermaßen definierte Objekte werden dynamisch im (Stack-)Speicher angelegt.
Sie müssen vor ihrer ersten lesenden Benutzung auf einen bestimmten Wert gesetzt werden,
denn sonst haben sie einen zufälligen Inhalt!
Dies kann durch sofortige Initialisierung oder spätere Zuweisung geschehen.

Alle nichtstatischen Objekte werden beim Verlassen der jeweiligen Funktion zerstört !
Verwenden Sie niemals eine Adresse eines solchen Objektes nach dem Verlassen
der jeweiligen Funktion !
Eine Adressen-Verwendung in anderen verschachtelt aufgerufenen Funktionen ist korrekt,
denn der Aufrufer wird ja nicht verlassen.

Alle Definitionen und Deklarationen von Objekten innerhalb von Funktionskörpern
müssen nach einer öffnenden Block-Klammer ({) vor allen andersartigen Anweisungen stehen:
{   int i=2, a, r;
    char buf[256];
    if (
/*...*/)  { int k=0; /*...*/; }
    { static int krest;
      printf("%s\n",
/*...*/);
      
/*...*/
    }
    
/*...*/
}
Nach jeder Blockklammer  {  dürfen Objekte angelegt werden, die dann im jeweiligen Block
gültig und sichtbar sind.


register- und auto-Objekte werden bei jedem Funktionsaufruf neu angelegt,
und bei rekursiven Aufrufen, direkt oder indirekt, entstehen jedesmal zusätzliche Instanzen
dieser Objekte, die für jeden Aufruf ihren separaten Speicherplatz haben.
Solange eine Funktion/Block nicht verlassen wird, haben deren dynamische Objekte Bestand!
(Solange eine bestimmte Funktions-Instanz nicht verlassen wird, haben deren dynamische
  Objekt-Instanzen Bestand.)
Alle statischen Objekte hingegen haben zu jedem Zeitpunkt genau einen Speicherplatz.

Register werden natürlich nicht 'angelegt', aber deren Inhalt schon.
Außerdem werden ihre Inhalte nötigenfalls in den Speicher kopiert
und wieder zurückgeholt, so daß sich obiges Verhalten ergibt.



^

Adressen (Pointer, Zeiger)

Objekte sind in C:
Sämtliche Variablen jeglichen Typs, Arrays, Strukturen, Unionen, (Enumerationen,) und Funktionen.
Funktionen sind Kode-Objekte, die anderen sind Daten-Objekte.

Jedem Objekt wird bei seiner Definition (Beschreibung+Erzeugung) Speicherplatz zugewiesen.
Jedes Objekt hat seine eigene(n) -unveränderbare(n)!- Adresse(n).
Die Adresse eines Objekts ist die Adresse des ersten Bytes (kleinste adressierbare Speichereinheit),
das von dem Objekt im Speicher belegt wird.
Jede Objektadresse hat einen bestimmten Typ, der die Art und Weise des Zugriffs angibt.

    int i=2, *ip, **ipp;

    ip= &i;
    ipp= &ip;

Hier wurden angelegt/definiert:
Eine Variable  i  , die einen int-Wert (hier: 2) enthält,
eine Variable  ip  , die die Adresse einer int-Variable enthält,
eine Variable  ipp  , die die Adresse einer Variable enthält, die die Adresse einer int-Variable enthält.

&ipp  hat den Typ  int*** ;  prinzipiell könnte man diese Verschachtelung beliebig weitertreiben.

i  und  *ip  und  **ipp  haben den Typ  int  und liefern  2 .
ip  und  *ipp  haben den Typ  int*  und liefern  &i .
ipp  hat den Typ  int**  und liefert  &ip .

ip[0]  und  ipp[0][0]  liefern ebenfalls  2 .  Andere Indexwerte als  0  wären allerdings falsch.

    char *cp= (char*)&i;
    cp[0] == 2
    cp[1] == 0
    cp[2] == 0
    cp[3] == 0

Falls eine int-Variable aus 4 char-Elementen besteht, also sizeof(int)==4 ist,
kann wie obenstehend verfahren werden.
Ein int-Objekt hat in diesem Fall 4 Byte-Adressen.
Achtung, sizeof(int) und die zahlenmäßige Wertigkeit der Bytes des int-Objektes sind plattformspezifisch!
(Bei Intel-Prozessoren iX86 befindet sich das niederwertigste Byte auf der niedrigsten Adresse, usw.)

Das obige Beispiel mit (Adressen-casting*) geht auch größenmäßig umgekehrt:

    char A[5];  int *ip;
    ip= (int*)(A+1);
    *ip= 0;
    /* setzt das 2. bis 5. Byte von A auf 0 */

Hier kann jedoch -je nach Prozessor- ein Misalignment-Fehler passieren!  (nicht bei iX86)
Speicherobjekte sollen/müssen auf einer Adresse beginnen, die ohne Rest durch deren Byte-Größe
bzw. durch die Byte-Größe der Objekt-Elemente  teilbar ist.

Eine falsche Zugriffs-Ausrichtung im Speicher kann vorliegen, wenn auf ein Speicherobjekt
durch  (Adressen-casting*)  mit einem längeren Typ als dem Original-Typ zugegriffen wird.
Mit Hilfe einer  union  ist dies Problem aber portabel lösbar.

Addition, Subtraktion und Differenzbildung bei Adressen:

neue_Adresse = Adresse + i
im Quelltext
bewirkt auf Assemblerebene:
neue_Adresse = Adresse + i * sizeof(&Typ_von_Adresse)

ip += 1
bewirkt
ip += 1 * sizeof(int)

ip - 1
bewirkt
ip - 1 * sizeof(int)

i = (int)( ipb - ipa );
bewirkt
i = (ipb-ipa) / sizeof(int)

Wenn also (auf Quelltextebene) eine Adresse um 1 verändert wird, zeigt der neue Adressenwert
stets auf das nächste bzw. vorherige Element  (auf das erste Byte des Elements),
unabhängig vom Typ der Adresse.
Und bei Differenzen wird stets die Elemente-Differenz errechnet, nicht die Byte-Differenz.
Man beachte, daß  sizeof(char)==1  ist.
Deshalb kann durch Typ-casting  (char*)  auch immer die Byte-Differenz festgestellt werden.
(Bei bereits vorliegenden  char*  ist das natürlich überflüssig.)

Streng nach ANSI:
Vergleiche, Subtraktionen, Verknüpfungen zwischen zwei Adressen
sollen nur erfolgen mit Adressen, die von ein und demselben Objekt stammen!
Selbstverständlich müssen die Adressen Typgleichheit haben.

Adressen-Variablen sollen nur gültige Adressen enthalten, die auf bestehenden Speicherplatz
von Objekten zeigen - auch wenn sie gar nicht zum Zugriff benutzt werden!
Ausnahme:   Hinter das letzte Element eines Arrays darf gezeigt werden,
                          so als ob da noch ein weiteres Element wäre.


extern char **environ;
extern char* *environ;
/* Alternativ-Darstellung */

Dieses externe Objekt gibt es auf den meisten Plattformen tatsächlich.
Es ist eine Adressen-Variable, z.B. 4 Byte groß (sizeof(char**)==4),
die eine Adresse auf ein Objekt enthält, das wiederum eine Adresse auf char enthält;
die also eine Adresse auf ein  char*-Objekt enthält.

In diesem spezifischen Fall wird nicht nur auf ein einzelnes  char*-Objekt gezeigt, sondern auf
das erste Element eines Arrays aus  char*-Objekten.

static char  *envarr[]= { "PATH=...............",
"CDPATH=.............", /*...*/, };
char **environ= envarr;
char **environ=&envarr[0];
/* Alternative */

So könnte das prinzipiell extern programmiert worden sein.

  environ        zeigt auf die Basisadresse des Arrays:  char**
  environ[0]    
liefert den Inhalt des ersten Array-Elements:  char* : "PATH=..."
  environ[0][0]  
liefert das erste Element der ersten Zeichenkette:  char : 'P'
++environ[0][0]  
würde verändern zu:  "QATH=..."
++environ[0]    
würde den Inhalt des ersten Array-Elements erhöhen:  zeigte jetzt auf  "ATH=" .
++environ[0][0]  würde verändern zu:  "QBTH=..."
++environ       
zeigt jetzt auf das zweite Array-Element, also ein Durchlaufen der Zeichenketten.
 *environ        liefert jetzt die Adresse von  "CDPATH=..."
  environ[0]    
liefert jetzt die Adresse von  "CDPATH=..."
  environ[0][0] 
liefert jetzt:  'C' :  char
  environ[-1]   
liefert jetzt die Adresse von  "PATH=..."

environ  liefert stets das aktuelle Environment,
während  envp  dasjenige vom Zeitpunkt des Programmstarts liefert.
( LIB: getenv(), putenv() )


Sammlung weiterer Beispiele, die die Zusammenhänge klarmachen sollen:

 char *cp;
short *sp;
long *lp, *lpb;
void *vp;
// Assembler-Ebene:
++cp; // cp += 1 * sizeof(char); cp wird um 1 erhöht
++sp; // sp += 1 * sizeof(short); sp wird um 2 erhöht
++lp; // lp += 1 * sizeof(long); lp wird um 4 erhöht
lp+1 // lp + 1 * sizeof(long); Resultat == lp+4
lp-2 // lp - 2 * sizeof(long); Resultat == lp-8
++vp; // vp += 1 * sizeof(????); VERBOTEN !

((struct abc *)vp) += 3; // vp += 3 * sizeof(struct abc); (2)
(( long(*)[5][10] )vp) += 3; // vp += 3 * sizeof(long [5][10]), (2)
// vp += 3 * 200;

(uint)(lp-lpb) // (lp-lpb) / sizeof(long) !
(uint)lp - (uint)lpb // lp-lpb ! (1)
(char*)lp - (char*)lpb // lp-lpb


// Event. Misalignment:
((long*)cp)++; // cp+=sizeof(long) (2)
*((long*)cp) = 0; // DWORD=0 auf das cp zeigt (a)
*((long*)cp++) = 0; // cp+=1 nach Zuweisung (a)
*((long*)cp)++ = 0; // cp+=4 nach Zuweisung (nicht ANSI!,a)
*++((long*)cp) = 0; // cp+=4 vor Zuweisung (nicht ANSI!,a)
++*((long*)cp); // DWORD+=1 auf das cp zeigt (a)
(*((long*)cp))++; // dito

*(cp+256) =48; ++cp; // cp[256]=48, cp+=sizeof(char)
*(cp++ +256)=48; // dito
*(256+cp++) =48; // dito
(cp++)[256]=48; // dito
cp++[256] =48; // dito
(a)  event. Misalignment auf Nicht-x86-Prozessoren
(1) nicht vollkommen portabel: (unsigned)addr
Besser, bei Differenzen : i= (char*)lpb - (char*)lpa;
(2) nicht Strict ANSI, dennoch ziemlich portabel
cp= (char*)((long*)cp+1); ist voll portabel

.



^

Arrays  (Vektoren, Felder)

Ein Array kann als Kette von lückenlos aufeinander folgenden Objekten gleichen Typs bezeichnet werden.
Diese einzelnen Objekte sind die Elemente des Arrays.
Elementtypen können alle elementaren Datentypen, Strukturen, Unionen, Sub-Arrays,
Funktions-Adressen  und alle sonstigen Adressen sein.
Ein Array kann prinzipiell beliebig viele Dimensionen besitzen:  n-dimensional.
Array-Elemente sind dann (n>=2) selbst wiederum Arrays (Sub-Arrays).

Arrays können nicht als Ganzes an Funktionen übergeben und ebensowenig retourniert werden.
Array-Inhalte können aber per Adressenübergabe/-rückgabe zugänglich gemacht werden.
Arrays können nicht einander zugewiesen werden, Array-Elemente ja.
Mit Strukturen geht das aber alles - und Arrays können Struktur-Mitglieder sein!

    int A[5];
    A[0]=A[1]=A[2]=A[3]=A[4]= 258;   /* 258 == 0x00000102 */
    21002100210021002100
    00001111222233334444
    *...*...*...*...*...*
    10200 10208 10216
    10204 10212 10220

Ein 1-dimensionales Array namens  A  aus 5 int-Elementen.
Nach der Definition und der Mehrfachzuweisung ist die Belegung im Speicher beispielhaft gezeigt,
unter der Annahme, daß ein   int 4 Byte groß ist.
Die erste Zahlenreihe zeigt den Inhalt, die Zahlenwerte der 5 int-Elemente. (Intel iX86)
Die zweite Zahlenreihe zeigt den Zugriffsindex  [0]  bis  [4] .
Das erste Element hat stets den Index  [0] .
Die Zahlen 10200 bis 10216 sollen angenommene Adressen der 5 int-Elemente sein.
Das Array belegt im Speicher die Bytes mit den Byte-Adressen  10200 bis 10219.

Größe in Bytes:   sizeof(A)            == 20  (10220-10200==20).
Anzahl Elemente:  sizeof(A)/sizeof(*A) == 5  (20/4==5).

Ein Array-Name allein und ohne Zugriffsoperatoren repräsentiert (fast immer) die Basisadresse des Arrays:
Ein Array-Name bezeichnet keine Variable (mit Inhalt), 'ihm' kann beispielsweise nichts zugewiesen werden!
(++A;  würde ja bedeuten, daß das Array im Speicher verschoben werden müßte.)

Der Typ des Ausdrucks 'A' ist kontextabhängig:
sizeof(A): Typ von  A: int[5]
... = &A : Typ von  A: int[5]
           Typ von &A: int(*)[5]
ansonsten: Typ von  A: int *
&A:
seit ANSI-C89/ISO-C90 möglich.
Wie man sieht, ist der Typ abweichend im Zusammenhang mit den beiden Operatoren  sizeof  und  & .

    Adresse    Zugriff auf Adresse          Typ
    10200       &A[0]  oder  A+0  oder  A   int *
    10204 &A[1] oder A+1 int *
    10208 &A[2] A+2 int *
    10212 &A[3] A+3 int *
    10216 &A[4] A+4 int *
    10200 &A &A+0 int(*)[5]
    10220 ! &A+1 int(*)[5]
    printf("%u %d\n", A, A[2]);  /* Ausgabe: 10200 258 */

Zugriff auf den Inhalt:  A[i]  entspricht  *(A+i)    A[0]  entspricht  *A

    Definition:
      AdressenAusdruck[IntegerAusdruck]
    *(AdressenAusdruck+IntegerAusdruck)
    *(IntegerAusdruck +AdressenAusdruck)
      IntegerAusdruck [AdressenAusdruck]
    i[A]
    3["abcde"]
    Die beiden letzten Zeilen sind gültig!

Zwei-dimensionales Array:

    int AA[5][2];
    0000000011111111222222223333333344444444
    0000111100001111000011110000111100001111
    0000111122223333444455556666777788889999 /* 1-dimensional */
    Typ           Ausdruck      Alternative     relativ E./A.
    int            AA[0][0]     **AA            Element 0
    int AA[4][0] *AA[4] Element 8
    int* &AA[4][0] AA[4] Adresse 8
    int* &AA[4][1] AA[4]+1 Adresse 9
    int(*)[2] AA &AA[0] Adresse 0
    int(*)[2] AA+1 &AA[1] Adresse 2
    int(*)[5][2] &AA &AA+0 Adresse 0
    int(*)[5][2] &AA+1 &AA+1 Adresse 10 !
    &:  Operator & dort erst seit ANSI-C89 möglich.
    Vorher waren diese Typen nur per (cast) herstellbar.

AA[i][j]  entspricht  (*(AA+i))[j]  entspricht  *(*(AA+i)+j)  entspricht  *(AA[i]+j)

    int (*aap)[2]= AA;

AA[i][j]  entspricht  aap[i][j]

    int *ip= (int*)AA;

AA[i][j]  entspricht  ip[i*2+j]

int *aap[2]  ist ein Array aus 2 Elementen vom Typ  int* .
int (*aap)[2]  ist eine Adresse auf ein Array aus 2 Elementen vom Typ  int .
[]  hat Vorrang vor  *  !

Siehe oben:
int AA[5][2];
int(*)[2]     
(Typ von AA und ip)
AA[i][j]
ip[i*2+j]
Man erkennt, warum bei Array-Pointern der Index ganz links wegfällt:
Der Compiler kann bei seiner Zugriffsberechnung  [i*2+j]  mit  [5]  gar nichts anfangen!
Dieser Index wird durch  i  (variabel) repräsentiert.

    int AAA[5][2][3];
    int (*aaap)[2][3];
    int *ip= (int*)AAA;

AAA[i][j][k]  entspricht  ip[i*2*3 + j*3 + k]

Der Compiler rechnet  ip + (i*2*3+j*3+k)*sizeof(int)  und erhält so die Zugriffsadresse.

AAA[4][1][2]:  (4*6+1*3+2)==(29)
Das Array hat  5*2*3==30  int-Elemente, und [29] ist das 30ste.

Folgendes ist in der Praxis nicht selten nützlich:

    char A[3][256];
    A[0]
    /* Adresse des ersten Array-Teiles: 0, Typ: (char*) */
    A[1] /* Adresse des zweiten Array-Teiles: 256, Typ: (char*) */
    A[2] /* Adresse des dritten Array-Teiles: 512, Typ: (char*) */


Beim Anlegen eines Arrays muß die Nicht-Pointer-Form mit Angabe aller Dimensionen
in  []  verwendet werden:  A[2][3][4]
Falls dabei gleichzeitig initialisiert wird, können Größenangaben in [] weggelassen werden,
da die Initialisierung die Größen bestimmt.
Bei Deklarationen in Funktionsparameterlisten oder nach  extern  darf die Größenangabe in der
nach dem Array-Namen folgenden eckigen Klammer  A[]...  weggelassen werden.
In Funktionsparameterlisten kann/sollte auch alternativ die Pointer-Form:  (*Ap)[3][4]
verwendet werden, bei der generell die innerste Dimensionsklammer fehlt.
(siehe weiter oben:  *argv[]  <-->  **argv)


Man beachte nachfolgend den Unterschied:

    static char *A[4]=     { "aaaaaa", "bbbbBBBBbbbb", "ccc" };
    static char AA[4][13]= { "aaaaaa", "bbbbBBBBbbbb", "ccc" };
    sizeof(A ) == 16
    sizeof(AA) == 52 == 6+7 + 12+1 + 3+10 + 0+13

A[i]  und  AA[i]  haben beide den Typ  char* .
A[1][4]  und  AA[1][4]  liefern beide das Zeichen  'B' .

A  ist ein Array aus 4 Adressen auf char,
AA  ist ein Array aus 4x13 char.

Zeichenkettenkonstanten sind eindimensionale Arrays.
Dennoch finden oben keine Zuweisungen von Arrays statt, sondern Initialisierungen.
Compiler, die solche Initialisierungen auch bei nichtstatischen Objekten zulassen,
legen intern ein statisches, solchermaßen initialisiertes Zweit-Objekt an und kopieren es bei jedem
Funktionsaufruf/Überlaufen in das nichtstatische hinein!
Also:  Verarscht, verarscht, sprach der Hahn!


Sammlung weiterer Beispiele, die die Zusammenhänge klarmachen sollen:

       char C[2][3][4];       // 2*3*4= 24 Elemente: 0...23
       000000000000111111111111   [2]
    000011112222000011112222 [3]
    012301230123012301230123 [4]
    ********** * * * *
    0123456789 12 16 20 23 [24] // 1-dimensional:

    C[1][1][1] char Element 17
    &C[1][1][1] char * Adresse 17
    C[1][1] char * Adresse 16
    C[1][1]+1 char * Adresse 17
    C[1] char (*)[4] Adresse 12
    C[1]+1 char (*)[4] Adresse 16
    C char (*)[3][4] Adresse 0
    C+1 char (*)[3][4] Adresse 12
    &C char (*)[2][3][4] Adresse 0
    &C+1 char (*)[2][3][4] Adresse 24 !
    *C char (*)[4] Adresse 0
    *C+1 char (*)[4] Adresse 4
    **C char * Adresse 0
    **C+1 char * Adresse 1
    ***C char Element 0

Zeiger auf ein zweidimensionales Array aus 3x4 char-Elementen, und,
Array, das einen Zeiger auf ein Array aus 3x4 char-Elementen enthält:

    char C[2][3][4];
    char (*cp)[3][4]= C; // der erste Index 'fehlt'
    char (*cpa[1])[3][4];
    cpa[0]= C;

    C[1][1][1]=4;
    // 4 Element 17
    cp[1][1][1]=4; // 4 Element 17

Alle Operationen mit cp und C sind gleichwertig, solange der Inhalt von cp nicht verändert wird.
cp++ erhöht cp um 12;
C++ ist falsch, weil C keine Variable ist, sondern den Anfang eines Objektes im Speicher repräsentiert.

    &cp         char (**)[3][4]     Adresse von cp
    cpa char (**)[3][4] Basisadresse von cpa
    C char (*)[3][4] Basisadresse von C
    &C char (*)[2][3][4] Basisadresse von C

&C  ist kein Fehler mehr seit  ANSI-C89/ISO-C90.

Speicher-Allokation mit der Library-Funktion  malloc() :

    char(*bp)[80][2]=  (char(*)[80][2]) malloc(
    sizeof(char[z_max+1][80][2]) );
    /* z_max==Konst. */
    bp[z][s][1]= 0x07;

Von  malloc() zu liefernde Bytes:  n= (z_max+1)*80*2*sizeof(char)
s: 0...79

    char C[2][3][4];
    char *cp = (char *) C;
    char (*p)[6]= (char(*)[6]) C;
    char c;

    c= **p++;
    // c = Element 0 von C
    c= **p++; // c = Element 6 von C
    c= **p; // c = Element 12 von C
    c= p[1][5]; // c = Element 23 von C
    p-=2;
    c= p[1][5];
    // c = Element 11 von C
    c= p[2][0]; // c = Element 12 von C

Der Speicherplatz eines Objekts kann durch Typ-casting (s.o.: p,malloc) typmäßig
beliebig benutzt werden.

    long La[24];

    La[1] long Element 1 von La
    &La[1] long * Adresse 1(4) von La
    La long * Adresse 0(0) von La
    &La long(*)[24] Adresse 0(0) von La
    *La long Element 0 von La

Byteadressen (Assembler-Ebene) in Klammern () .

Ein physikalisch eindimensionales jedoch typmäßig zweidimensionales Array:

    char C1[1][10];     // 10 Elemente: 0...9

    C1[0][1] char Element 1 von C1
    C1[0][9] char Element 9 von C1
    C1[0] char * Adresse 0 von C1
    C1 char (*)[10] Adresse 0 von C1
    &C1 char (*)[1][10] Adresse 0 von C1
    *C1 char * Adresse 0 von C1
    **C1 char Element 0 von C1
    char CA[8];

    CA[3]=6;
    // Zuweisung von 6 an das vierte Element
    *&*&CA[3]=6; // dito
    (&((&CA[3])[ 0]))[0]=6; // dito
    (&((&CA[3])[-3]))[3]=6; // dito
    (&((&CA[0])[ 3]))[0]=6; // dito

*  und  &  heben sich gegeneinander auf.
[]  und  &  heben sich gegeneinander auf,
wobei aber die Wirkung eines Indexwertes ungleich 0 erhalten bleibt.
Daraus folgt die Austauschbarkeit von  *  und  [] , (bei  [0])



^

Strukturen, Unionen, Bitfelder, Enumerationen

Strukturen:

Strukturen dürfen sämtliche Datenobjekte in beliebiger Mischung enthalten.
Die in einer Strukturdefinition angegebenen Objekte nennt man Mitglieder (members).

Rekursive Strukturen sind zwar nicht möglich, aber Strukturen können ein
Adressen-Objekt des eigenen Typs enthalten - einen Zeiger auf den eigenen Typ.
(Der Adressenwert kann tatsächlich auch die eigene Adresse sein.)

Im Unterschied zu Arrays können Strukturen als Ganzes einander zugewiesen werden,
falls der Strukturtyp der gleiche ist.
Analog dazu können sie an Funktionen als Ganzes übergeben als auch retourniert werden.

Der Name einer Struktur ist diese Struktur als Ganzes.
Die Adresse erhält man so:  &Strukturname
Bei einem Array mit Strukturen als Elemente repräsentiert der Array-Name
die Adresse der ersten Struktur im Array.

Strukturen können auf verschiedene Weise deklariert und definiert werden:

  • Nur einen Struktur-Typ mit Etikett (aber ohne Namen) deklarieren.
    Es wird dann kein Objekt im Speicher angelegt.
    Ein Etikett unterscheidet Strukturtypen.
  • Namen des Speicherobjektes gleich mit angeben.
    Oder auch gleich mehrere Namen angeben.
    Ohne Etikett möglich, aber weitere Objekte dieses Typs können
    dann später nicht mehr angelegt werden.
  • Speicherobjekte später anlegen, mit Hilfe des zuvor definierten Strukturtyps.
  • Speicherobjekte sofort als auch später anlegen.
  • Beim Anlegen von Sofort- oder Später-Speicherobjekten wahlweise
    Initialisierungs-Listen angeben.
  • typedef  verwenden. (siehe FILE-Typ an anderer Stelle.)
    struct etikett { /*...*/ };
    struct { /*...*/ } S;
    struct se_t { /*...*/ } SE, *pSE, SEA[4]={ /*init*/ };
    struct sa_t { int i; char ca[4]; };
    struct sb_t { struct sb_t *p; int i; struct sa_t saA[12]; };
    struct sb_t SB1,SB2, *pSB= &SB1;
    SB2= SB1;
    SB2= *pSB;
    /* Struktur kopieren, genau wie zuvor */
    SB1.p= pSB;
    ++SB1.p->p;
    /* entspricht: ++SB1.p; SB1.p == &SB1+1 */
    ++pSB->i; /* SB1.i += 1 */
    SB1.saA[3].i= 2;
    SB1.saA[3].ca[2]+= 4;
    &SB1.saA[2]
    /* Adresse 3. Struktur: (struct sa_t*) */
    SB1.saA /* Adresse 1. Struktur: (struct sa_t*) */
    pSB->saA[1]= pSB->saA[0]; /* Struktur sa_t kopieren */

Namen der Mitglieder eines Strukturtyps sind trotz eventueller Gleichheit konfliktfrei
zu allen Namen außerhalb dieses Strukturtyps!


Unionen:

Unionen gleichen Strukturen bis auf einen Punkt:
Alle Mitglieder teilen sich ein und denselben Speicherplatz!
Die Mitglieder dürfen byte-mäßig unterschiedlich groß sein;
die Größe einer Union wird vom größten Mitglied bestimmt.

Die Ausrichtung im Speicher (Alignment) paßt für das Mitglied
mit dem größten Elementartyp an seiner Basisadresse.

    union ua_t { char cBuf[100*sizeof(long long)];
    long lBuf[(101*sizeof(long long))/sizeof(long)];
    unsigned long long ullBuf[100] } Ucl;
    for (i=0;  i<100;  ++i)  Ucl.ullBuf[i]= 0xA5A5A5A5A5A5A5A5ull;

Unionen eignen sich für Konvertierungen, Umsetzungen, Speicherfülloperationen
und Objektinhaltsanalysen ohne Alignment-Probleme.
(siehe auch Kapitel: "Tips/Hinweise")

Der Speicherbedarf kann reduziert werden, falls mit verschiedenen Mitgliedstypen
nacheinander gearbeitet werden kann.

(long long:  C99)


Bitfelder:

Bitfelder können nur innerhalb von Strukturen und Unionen definiert werden.
Dies wird gemacht, indem ein Name folgendermaßen ergänzt wird:

    name:bit_anzahl

Bitfelder sollen den Typ  unsigned  haben.
(Mehrere) Bitfelder belegen Teile einer oder mehrerer  unsigned-Einheit(en).
int  ist auch erlaubt, aber eine Vorzeichenberücksichtigung ist nicht garantiert.
Bitfelder haben keine Adresse:  &bitfeld

In aller Regel verarbeiten die Compiler jedoch auch die schmaleren unsigned-Typen
zur Aufnahme von Bitfeldern, bis hinab zu  unsigned char .
(Das konnte ich schon mal gut gebrauchen, bei der Umwandlung eines exotischen
48-Bit-Gleitkomma-Formates.)

Auf ein definiertes Bitfeld von beispielsweise 12 Bit Breite kann im Zahlenbereich
von  0...(2^12-1) == 0...4095  zugegriffen werden.
Eine solche Verwendung ist grundsätzlich portabel, solange unsigned >=12Bit breit ist.

Achtung, die nachfolgenden Bit-Darstellungen der initialisierten Werte sind typisch
für Intel-Plattformen, sie sind nicht allgemeingültig!
Die resultierende Bit-Repräsentation von Bitfeldern innerhalb der Aufnahmeeinheit(en)
ist implementationsabhängig!

    struct bit { ushort a:1, b:2, c:3, d:4; } bf = {1,2,4,8};

Für diese Bitfelder initialisieren Compiler in der Regel ein WORD folgendermaßen:

    0x0225 = 000000_1000_100_10_1b
    = 0 8 4 2 1

    struct bit Bitf[512];
    // enthält 2048 Bitfelder
    long l= 0xbaL; // Wert ist zu groß (s.u.)

    bf.a= 0;
    bf.d= 11;
    Bitf[100].b= 3;
    Bitf[511].c= l;
    // Pauschalmaßnahme des Compilers:
    // (l & 7) = 2
-----------------------------------------
    struct bit { ushort a:1,       // 1
    b:2, // 2
    :4,
    c:3,
    // 4
    d:4, // 8
    :0,
    e:5;
    // 16
    } bf= { 1,2, 4,8, 16 };

Vorstehend initialisiert der Compiler (in der Regel) zwei WORDs (bf) folgendermaßen:

    0x0010 0x2205 = 00000000000_10000  00_1000_100_0000_10_1
    = 16 8 4 2 1

    // Bitf[] enthält jetzt 2560 Bitfelder
    struct bit Bitf[512]; // in 512 DWORDs

:4  ist ein Füller, und  :0  bewirkt einen Sprung zum nächsten Wort.
Das Layout von Bitfeldern sollte man explizit selbst verwalten.
Wird  
:0  nicht angegeben, so führt das zwar (meist) zum gleichen Ergebnis,
aber dieses Verhalten des Compilers ist nicht garantiert.

In einer Struktur dürfen natürlich Bitfelder und 'normale' Mitglieder gleichzeitig
und in beliebiger Reihenfolge vorkommen.
Eine 'normales' Mitglied bewirkt dabei den Abschluß eines vorhergehenden Bitfeldes,
so als enthielte es gleichzeitig  
:0 .
Ein Bitfeld, vor dem sich kein anderes Bitfeld befindet, belegt also stets den Anfang einer neuen Worteinheit.


Enumerationen:

    enum { null, eins, zwei, drei, vier };

Die Namen  null ... vier  sind jetzt mit den konstanten  int-Werten  0  bis  4  verknüpft
und können anstelle der fünf  int-Werte eingesetzt werden.
Die Namen nennt man: Enumeratoren.

    enum ft { False, True };
    enum monate { Jan=1,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec };
    enum { zehn=10, elf, Zehn=10, sechzehn=16, siebzehn };

.



^

Initialisierungen

Initialisierungen können als sofortige Zuweisungen bezeichnet werden,
die unmittelbar nach/bei dem Anlegen eines Objekts im Speicher vorgenommen werden.

Alle statischen Objekte sind/werden nur einmal beim Programmstart initialisiert,
und zwar typgerecht mit Null, falls keine explizite Initialisierung vorhanden ist.
Dynamische Objekte innerhalb von Funktionen (auto,register) werden beim Eintritt
in die Funktion (jedesmal neu) angelegt und haben danach einen zufälligen Inhalt.
Vor dem ersten Lesezugriff darauf muß also irgendeine Zuweisung/Initialisierung erfolgt sein.
Sie werden bei jedem Überlaufen initialisiert (bei expliziter Angabe).

    static int I, D=4;
    int i= 0;
    int k= '5';  double d= 0.0;
    int monat=6, jahr=2000, tag, stunde=0, lv=120, rv=lv*2;

    if (!I && a+k > 10) D=8, ++I;
    while (lv<240)  { int j=1; /*...*/ lv+=j; }

Man beachte, daß  D=4  nur beim Programmstart einmalig erfolgt, auch wenn sich die betreffende
Zeile in einer Funktion befindet.
D=8  wird allerdings jedesmal ausgeführt wenn die if-Bedingung zutrifft.
In der while-Schleife wird  j  bei jedem Schleifendurchlauf mit  1  initialisiert.
k  enthält  53 .

    static char A[5]= { 10, 14, 'z', 22 };

Das fünfte und letzte Array-Element ([4]) wird implizit mit  0  initialisiert;
Nicht angegebene Restelemente werden stets auf  0  gesetzt.
Ältere Compiler können nur statische Objekte mit solchen { Init-Listen }
oder  "zeichenkettenkonstanten"  initialisieren.

    static char A[ ]= { 10, 14, 'z', 22 };

Dies Array hat nur  4  Elemente, da die Größenangabe fehlt.
In solchen Fällen bestimmt die Init-Liste die Größe.

    static char A[4]= "abc";
    static char A[4]= { 'a', 'b', 'c', 0 };
    const char *cp = "abc";

Die beiden Array-Definitionen haben das gleiche Resultat.
Anstelle von  0  kann hier auch  '\0'  verwendet werden.
In der ersten Zeile ist  A  keine Zeichenkettenkonstante, wie man vielleicht denken könnte;
dazu müßte man  const  verwenden.
 A  ist ein Array, das mit 4 Zeichen initialisiert ist.
cp  ist eine Adressen-Variable, die mit der Adresse einer Zeichenkettenkonstanten initialisiert wurde.

    static char A[2][3]= { {3,4,5}, {20,24,28} };
    struct { int a[3], b; } AS[]= { {{1,0,3}, 8}, {{2,0,4}, 9} };

Arrays, Strukturen und Unionen müssen mit (mindestens) einem Paar geschweifter Klammern
initialisiert werden.
Daraus folgt, falls diese Objektarten ineinander verschachtelt sind, daß auch verschachtelte Paare
geschweifter Klammern vorhanden sein müssen.
AS  ist ein Array aus zwei Struktur-Elementen.

Bei Unionen kann nur gemäß dem ersten Mitgliedstyp initialisiert werden.



^

Funktionen

Funktionen sind Kode-Objekte, während Variablen, Strukturen, etc. Daten-Objekte sind.
Der Inhalt von Funktionskörpern wird vom Compiler in Prozessor-Instruktionen übersetzt.

Der Name einer Funktion (allein) repräsentiert ihre Adresse, eine Kode-Adresse.
Funktions-Adressen können in Datenobjekte gespeichert werden.
Funktionen können direkt, als auch mittels ihrer gespeicherten Adresse aufgerufen werden.
(siehe unten)

Funktionen können beliebig viele Argumente haben (auch gar keines), die beim Aufruf
an die jeweilige Funktion übergeben werden.
(Innerhalb von Funktionen spricht man von 'Parametern'.)
Funktionen können einen Wert an die jeweilige Aufrufstelle zurückgeben, oder auch nicht (void).

Beim Aufruf werden die Argumentwerte kopiert, und diese Kopien werden an die Funktion
übergeben.
Argumentwerte sind Objektinhalte oder Objekt-Adressen.
(Wobei Objektinhalte (von Adressen-Variablen) natürlich auch Objektadressen sein können.)
Diese Argumentwerte-Kopien (jetzt Parameter) 'gehören' der Funktion und
können innerhalb der Funktion beliebig verwendet - auch verändert werden, ohne daß die
Argument-Objekte an der Aufrufstelle verändert werden!

Argumentwerte werden mit mindestens int-Breite kopiert.
Das heißt, es findet zuvor nötigenfalls eine Erweiterung auf  int/unsigned  statt.
(Siehe weiter unten:  <stdarg.h>)

Sobald eine Funktion die Adresse eines Objektes erhalten hat, kann sie natürlich durch
Dereferenzierung dieser Adresse ( *  [i] ) das betreffende Objekt verändern!
Und umgekehrt: falls eine Funktion die Adresse eines ihr gehörenden statischen Objektes
an die Aufrufstelle liefert, kann von der Aufrufstelle her dieses Objekt verändert werden.
(Mit globalen Variablen können solche Effekte natürlich auch erreicht werden.)

           int Fua(int, long);    /* Prototypen */
    static int Fub(int, int, int*);


    int Fua(int i, register long l)
    {
    int a=0, b[4];
    /* ... */
    l+= (long) Fub(++i, a, b+1);
    /* ... */
    return ( a+(int)l );
    }


    static int Fub(int i, int i1, int *ip)
    {
    int ab=32;
    /* ... */
    *++ip-=2;
    /* b[?] wird verändert */
    return (ab);
    }

Man beachte, daß zwischen den Parametern  i,i1,ip  und  der auto-Variablen  ab  hinsichtlich
ihrer Benutzung und Gültigkeitsbereich kein Unterschied besteht!
Alle diese Objekte wurden beim Aufruf der Funktion dynamisch erzeugt;
die Parameter wurden lediglich von außerhalb initialisiert.


Funktions-Adressen:

    int(*FuaP)(int,long)= Fua;
    i= (*FuaP)(2, 0L);
    i= ( FuaP)(2, 0L); /* ohne * geht's auch */

Anlegen einer Funktionsadressen-Variablen  FuaP  und Aufruf von Fua über ihre Adresse.
FuaP  kann Adressen beliebiger anderer Funktionen des gleichen Typs aufnehmen.

    printf("%u %u\n", main, main+1);

Na sowas! - Man könnte sogar erste Funktions-Instruktionen überspringen...!       =8-O


Funktion mit variabel vielen Argumenten (eingeschränkt portabel):

Für vollkommene Portabilität sollten die Makros  va_list, va_start, va_arg, va_end
aus  <stdarg.h>  verwendet werden:  siehe unten.

Folgendes Konzept ohne <stdarg.h> ist aber grundsätzlich flexibler, offener und
-zumindest einzeln betrachtet- leichter zu handhaben:

    static int CatS(char *, char *, ...);


    static int CatS(char *S0, char *S1, ...)
    {
    register char *s;
    if (s=S0, s!=NULL) { register char *sa, **a;

    for (a=&S1; (sa=*a, sa!=NULL); ++a)
    while (*s=*sa, *sa) ++s,++sa;
    }
    return ((int)(s-S0));
    }


    CatS(ziel, a, bp, s1, (char*)0);
    l+= CatS(ziel+l, a, bp, s1, (char*)0);

Die Funktion kopiert beliebig viele Zeichenketten hintereinander in einen ziel-Puffer.
Endmarkierung ist ein übergebener NULL-Pointer.
Die Anzahl kopierter Zeichen ohne die abschließende '\0' wird retourniert.

Adresse des zweiten Parameters:  a=&S1
Die Parameter werden 'abgeklappert' durch:  ++a
Die while-Schleife kopiert die einzelnen Zeichenketten.
*s=*sa  kopiert '\0' bevor darauf geprüft wird.

Bei dieser Funktion haben alle Parameter (alle: char*) gleiche Länge.
Bei gemischten Parametertypen oder Typen mit kleinerer Breite als 'int' sollten die Makros
aus <stdarg.h> verwendet werden.
Es sei denn, Sie müssen etwas machen, was diese Makros nicht erlauben!
Aber dann informieren Sie sich zunächst anhand der weiter unten gezeigten Makro()-Analyse;
in solchen Fällen müssen Sie -plattformspezifisch- genau wissen, was Sie da tun!


Funktion mit variabel vielen Argumenten:  Voll portable Variante:

    #include <stdarg.h>
    static int CatS(char *, ...);


    static int CatS(char *S0, ...)
    {
    register char *s;
    if (s=S0, s!=NULL) { register char *sa;
    va_list a;
    for (va_start(a,S0); (sa=va_arg(a,char*), sa!=NULL); )
    while (*s=*sa, *sa) ++s,++sa;
    va_end(a);
    }
    return ((int)(s-S0));
    }


va_start(a,S0)  initialisiert quasi  a  bereits mit  S1 .
Der erste  va_arg()-Aufruf liefert daher  S1 .
va_arg()  justiert nach der Wertlieferung jeweils auf den nächsten Parameter,
gemäß dem angegebenen Typ des aktuellen Parameters.

Auf vielen typischen Intel-Plattformen machen diese Makros  va_xxx  das gleiche wie die
weiter oben gezeigte  CatS()-Variante ohne <stdarg.h> .
va_end()  ist als  ((void)0)  definiert und macht gar nichts.
Siehe unten.

Es soll laut ANSI-Standard der letzte Parameter vor  ,...  an  va_start()  übergeben werden.
Deshalb wurde bei dieser CatS()-Variante nur S0 als einziger sichtbarer Parameter definiert.


<stdarg.h>

    Typ: va_list
    void va_start(va_list ap, parmN);
    type va_arg(va_list ap, type);
    void va_copy(va_list dest, va_list src);
    // Neu ab C99
    void va_end(va_list ap);

va_end(ap)  soll einmal aufgerufen werden:
Sobald eine Benutzung von diesem  ap  stattfand durch  va_start  oder  va_copy .
Bevor  va_start  oder  va_copy  erneut verwendet werden mit diesem  ap .

Da  va_copy()  neu ist ab C99, wird nachstehend eine Analyse einer typischen
stdarg-Definition (iX86) gemacht, und ein  va_copy()  für eine typische Plattform konstruiert:

typedef char*  va_list;

#define va_start(ap,pn) ((void)(ap = (va_list)((char *)&pn \
+ ((sizeof(pn)+(sizeof(int)-1)) & ~(sizeof(int)-1)))))

#define __adjust(type) \
((sizeof(type) + sizeof(int) - 1) & ~(sizeof(int) - 1))

#define va_arg(list, type) \
(*((type *)((list += __adjust(type)) - __adjust(type))))

#define va_end(ap) ((void)0)
va_start(a,S0)
a= (char*) { (char*)&S0 + [(sizeof(S0)+(4-1)) & ~(4-1)] }
a= (char*) { (char*)&S0 + (szS0+3 & ~3) }
a= (char*) ( (char*)&S0 + X )
sizeof(S0)       X
1 4
2 4
3 4
4 4
5 8
8 8
9 12
arg= va_arg(a,typ)
arg= *( (a += adjust(typ)) - adjust(typ) )
//typ für arg weggelassen

Man erkennt, daß  va_start()  den Parameter-Zeiger  a  mit der auf  &S0  folgenden
Parameter-Adresse setzt, in Abhängigkeit von der Byte-Breite von  S0 .
Und offensichtlich haben Funktionsparameter mindestens eine Breite von 4 Byte, die auch nur
in 4er-Schritten erhöht wird.
(int-Erweiterung beim Funktionsaufruf!  Alignments!)
(Maschinen-Wort == 32 Bit == 4 Byte == sizeof(int))
Man erkennt, daß  va_arg()  den Parameter-Zeiger  a  typgerecht auf die nächste
Parameter-Adresse setzt, aber dann wieder diesen Wert auf den aktuellen Parameter
zurücksubtrahiert und diesen Adressenwert auch sogleich dereferenziert, damit  arg  den
eigentlichen Parameter-Inhalt erhalten kann.

Sie können an dieser Analyse erkennen, was Sie beachten müssen, falls Sie diese Makros aus
irgendwelchen Gründen nicht verwenden können oder wollen.
Damit hätten Sie aber plattformspezifisch programmiert!

Der neue C99-Makro:   va_copy(va_list ziel, va_list quelle) :

    #define va_copy(ziel,a)  ((void)(ziel=(a)))

Ist irgendwie zum Lachen - was?!
Dieser Makro paßt -streng genommen- nur zu genau den obigen anderen Makros!

Mit  va_copy  kann ein gültiger Parameterzeiger-Wert zu einem beliebigen Zeitpunkt
abgespeichert werden, um beispielsweise einen Teil der Parameterliste erneut zu durchlaufen:

    f= va_arg(a,int);
    if (n==4) va_copy(a_safe, a);

.


Funktionen rekursiv aufrufen:

Die folgende Funktion ruft sich direkt rekursiv selbst auf.
Sie errechnet die Fakultäten bis  8!:  1, 2, 6, 24, 120, 720, 5040, 40320

Die normalen Startwerte beim Hauptaufruf wären:  1,1  statt  0,8
Aber mit  F  wurde eine Einstellung der höchsten zu berechnenden Fakultät realisiert.

    static void Rek(int i, int m)
    {
    static int F;
    if (!i) F=m, m=++i;

    printf(" %d", m);
    if (++i<=F) Rek(i, m*i);
    else printf("\n");
    return;
    }


    Rek(0, 8);

Die Parameter  i,m  werden bei jedem Aufruf neu angelegt.
Beim achten Aufruf existieren je acht Instanzen von  i,m  im (Stack-)Speicher.
Von  F  gibt es nur eine Instanz, vom Programmstart her (static).

Funktionen können sich auch über andere Funktionen hinweg indirekt selbst aufrufen.


Eine Dateiliste rekursiv herstellen:

Die folgenden Funktionen bilden die Library-Funktion  ftw  nach, die nicht bei allen Systemen
anzutreffen ist.
(ftw:  Walk a file tree)
Diese Maßnahme wurde bei einem alten Programm bei einer Portierung vorgenommen.
Ftw_r  ruft sich direkt -rekursiv- selbst auf.
FTree  erhält über Pointer-Aufruf die einzelnen Pfadnamen + Infos darüber.

#         include <dirent.h>
/*# include <ftw.h>*/
# include <sys/stat.h>

static int Ftw(char *, int(*)(char*,struct stat*,int), int);
static int Ftw_r(char *, int);
static int FTree(char *, struct stat *, int);
static void FileTree(void);
/* Start-Funktion */

# define FTW_F 1
# define FTW_D 2
# define FTW_DNR 4
# define FTW_NS 8

static int (*
Fnp)(char*,struct stat*,int);


static int Ftw(char *p, int(*
fn)(char*,struct stat*,int), int nsub)
{
char pfad[6*512];
register int l;
Fnp= fn;
if (nsub<=0) return (0);
l= CatS(pfad, p, (char*)0);
return (
Ftw_r(pfad, l) );
}



static int
Ftw_r(char *pfad, int pl)
{
static struct stat sstat;
register struct dirent *dp;
DIR *dirp;
register int df, l;
if ((dirp= opendir(pfad))==NULL) { Perr('b'); return (0); }
if (pl+1+256+1>=6*512) {
write(2, "\r\tLIMIT: Ziel-Puffer\a:\r\n\r\t", 26);
write(2, pfad, pl);
return (0);
}
while ( (dp=readdir(dirp))!=NULL ) { int r;
l= strlen_F(dp->d_name);
if (l<1¦¦(l==1&&dp->d_name[0]=='.'¦¦
l==2&&dp->d_name[0]=='.'&&dp->d_name[1]=='.'))
continue;
l= CatS(pfad+pl, "/", dp->d_name, (char*)0);
if (df=FTW_F, stat(pfad, &sstat)==0) {
if (S_ISDIR(sstat.st_mode)) df=FTW_D;
}
else df=FTW_NS;
if (r=(*
Fnp)(pfad, &sstat, df), r!=0) return (r);
if (df!=FTW_D) continue;
if (df=
Ftw_r(pfad, pl+l), df!=0) return (df);
}
closedir(dirp);
pfad[pl]=0;
return (0);
}



static void FileTree(void)
{
/* ... */
if (0 !=
Ftw(FTdir, FTree, 15) ) Perr('b');
/* ... */
return;
}



static int
FTree(pfad, STat, typ)
register char *pfad;
struct stat *STat;
int typ;
{
int err=0;
static char *dnp=RtD;
register char *sp;
if (*pfad!='/') return (1);
sp=FT_PfadePtr;
switch (typ) {register char *basep;
case FTW_DNR: ++FT_Dirs; break;
case FTW_NS : ++FT_Files; break;
case FTW_F :
++FT_Files;
for (basep=pfad; *basep; ++basep);
/* ....................... */
break;
case FTW_D :
++FT_Dirs;
*((ino_t*)sp)= STat->st_ino; sp+=IB;
dnp=sp;
while (*sp++ = *pfad) ++pfad;
break;
default : break;
}
FT_PfadePtr=sp;
if (sp-FT_Pfade > D_NBSYS-D_NBLF) err=1;
return (err);
}

#undef FTW_F
#undef FTW_D
#undef FTW_DNR
#undef FTW_NS

.



^

Steuerung des Programmablaufes

Die nachfolgend gezeigten Anweisungen und Blöcke können beliebig ineinander
verschachtelt werden.
Blöcke:  { ... }  dienen dazu, um mehrere Anweisungen zusammenzufassen,
eine bestimmte else-Zuordnung zu erreichen
und um Variablen/Objekte an ihrem Anfang zu definieren, die dann nur in ihnen sichtbar sind.
Blöcke werden in der Regel einem Schlüsselwort/Funktionskopf zugeordnet, können aber auch
für sich allein stehen:  { { ... } { ... } ... { ... } }

if-Anweisung:

    if ( bedingung )  anweisung ;
    if ( bedingung )  anweisungs-kommaliste;
    if ( bedingung )  { anweisung; anweisung; ...; }
    if ( bedingung )  ;     /* Leeranweisung */
    if ( bedingung )  falls bedingung!=0 ;
    else falls bedingung==0 ;
    if ( ... )  { if (...)  ...;  anw; }
    if ( ... )  ...;  anw2;  else  ...;     /* else-Fehler! */
    if ( ... )  { if (...)  ...; }
    else ...;
    if ( ... )  ...;
    else if ( ... ) ...;
    else if ( ... ) ...;
    else ...;
    if (r=0,  !a&&(r=1, b>6)&&(r=2, c))  ...

Eine leere Bedingung  ()  entspricht  (1) - immer TRUE.

Bei der Zuordnung eines else-Zweiges zu einem  if  wird rückwärts das
nächsterreichbare  if  gesucht.
else  selbst wird dabei vorwärts gefunden.
Eine if-Anweisung innerhalb eines Blockes ist isoliert und kann nicht von außen erreicht werden.
Ein  if  , das bereits ein  else  hat, ist natürlich ebenfalls unerreichbar und 'gesättigt'.

anw  oben hat nichts mit der direkt davor stehenden if-Anweisung zu tun!
Bei der Rückwärtssuche nach einem  if  blockiert  anw2 !
(Es sei denn,  anw2  ist selbst eine if-Anweisung.)

    i= !a&&(r=1, b>6)&&(r=2, c);      /* siehe oben: if (r=0, ... */

i  erhält den Wert  1  bei zutreffender Bedingung, andernfalls  0 .
  r=1  wenn  a==0
  r=2
 wenn  a==0 UND b>6 .
  i=1  wenn  !a UND b>6 UND c!=0 .
Man beachte die vorliegende KO-Logik.
Diese Beispiele zeigen auch, wie man mit Klammern und Komma umgehen kann.


for-Schleife:

Siehe auch if-Anweisung für die verschiedene Gestaltung des Anweisungsteils.

    for (liste;  bedingung;  liste_b)  anweisung;
    for (;  bedingung;  )  anweisung;
    for (;;) { ...; }
    for (;  bedingung;  )  {
    /* ... */
    if (bed) continue;
    /* ... */
    break;
    /* ... */
    }
    for (a=b+2,++c;  b<10&&d;  ++b)  ...

Die  anweisung  wird nur ausgeführt, wenn die  bedingung!=0  ist.
Bei  bedingung==0  wird die Schleife beendet.
liste  und  liste_b  sind optional.
liste  wird einmal vor der Schleife ausgeführt, so als ob sie vor  for  stünde.
liste_b  wird jedesmal nach  anweisung  ausgeführt.
liste  und  liste_b  sind jeweils eine Anweisung oder mehrere durch Komma(ta) getrennt.

continue  springt zu  liste_b  oder direkt zur  bedingung  , falls  liste_b  fehlt.
break  springt aus der Schleife heraus.


while-Schleife:

    while ( bedingung )  anweisung;
    while ( bedingung )  { anweisung; anweisung; ...; }

Weiteres:  siehe oben, siehe unten.


do-while-Schleife:

    do  anweisung;  while ( bedingung );
    do  { anweisung; anweisung; ...; }  while ( bedingung );

anweisung  wird garantiert 1-mal ausgeführt  --  mindestens 1-mal.

Weiteres:  siehe oben, siehe unten.


switch-Fallunterscheidung:

In Abhängigkeit von einem case-Wert wird zur dazu passenden case-Marke gesprungen.
Gibt es keine passende case-Marke, wird zur  default-Marke gesprungen, und,
falls diese fehlt, direkt hinter das switch-Ende.
Der case-Wert muß ein Integer sein.
Die case-Marken müssen konstante Ausdrücke sein.

    switch ( case-wert-ausdruck )  {
    case 'a': anweisung(en);
    case 'A': anweisung(en); break;
    case 'b':
    case 'B': anweisung(en); break;
    case 600: anweisung(en); goto DFLT;
    case 620: anweisung(en); break;
    default : DFLT: k=l=0; break;
    }

Das  break  hinter  default  ist hier nicht nötig, denn die Verarbeitung würde nach
der letzten Anweisung im Switch ohnehin aus ihm herauslaufen.
Die case-Marken (auch default) dürfen prinzipiell in einer beliebigen Reihenfolge stehen,
es sei denn, man will absichtlich mit bestimmten Reihenfolgen arbeiten,
wie beispielsweise beim 'Durchfallen (fall through)'.

Der 'a'-Zweig fällt nach 'A' durch, weil hinter 'a' kein Sprung erfolgt.
Der 'b'-Zweig fällt nach 'B' durch, jedoch ohne eine eigene Anweisung zu haben,
woraus ein ODER-Verhalten resultiert:  'b' ODER 'B' .

Siehe unten: switch-Beispiel


break

springt aus einer Schleife oder aus einem  switch-Block heraus,
also direkt hinter das Ende einer Schleife oder eines  switch .

continue

springt zur Bedingungsprüfung einer umgebenden Schleife.
In  for-Schleifen zu  liste_b  (siehe oben), falls vorhanden.

return;
return
 ausdruck ;

Beendet Funktionen.
Bei  void-Funktionen darf kein  return-Wert(Ausdruck) angegeben werden,
bei Nicht-void-Funktionen muß ein Rückgabewert vorhanden sein.


goto-Anweisung:

goto  springt innerhalb von Funktionen vor jede beliebige Anweisung, auch Leeranweisung.
In einen Ausdruck oder eine Kommaliste kann jedoch niemals hineingesprungen werden.

    goto sprungMARKE;
    /* ... */
    sprungMARKE: anweisung;     /* oder Leeranweisung ; */


switch-Beispiel:

/* ... */
int nums[16+1], num;
register byte *bp= bp0;
byte *bpe=bpe0;
register int z, s;
int teil=*pteil, ei, ft,fi, ni;
byte del;
if (teil) bpe=bp=Teil, bpe+=teil;
ESCAGAIN:;
fi=ni=z=s=del=0;
NOWBUF:;
for (; bp<bpe; ++bp) { register byte c;
switch (c=*bp, z) {
case 0: if (c!=27) break;
++z, s¦=1; continue;
case 1: if (c=='[') { ++z, s¦=2; continue; }
if (c=='Q') { z=10, s¦=32¦2; continue; }
break;
case 2: if (++z, c=='=') { s¦=4; continue; }
case 3: if (!Cdig[c])
if (s&4) break;
else if (c=='"'¦¦c=='\'') { z=6,del=c; continue; }
else { z=9; goto ENDCK; }
++z, s¦=8; num=0;
case 4: DIG:;
if (Cdig[c]) { num*=10, num+=c-'0'; continue; }
if (ni>=16) bsh_Err(LS16¦E_LIMIT, "ansi: esc[#;#;#");
strs[ni]=0;
nums[ni++]=num;
if (c==';') { ++z; continue; }
z=9; goto ENDCK;
case 5: if (Cdig[c]) { --z; num=0; goto DIG; }
if (c=='"'¦¦c=='\'') { ++z,del=c; continue; }
break;
case 6: if (ni>=16) bsh_Err(LS16¦E_LIMIT, "ansi: esc[#;#;#");
s¦=64;
nums[ni]=-1;
strs[ni++]= fts+fi;
++z;
case 7: if (c==del&&(bp+1>=bpe¦¦bp[1]!=c)) { ++z; continue; }
if (fi>=sizeof(fts))
bsh_Err(LS16¦E_LIMIT, "ansi: esc[\"...\"");
if (c=='\n'&&!(ni&1)) c='\r';
fts[fi++]= c;
if (c==del) ++bp;
continue;
case 8: if (c==';') { z=5; continue; }
++z;
case 9: ENDCK:;
if (ei=CCk(s&4?ec2:ec1, c), ei) s¦=16;
break;
case 10: if (c>='0'&&c-'0'<96) { ++z,ft=c-'0'+1; continue; }
break;
case 11: if (c&&c<255) { ++z, del=c, fi=0; continue; }
break;
case 12: if (c==del) { s¦=16; break; }
if (fi>=sizeof(fts))
bsh_Err(LS16¦E_LIMIT, "ansi: escQFn\"...");
fts[fi++]=c; continue;
}
nums[ni]=-1, strs[ni]=fts+fi;
*pteil=0;
/* ... */
} /* Ende for */

.



^

Der C-Präprozessor

ist das erste Programm eines C-Compilers, das eine C-Datei bearbeitet.

    #define kLAENGE  360

Zeilen, die als erstes Zeichen  #  enthalten, sind für den Preprocessor bestimmt.
(Nur Zwischenraumzeichen dürfen davor stehen.)

Überall, wo im Quellkode  kLAENGE  steht, ersetzt der Preprocessor dies durch  360 ,
ab der obenstehenden Definition.
Allerdings nicht innerhalb von Zeichenkettenkonstanten  "..."  und nicht innerhalb von anderen
geschlossenen Namen - logisch.

    cc -DkLAENGE=360 -DDOS ...

Dies kann auch von der Kommandozeile aus definiert werden.

    #      include <stdio.h>
    # include "./mod/kfu.c"

Die angegebenen Dateien(Dateiinhalte) werden an den include-Zeilen eingefügt.

Nachfolgend eine Menge komplexerer Preprocessor-Zeilen, die viele verschiedene
Syntax-Merkmale zeigen:

    #undef kLAENGE
    # define D_NBLF  (D_NBLD+32)
    # define NDM 80
    # define OVSCAN(c) printf("\033[=%uA", c)

    #if !defined(FREEBSD)
    #undef CURSOR
    # define CURSOR(s,e) printf("\033[=" #s ";" #e "C")
    # define CURSOR_0 printf("\033[=%u;%uC", \
    getErr<0?12:(V6845.cursor_type>>8), \
    getErr<0?14:(V6845.cursor_type&0xff))
    # define CURSOR_OFF CURSOR(16,15)
    #else
    # define CURSOR(s,e) printf("\033[=%uC", (s)>(e)¦¦(s)>15?2:3 )
    # define CURSOR_0 printf("\033[=3C")
    # define CURSOR_OFF printf("\033[=2C")
    #endif

    # define FIPBUFF (1*1024)
    # define D_BSIZ1 BUFSIZ
    # define D_BSIZ2 ((D_NDE>>6)%1 ? ((D_NDE>>6)+1)<<10 : D_NDE<<4)

CURSOR(8,12);
ergibt:
printf("\033[=" "8" ";" "12" "C");    /* preprocessor */
und dann:
printf("\033[=8;12C");
falls  FREEBSD  nicht definiert ist.

#s  ist speziell und bewirkt, daß nach Ersetzung von  s  zu  8  "8"  entsteht.
##  ist ebenfalls speziell:

    #define cat(a,b)  a##b

cat(ooo, OOO)
ergibt:
oooOOO

Man beachte oben die Vorsichtsmaßnahme:  (s)>(e) .
Die Klammern schützen vor falschen Interpretationen, falls man bei Makro-'Aufrufen':
MAKRO(ausdruck)  Ausdrücke mit Operatoren angibt.

    #if defined(HPUX) && ( NDK > 4 ¦¦ SDK == 11 )
    # define DKVAL 2
    /* ... */
    # ifdef V12
    # undef VL_A
    /*# define VL_A 0x63*/
    static int Ask[10];
    # else
    static long Ask[10];
    # endif
    #endif

Wie man sieht, ist der Preprocessor ein enormes Hilfsmittel!
Man beachte aber, daß korrekter C-Kode in Abhängigkeit gesetzt wird,
denn  #if-#else-#endif  ist kein Kommentar, wie  /*...*/  !

    #ifdef  NAME
    #ifndef NAME
    #if       /* Bedingte Kompilierung, verschachtelbar: */
    #elif
    #else
    #endif
    #
    #line
    #error
    #pragma
    #pragma pack()
    /* Alignment in Strukturen: */
    #pragma pack(1) /* Nicht ANSI */
    #pragma pack(2)
    #pragma pack(4)

Vordefinierte Namen:

    __LINE__
    __FILE__
    __DATE__
    __TIME__
    __STDC__

.



^

C-Präprozessor - Extrembeispiel

Der Präprozessor ist -in den richtigen Händen- ein mächtiges Werkzeug:



  1
2 #if _k == 4
3 # define Kx() K(0) K(1) K(2) K(3)
4 # define K2x() K(0,1) K(1,2) K(2,3) K(3,4)
5 # define Kbx(b) K(0,b) K(1,b) K(2,b) K(3,b)
6 # define KxGg(G,g) K(0,G,g) K(1,G,g) K(2,G,g) K(3,G,g)
7 #endif
8
9
10
11 static void TBTcankontakte(void)
12 {
13 UNS4 kontakt[sizeof(Can.PSS.kontakt)/sizeof(*Can.PSS.kontakt)];
14 UNS4 sammelst[sizeof(Can.PSS.sammelstoerung)/sizeof(*Can.PSS.sammelstoerung)];
15 UNS4 kontaktK[sizeof(kontakt)/4];
16 BYTE kontaktB, alarmB;
17 UNS n, b;
18 UNS2 kontaktW, statusW;
19
20
...........
71 #define von n
72 #define anz b
73 #define kont kontaktB
74 #define sammelstK kontaktK
75 #define OPpss(A,o,B) A[0] o B[0], A[1] o B[1], A[2] o B[2]
76 #define CLRpss(A) A[0]=0, A[1]=0, A[2]=0
77 #define MSKpss(A,B) (A[0]==B[0] && A[1]==B[1] && A[2]==B[2])
78 #define IFOpss(A) (A[0] || A[1] || A[2])
79 #define OPunv(A,o,B) A[0] o B[0]
80 #define CLRunv(A) A[0]=0
81 #define MSKunv(A,B) (A[0]==B[0])
82 #define IFOunv(A) (A[0])
83
84 #define Gcopy(GGG,ggg) \
85 __DI(); \
86 OP##ggg(kontakt, =, Can.GGG.kontakt); \
87 OP##ggg(sammelst, =, Can.GGG.sammelstoerung); \
88 CLR##ggg(Can.GGG.kontakt); CLR##ggg(Can.GGG.sammelstoerung); \
89 __EI();
90
91 #define Gkont(k,GGG,ggg) \
92 OP##ggg(kontaktK, =, kontakt); \
93 OP##ggg(kontaktK, &=, GGG.K[k].mask); \
94 if (MSK##ggg(kontaktK, GGG.K[k].mask)) \
95 { \
96 EAbit.ggg##can##k=0; \
97 EAbit.ggg##rdanz##k=0; \
98 Can.GGG.timeout[k]= 0; \
99 OP##ggg(Can.GGG.kont_alt, &=, Can.GGG.anz_mask); \
100 OP##ggg(Can.GGG.kont_alt, &=, ~GGG.K[k].mask); \
101 OP##ggg(Can.GGG.kont_alt, |=, kontaktK); \
102 } \
103 else { \
104 if (++Can.GGG.timeout[k] > CAN_TIMEOUT) { \
105 --Can.GGG.timeout[k]; \
106 OP##ggg(Can.GGG.kont_alt, &=, Can.GGG.anz_mask); \
107 OP##ggg(Can.GGG.kont_alt, &=, ~GGG.K[k].mask); \
108 OP##ggg(Can.GGG.kont_alt, |=, kontaktK); \
109 EAbit.ggg##can##k=1; \
110 if (Can.GGG.ranz[k]&&(von=GGG.kamm[k],anz=GGG.kamm[k+_k], von&&anz)) \
111 { \
112 for (kont=0,--von; anz&&von<96; ++von,--anz) { \
113 if ( ISBIT(kontakt, von) && \
114 !ISBIT(sammelst, von)) ++kont; \
115 } \
116 if (kont+Can.GGG.ranz[k] >= GGG.kamm[k+_k]) \
117 EAbit.ggg##rdanz##k=0, EAbit.ggg##can##k=0; \
118 else EAbit.ggg##rdanz##k=1; \
119 } \
120 else EAbit.ggg##rdanz##k=0; \
121 } \
122 }
123
124
125 #define Gstat(k,GGG,ggg) \
126 if (k==0) OP##ggg(sammelst, &=, Can.GGG.anz_mask), \
127 CLR##ggg(Can.GGG.sammelst_alt); \
128 OP##ggg(sammelstK, =, sammelst); \
129 OP##ggg(sammelstK, &=, GGG.K[k].mask); \
130 if (!IFO##ggg(sammelstK)) { \
131 EAbit.ggg##k=0; \
132 } \
133 else { \
134 EAbit.ggg##k=1; \
135 OP##ggg(Can.GGG.sammelst_alt, |=, sammelstK); \
136 }
137
138 #define K(k,G,g) Gkont(k,G,g) \
139 Gstat(k,G,g)
140
141 Gcopy(PSS,pss)
142 KxGg(PSS,pss)
143 Gcopy(UNV,unv)
144 KxGg(UNV,unv)
145
146 #undef K
147 #undef Gcopy
148 #undef Gkont
149 #undef Gstat
150
151 #undef OPpss
152 #undef CLRpss
153 #undef MSKpss
154 #undef IFOpss
155 #undef OPunv
156 #undef CLRunv
157 #undef MSKunv
158 #undef IFOunv
159 #undef sammelstK
160 #undef von
161 #undef anz
162 #undef kont
163
164
165 #define Geraet(GGG,ggg) \
166 if (Can.GGG.anz) { \
167 __DI(); \
168 kontaktB= Can.GGG.kontakt; \
169 alarmB= Can.GGG.sammelstoerung; \
170 Can.GGG.kontakt=0; \
171 Can.GGG.sammelstoerung=0; \
172 __EI(); \
173 if (Can.GGG.anz_mask==kontaktB) { \
174 EAbit.ggg##can= 0; \
175 Can.GGG.kont_alt= kontaktB; \
176 Can.GGG.timeout = 0; \
177 } \
178 else { \
179 if (++Can.GGG.timeout > CAN_TIMEOUT) { \
180 --Can.GGG.timeout; \
181 Can.GGG.kont_alt= kontaktB; \
182 EAbit.ggg##can=1; \
183 } \
184 } \
185 Can.GGG.sammelstoerung_alt= alarmB; \
186 if (alarmB) EAbit.ggg=1; \
187 else EAbit.ggg=0; \
188 } \
189 else { \
190 EAbit.ggg= 0; \
191 EAbit.ggg##can= 0; \
192 Can.GGG.kont_alt= 0; \
193 Can.GGG.timeout = 0; \
194 Can.GGG.sammelstoerung= 0; \
195 Can.GGG.sammelstoerung_alt= 0; \
196 }
197
198 Geraet(UNB,unb)
199 Geraet(BM1,bm1)
200 Geraet(MM,mm1)
201 Geraet(D8,di8)
202 Geraet(RB,rb1)
203 if (EAbit.mm1||EAbit.mm1can) {
204 Mess.uL[0]=0; Mess.uL[1]=0; Mess.uL[2]=0;
205 Mess.iL[0]=0; Mess.iL[1]=0; Mess.iL[2]=0;
206 Mess.fL[0]=0; Mess.fL[1]=0; Mess.fL[2]=0;
207 }
208 #undef Geraet
209 return;
210 }
211

Preprocessor-Output (nur PSS, UNV):



212 __DI();
213 kontakt [0] = Can. PSS .kontakt [0],
214 kontakt [1] = Can. PSS .kontakt [1],
215 kontakt [2] = Can. PSS .kontakt [2] ;
216 sammelst [0] = Can. PSS .sammelstoerung [0],
217 sammelst [1] = Can. PSS .sammelstoerung [1],
218 sammelst [2] = Can. PSS .sammelstoerung [2] ;
219 Can. PSS .kontakt [0]=0,
220 Can. PSS .kontakt [1]=0,
221 Can. PSS .kontakt [2]=0 ;
222 Can. PSS .sammelstoerung [0]=0,
223 Can. PSS .sammelstoerung [1]=0,
224 Can. PSS .sammelstoerung [2]=0 ;
225 __EI();
226
227 kontaktK [0] = kontakt [0],
228 kontaktK [1] = kontakt [1],
229 kontaktK [2] = kontakt [2] ;
230 kontaktK [0] &= PSS .K[ 0 ].mask [0],
231 kontaktK [1] &= PSS .K[ 0 ].mask [1],
232 kontaktK [2] &= PSS .K[ 0 ].mask [2] ;
233 if ( ( kontaktK [0]== PSS .K[ 0 ].mask [0] && kontaktK [1]== PSS .K[ 0 ].mask [1]
 && kontaktK [2]== PSS .K[ 0 ].mask [2]) ) {
234 Err.Alarm.bit . psscan0 =0;
235 Err.Alarm.bit . pssrdanz0 =0;
236 Can. PSS .timeout[ 0 ]= 0;
237 Can. PSS .kont_alt [0] &= Can. PSS .anz_mask [0],
238 Can. PSS .kont_alt [1] &= Can. PSS .anz_mask [1],
239 Can. PSS .kont_alt [2] &= Can. PSS .anz_mask [2] ;
240 Can. PSS .kont_alt [0] &= ~ PSS .K[ 0 ].mask [0],
241 Can. PSS .kont_alt [1] &= ~ PSS .K[ 0 ].mask [1],
242 Can. PSS .kont_alt [2] &= ~ PSS .K[ 0 ].mask [2] ;
243 Can. PSS .kont_alt [0] |= kontaktK [0],
244 Can. PSS .kont_alt [1] |= kontaktK [1],
245 Can. PSS .kont_alt [2] |= kontaktK [2] ;
246 } else {
247 if (++Can. PSS .timeout[ 0 ] > 3 ) {
248 --Can. PSS .timeout[ 0 ];
249 Can. PSS .kont_alt [0] &= Can. PSS .anz_mask [0],
250 Can. PSS .kont_alt [1] &= Can. PSS .anz_mask [1],
251 Can. PSS .kont_alt [2] &= Can. PSS .anz_mask [2] ;
252 Can. PSS .kont_alt [0] &= ~ PSS .K[ 0 ].mask [0],
253 Can. PSS .kont_alt [1] &= ~ PSS .K[ 0 ].mask [1],
254 Can. PSS .kont_alt [2] &= ~ PSS .K[ 0 ].mask [2] ;
255 Can. PSS .kont_alt [0] |= kontaktK [0],
256 Can. PSS .kont_alt [1] |= kontaktK [1],
257 Can. PSS .kont_alt [2] |= kontaktK [2] ;
258 Err.Alarm.bit . psscan0 =1;
259 if (Can. PSS .ranz[ 0 ]&&( n = PSS .kamm[ 0 ],
260 b = PSS .kamm[ 0 + 2 ],
261 n && b )) {
262 for ( kontaktB =0,-- n ;
263 b && n <96;
264 ++ n ,-- b ) {
265 if ( ( (( unsigned char *)( kontakt ))[( n )>>3] & Bit8msk[( n )&7] )
 && ! ( (( unsigned char *)( sammelst ))[( n )>>3] & Bit8msk[( n )&7] ) ) ++ kontaktB ;
266 } if ( kontaktB +Can. PSS .ranz[ 0 ] >= PSS .kamm[ 0 + 2 ]) Err.Alarm.bit . pssrdanz0 =0,
267 Err.Alarm.bit . psscan0 =0;
268 else Err.Alarm.bit . pssrdanz0 =1;
269 } else Err.Alarm.bit . pssrdanz0 =0;
270 } } if ( 0 ==0) sammelst [0] &= Can. PSS .anz_mask [0],
271 sammelst [1] &= Can. PSS .anz_mask [1],
272 sammelst [2] &= Can. PSS .anz_mask [2] ,
273 Can. PSS .sammelst_alt [0]=0,
274 Can. PSS .sammelst_alt [1]=0,
275 Can. PSS .sammelst_alt [2]=0 ;
276 kontaktK [0] = sammelst [0],
277 kontaktK [1] = sammelst [1],
278 kontaktK [2] = sammelst [2] ;
279 kontaktK [0] &= PSS .K[ 0 ].mask [0],
280 kontaktK [1] &= PSS .K[ 0 ].mask [1],
281 kontaktK [2] &= PSS .K[ 0 ].mask [2] ;
282 if (! ( kontaktK [0] || kontaktK [1] || kontaktK [2]) ) {
283 Err.Alarm.bit . pss0 =0;
284 } else {
285 Err.Alarm.bit . pss0 =1;
286 Can. PSS .sammelst_alt [0] |= kontaktK [0],
287 Can. PSS .sammelst_alt [1] |= kontaktK [1],
288 Can. PSS .sammelst_alt [2] |= kontaktK [2] ;
289 } kontaktK [0] = kontakt [0],
290 kontaktK [1] = kontakt [1],
291 kontaktK [2] = kontakt [2] ;
292 kontaktK [0] &= PSS .K[ 1 ].mask [0],
293 kontaktK [1] &= PSS .K[ 1 ].mask [1],
294 kontaktK [2] &= PSS .K[ 1 ].mask [2] ;
295 if ( ( kontaktK [0]== PSS .K[ 1 ].mask [0] && kontaktK [1]== PSS .K[ 1 ].mask [1]
 && kontaktK [2]== PSS .K[ 1 ].mask [2]) ) {
296 Err.Alarm.bit . psscan1 =0;
297 Err.Alarm.bit . pssrdanz1 =0;
298 Can. PSS .timeout[ 1 ]= 0;
299 Can. PSS .kont_alt [0] &= Can. PSS .anz_mask [0],
300 Can. PSS .kont_alt [1] &= Can. PSS .anz_mask [1],
301 Can. PSS .kont_alt [2] &= Can. PSS .anz_mask [2] ;
302 Can. PSS .kont_alt [0] &= ~ PSS .K[ 1 ].mask [0],
303 Can. PSS .kont_alt [1] &= ~ PSS .K[ 1 ].mask [1],
304 Can. PSS .kont_alt [2] &= ~ PSS .K[ 1 ].mask [2] ;
305 Can. PSS .kont_alt [0] |= kontaktK [0],
306 Can. PSS .kont_alt [1] |= kontaktK [1],
307 Can. PSS .kont_alt [2] |= kontaktK [2] ;
308 } else {
309 if (++Can. PSS .timeout[ 1 ] > 3 ) {
310 --Can. PSS .timeout[ 1 ];
311 Can. PSS .kont_alt [0] &= Can. PSS .anz_mask [0],
312 Can. PSS .kont_alt [1] &= Can. PSS .anz_mask [1],
313 Can. PSS .kont_alt [2] &= Can. PSS .anz_mask [2] ;
314 Can. PSS .kont_alt [0] &= ~ PSS .K[ 1 ].mask [0],
315 Can. PSS .kont_alt [1] &= ~ PSS .K[ 1 ].mask [1],
316 Can. PSS .kont_alt [2] &= ~ PSS .K[ 1 ].mask [2] ;
317 Can. PSS .kont_alt [0] |= kontaktK [0],
318 Can. PSS .kont_alt [1] |= kontaktK [1],
319 Can. PSS .kont_alt [2] |= kontaktK [2] ;
320 Err.Alarm.bit . psscan1 =1;
321 if (Can. PSS .ranz[ 1 ]&&( n = PSS .kamm[ 1 ],
322 b = PSS .kamm[ 1 + 2 ],
323 n && b )) {
324 for ( kontaktB =0,-- n ;
325 b && n <96;
326 ++ n ,-- b ) {
327 if ( ( (( unsigned char *)( kontakt ))[( n )>>3] & Bit8msk[( n )&7] )
 && ! ( (( unsigned char *)( sammelst ))[( n )>>3] & Bit8msk[( n )&7] ) ) ++ kontaktB ;
328 } if ( kontaktB +Can. PSS .ranz[ 1 ] >= PSS .kamm[ 1 + 2 ]) Err.Alarm.bit . pssrdanz1 =0,
329 Err.Alarm.bit . psscan1 =0;
330 else Err.Alarm.bit . pssrdanz1 =1;
331 } else Err.Alarm.bit . pssrdanz1 =0;
332 } } if ( 1 ==0) sammelst [0] &= Can. PSS .anz_mask [0],
333 sammelst [1] &= Can. PSS .anz_mask [1],
334 sammelst [2] &= Can. PSS .anz_mask [2] ,
335 Can. PSS .sammelst_alt [0]=0,
336 Can. PSS .sammelst_alt [1]=0, Wird wegoptimiert
337 Can. PSS .sammelst_alt [2]=0 ;
338 kontaktK [0] = sammelst [0],
339 kontaktK [1] = sammelst [1],
340 kontaktK [2] = sammelst [2] ;
341 kontaktK [0] &= PSS .K[ 1 ].mask [0],
342 kontaktK [1] &= PSS .K[ 1 ].mask [1],
343 kontaktK [2] &= PSS .K[ 1 ].mask [2] ;
344 if (! ( kontaktK [0] || kontaktK [1] || kontaktK [2]) ) {
345 Err.Alarm.bit . pss1 =0;
346 } else {
347 Err.Alarm.bit . pss1 =1;
348 Can. PSS .sammelst_alt [0] |= kontaktK [0],
349 Can. PSS .sammelst_alt [1] |= kontaktK [1],
350 Can. PSS .sammelst_alt [2] |= kontaktK [2] ;
351 }
352 __DI();
353 kontakt [0] = Can. UNV .kontakt [0] ;
354 sammelst [0] = Can. UNV .sammelstoerung [0] ;
355 Can. UNV .kontakt [0]=0 ;
356 Can. UNV .sammelstoerung [0]=0 ;
357 __EI();
358
359 kontaktK [0] = kontakt [0] ;
360 kontaktK [0] &= UNV .K[ 0 ].mask [0] ;
361 if ( ( kontaktK [0]== UNV .K[ 0 ].mask [0]) ) {
362 Err.Alarm.bit . unvcan0 =0;
363 Err.Alarm.bit . unvrdanz0 =0;
364 Can. UNV .timeout[ 0 ]= 0;
365 Can. UNV .kont_alt [0] &= Can. UNV .anz_mask [0] ;
366 Can. UNV .kont_alt [0] &= ~ UNV .K[ 0 ].mask [0] ;
367 Can. UNV .kont_alt [0] |= kontaktK [0] ;
368 } else {
369 if (++Can. UNV .timeout[ 0 ] > 3 ) {
370 --Can. UNV .timeout[ 0 ];
371 Can. UNV .kont_alt [0] &= Can. UNV .anz_mask [0] ;
372 Can. UNV .kont_alt [0] &= ~ UNV .K[ 0 ].mask [0] ;
373 Can. UNV .kont_alt [0] |= kontaktK [0] ;
374 Err.Alarm.bit . unvcan0 =1;
375 if (Can. UNV .ranz[ 0 ]&&( n = UNV .kamm[ 0 ],
376 b = UNV .kamm[ 0 + 2 ],
377 n && b )) {
378 for ( kontaktB =0,-- n ;
379 b && n <96;
380 ++ n ,-- b ) {
381 if ( ( (( unsigned char *)( kontakt ))[( n )>>3] & Bit8msk[( n )&7] )
 && ! ( (( unsigned char *)( sammelst ))[( n )>>3] & Bit8msk[( n )&7] ) ) ++ kontaktB ;
382 } if ( kontaktB +Can. UNV .ranz[ 0 ] >= UNV .kamm[ 0 + 2 ]) Err.Alarm.bit . unvrdanz0 =0,
383 Err.Alarm.bit . unvcan0 =0;
384 else Err.Alarm.bit . unvrdanz0 =1;
385 } else Err.Alarm.bit . unvrdanz0 =0;
386 } } if ( 0 ==0) sammelst [0] &= Can. UNV .anz_mask [0] ,
387 Can. UNV .sammelst_alt [0]=0 ;
388 kontaktK [0] = sammelst [0] ;
389 kontaktK [0] &= UNV .K[ 0 ].mask [0] ;
390 if (! ( kontaktK [0]) ) {
391 Err.Alarm.bit . unv0 =0;
392 } else {
393 Err.Alarm.bit . unv0 =1;
394 Can. UNV .sammelst_alt [0] |= kontaktK [0] ;
395 } kontaktK [0] = kontakt [0] ;
396 kontaktK [0] &= UNV .K[ 1 ].mask [0] ;
397 if ( ( kontaktK [0]== UNV .K[ 1 ].mask [0]) ) {
398 Err.Alarm.bit . unvcan1 =0;
399 Err.Alarm.bit . unvrdanz1 =0;
400 Can. UNV .timeout[ 1 ]= 0;
401 Can. UNV .kont_alt [0] &= Can. UNV .anz_mask [0] ;
402 Can. UNV .kont_alt [0] &= ~ UNV .K[ 1 ].mask [0] ;
403 Can. UNV .kont_alt [0] |= kontaktK [0] ;
404 } else {
405 if (++Can. UNV .timeout[ 1 ] > 3 ) {
406 --Can. UNV .timeout[ 1 ];
407 Can. UNV .kont_alt [0] &= Can. UNV .anz_mask [0] ;
408 Can. UNV .kont_alt [0] &= ~ UNV .K[ 1 ].mask [0] ;
409 Can. UNV .kont_alt [0] |= kontaktK [0] ;
410 Err.Alarm.bit . unvcan1 =1;
411 if (Can. UNV .ranz[ 1 ]&&( n = UNV .kamm[ 1 ],
412 b = UNV .kamm[ 1 + 2 ],
413 n && b )) {
414 for ( kontaktB =0,-- n ;
415 b && n <96;
416 ++ n ,-- b ) {
417 if ( ( (( unsigned char *)( kontakt ))[( n )>>3] & Bit8msk[( n )&7] )
 && ! ( (( unsigned char *)( sammelst ))[( n )>>3] & Bit8msk[( n )&7] ) ) ++ kontaktB ;
418 } if ( kontaktB +Can. UNV .ranz[ 1 ] >= UNV .kamm[ 1 + 2 ]) Err.Alarm.bit . unvrdanz1 =0,
419 Err.Alarm.bit . unvcan1 =0;
420 else Err.Alarm.bit . unvrdanz1 =1;
421 } else Err.Alarm.bit . unvrdanz1 =0;
422 } } if ( 1 ==0) sammelst [0] &= Can. UNV .anz_mask [0] ,
423 Can. UNV .sammelst_alt [0]=0 ;
424 kontaktK [0] = sammelst [0] ;
425 kontaktK [0] &= UNV .K[ 1 ].mask [0] ;
426 if (! ( kontaktK [0]) ) {
427 Err.Alarm.bit . unv1 =0;
428 } else {
429 Err.Alarm.bit . unv1 =1;
430 Can. UNV .sammelst_alt [0] |= kontaktK [0] ;
431 }
432

Assembler - Das Zielvorhaben:

Komplette Adressenberechnung zur Compile-Zeit.
Bits in Bitfeldern als Array-Bit direkt ansprechbar.

433                                17565      ;;;;        Gcopy(PSS,pss)
434 24BF 17566 AND CCR, #191
435 719F0000 I 17567 MOVL A, _Can+384
436 71B3A2 17568 MOVL @RW3+-94, A
437 719F0000 I 17569 MOVL A, _Can+388
438 71B3A6 17570 MOVL @RW3+-90, A
439 719F0000 I 17571 MOVL A, _Can+392
440 71B3AA 17572 MOVL @RW3+-86, A
441 719F0000 I 17573 MOVL A, _Can+420
442 71B3AE 17574 MOVL @RW3+-82, A
443 719F0000 I 17575 MOVL A, _Can+424
444 71B3B2 17576 MOVL @RW3+-78, A
445 719F0000 I 17577 MOVL A, _Can+428
446 71B3B6 17578 MOVL @RW3+-74, A
447 D0 17579 MOVN A, #0
448 1D 17580 ZEXTW
449 71BF0000 I 17581 MOVL _Can+384, A
450 71BF0000 I 17582 MOVL _Can+388, A
451 71BF0000 I 17583 MOVL _Can+392, A
452 71BF0000 I 17584 MOVL _Can+420, A
453 71BF0000 I 17585 MOVL _Can+424, A
454 71BF0000 I 17586 MOVL _Can+428, A
455 2540 17587 OR CCR, #64
456 17588 ;;;; KxGg(PSS,pss)
457 7193A2 17589 MOVL A, @RW3+-94
458 71B3BA 17590 MOVL @RW3+-70, A
459 7193A6 17591 MOVL A, @RW3+-90
460 71B3BE 17592 MOVL @RW3+-66, A
461 7193AA 17593 MOVL A, @RW3+-86
462 71B3C2 17594 MOVL @RW3+-62, A
463 719F0000 I 17595 MOVL A, _PSS
464 71A4 17596 MOVL RL2, A
465 7093BA 17597 ANDL A, @RW3+-70
466 71B3BA 17598 MOVL @RW3+-70, A
467 7193BE 17599 MOVL A, @RW3+-66
468 71A6 17600 MOVL RL3, A
469 719F0000 I 17601 MOVL A, _PSS+4
470 71B3D6 17602 MOVL @RW3+-42, A
471 7186 17603 MOVL A, RL3
472 7093D6 17604 ANDL A, @RW3+-42
473 71B3BE 17605 MOVL @RW3+-66, A
474 7193C2 17606 MOVL A, @RW3+-62
475 71A6 17607 MOVL RL3, A
476 719F0000 I 17608 MOVL A, _PSS+8
477 71B3DA 17609 MOVL @RW3+-38, A
478 7186 17610 MOVL A, RL3
479 7093DA 17611 ANDL A, @RW3+-38
480 71A0 17612 MOVL RL0, A
481 71B3C2 17613 MOVL @RW3+-62, A
482 7193BA 17614 MOVL A, @RW3+-70
483 71B3DE 17615 MOVL @RW3+-34, A
484 7064 17616 CMPL A, RL2
485 ...................
486 6C580000 I 17785 CLRB _Err+27:0
487 6C580000 I 17786 CLRB _Err+26:4
488 600A 17787 BRA L_3477
489 17788 L_3474:
490 17789 ;;;; KxGg(PSS,pss)
491 6C780000 I 17790 SETB _Err+27:0
492 6004 17791 BRA L_3477
493 17792 L_3462:
494 17793 ;;;; KxGg(PSS,pss)
495 6C580000 I 17794 CLRB _Err+27:0
496 17795 L_3477:
497 17796 ;;;; KxGg(PSS,pss)
498 719F0000 I 17797 MOVL A, _Can+408
499 7093AE 17798 ANDL A, @RW3+-82
500 17617 x BNE16 L_3455

.



^

Komplexe Typen

Um Typen in komplexen Fällen korrekt zu erkennen, muß man vom Namen ausgehen,
und rechts und links vom Namen befindliche Operatoren gemäß ihrer Rangreihenfolge interpretieren,
dabei von innen nach außen arbeiten,
und Paare runder Klammern unterscheiden, ob sie einen Vorrang erzwingen sollen
oder aber eine Funktion anzeigen.

    extern void ( *signal(int, void(*)(int)) )(int);

signal
ist eine Funktion  signal( ,
die ein
int und eine Funktions-Adresse  (*)()  als Argumente erhält  )
und eine Adresse  *signal auf eine Funktion  ( *signal() )() ,
die ein int-Argument erhält  
)(int)  und keinen Rückgabewert hat  void( ,
zurückgibt.

    void (*sig)(int);

sig
ist eine Adresse  (*sig)
auf eine Funktion  *sig ( ,
die ein int-Argument erhält  
(int)
und nichts  void (  zurückgibt.

    char *cp[3];

cp  ist ein Array aus 3 Elementen des Typs: Adresse auf char:  char*

    char (*cp)[3];

cp  ist eine Adresse auf ein Array aus 3 Elementen des Typs:  char

    char *(*cpa[1])[3];

cpa  ist ein Array aus 1 Element des Typs:
Adresse auf ein Array aus 3 Elementen des Typs: Adresse auf char:  char*

    char *(*fuu(int,int))[3][4];

fuu  ist eine Funktion  fuu( )  mit zwei int-Args  fuu(int,int)  , die einen Zeiger  *fuu
auf ein Array  (*fuu   )[  aus 3x4  )[3][4]  Adressen auf char retourniert:  char *(  )[  .

    int   main  (void)
    int (main) (void)
    int ((main))(void)

Die drei Zeilen sind gleichbedeutend, da es innerhalb der Klammern  (main)  keinen Operator gibt,
dem die Klammern Vorrang verschaffen könnten.



^

Sequenzpunkt-Regeln

Ein Sequenzpunkt schließt alle eventuell noch ausstehenden 'schwebenden' Aktionen ab.

  • Zwischen zwei Sequenzpunkten darf nur ein Schreibzugriff auf ein und dasselbe Objekt stattfinden.
  • Falls hier neben einem Schreibzugriff zusätzlich Lesezugriffe auf dies Objekt vorhanden sind,
    müssen alle diese Lesezugriffe (auch) der Ermittlung des neuen Wertes dieses Objekts dienen.
  • Sequenzpunkte sind zeitliche Punkte, keine positionalen Punkte.
  • Bei Inkrement/Dekrement (++ --) finden jeweils ein Lese- und ein Schreibzugriff statt.
  • Sequenzpunkte sind:
    Semikolon (;), Komma (,), &&, ¦¦, ?, Funktionsaufrufe(der call-Zeitpunkt),
    direkt vor dem return von Library-Funktionen,
    das Ende vollständiger Ausdrücke:
     if (...), while (...), for (...; ...; ...), switch (...), etc.

Dieses Thema ist im Standard-Dokument enorm umfangreich, sehr verteilt und schwer lesbar.
Dennoch reicht diese knappe Zusammenfassung eigentlich aus.
Probleme mit Sequenzpunkten treten in der Praxis fast nur im Zusammenhang mit  ++  --
und bei Funktions-Argumenten (falls diese dort geändert werden) auf.

Hinweise:
Die Kommata, die die Argumente bei Funktionsaufrufen trennen, sind keine Sequenzpunkte!
Zuweisungsoperatoren ( = += etc.) sind -leider- keine Sequenzpunkte!
Die Reihenfolge der Berücksichtigung von Funktionsargumenten ist beliebig!

Merksatz:
Sobald irgendwo zwischen zwei Sequenzpunkten ein und dasselbe Objekt mehr als einmal
vorkommt und irgendein Schreibzugriff darauf vorkommt, sollte man genau prüfen!

Beispiele:

arr[x][x++]
Lesezugriff dient nicht der Ermittlung des neuen x-Wertes!
a= i++ - i + b;
Lesezugriff dient nicht der Ermittlung des neuen i-Wertes!
i= ++i + 1;
Mehr als ein Schreibzugriff!
i++ = a;
Mehr als ein Schreibzugriff!
(i++ = ist schon zuvor falsche Syntax!)
funktion(++i, i, a);
Zwei Werte möglich beim 2. Argument! (Reihenfolge)
Und: Lesezugriff dient nicht ...
funktion(a+4, a=b+1, c);
Zwei Werte möglich beim 1. Argument! (Reihenfolge)
Und: Lesezugriff dient nicht ...
y+= y * y;
Ein Schreibzugriff, jedes Lesen (3x) dient dem neuen Wert: OKAY

Man sieht, daß 'undefined behaviour' und dergleichen eigentlich recht leicht vermeidbar ist,
sobald man daran denkt und sich einfache Merksätze einprägt.



^

Der NULL-Pointer

Der NULL-Pointer ist ein Adressenwert, der niemals irgendeinem Objekt zugeordnet wird,
der also in diesem Sinne frei/unbelegt ist.
Daher kann er als eindeutige Erkennungsmarke benutzt werden.

Sobald im C-Quelltext der konstante Wert  0  vorkommt und dies im Zusammenhang mit einem
Adressenwert geschieht, wird dieser Wert 0 als NULL-Pointer interpretiert.
Ebenso, falls eine Adresse in einer Bedingungsprüfung ohne Vergleichsoperator vorkommt,
wird implizit mit dem NULL-Pointer verglichen:

    (addr != NULL)
    (addr != 0)
    (addr)
    (addr == NULL)
    (addr == 0)
    (!addr)

Die ersten drei Zeilen sind gleichbedeutend, die letzten drei ebenso.

Gemäß ANSI ist NULL folgendermaßen definiert (<stddef.h>, <stdio.h>, ...):

    #define NULL  0
    oder
    #define NULL  ((void*)0)

Achtung, der wirkliche Wert eines NULL-Pointers muß nicht Null (0x00000000) sein!
Auch, obwohl  #define NULL 0  gleichzeitig vorliegt!
Der Compiler setzt intern den richtigen Wert (für 0) ein, bei NULL-Pointer-Prüfungen.

    char *addr= NULL;
    if ( 0 == addr )  ...
    if ( 0 == (unsigned)addr )  ...

Deshalb kann die zweite Bedingung FALSE sein, während die erste garantiert TRUE ist.
Es kann sein, daß der Compiler die obigen Bedingungen folgendermaßen prüft:

    char *addr= 0xC0000000;
    if ( 0xC0000000 == addr )  ...
    if ( 0x00000000 == addr ) ...

Zahlendarstellungen im Quelltext befinden sich auf einer anderen Ebene als die tatsächliche
Bit-Repräsentation der diversen Objekte im Speicher.
Beispielsweise muß der Gleitkomma-Wert  0.0  durchaus nicht ausschließlich durch 0-Bits
repräsentiert sein!

    static int CatS(char *, char *, ...);
    /* ... */

    CatS(ziel, a, bp, s1, (char*)0);
    CatS(ziel, a, bp, s1, (char*)NULL);

Falls der Compiler nicht 'sieht', welchen Typ ein Argument hat, soll ein expliziter Typ-cast
diese Information bereitstellen.
Dies ist auch innerhalb von Funktionskörpern von Funktionen mit variabler Argumentanzahl
notwendig.
Denn die realen Null-Pointer-Werte können typabhängig sein!

Hinweis:
Eine Adressen-Umwandlung  (unsigned)addr  ist sehr weitgehend portabel, jedoch nicht
vollkommen portabel!



^

Neuer C-Standard C99

An einigen anderen Stellen dieses C-Tutorials sind verschiedene C99-Neuheiten kurz genannt.

Besserer C99

Die tollsten Neuheiten sind VL-Arrays, VM-Typen und 'long long' als 64Bit-Integer-Typ.
Diese Neuheiten eröffnen wirklich neue Programmier-Perspektiven und -Konzepte.

gcc 2.95.x  können offensichtlich zumindest long long, gcc-VLAs, Compound Literals
und die neuen Initialisierungen verarbeiten !
Diese neuen gcc sind offenbar die ersten Beinahe-C99-Compiler !
(siehe unten)

Die anderen Neuerungen sind 'nur' weitere Optimierungshilfen, Vervollständigungen, Präzisierungen
und kleinere bis mittlere Annehmlichkeiten.

//
Zeilenkommentar.
Viele C-Compiler akzeptieren dies schon länger.

inline
Jetzt auch in C  -  C++ hat dies Schlüsselwort von Anfang an.

restrict
Typ-Qualifizierer, wie const und volatile .
Optimierungsmöglichkeit:
Der Programmierer garantiert dem Compiler, daß zwei oder mehr Adressenbereiche nicht überlappen,
auf die mittels jeweils zugeordneter Adressen-Variable zugegriffen wird.
Wenn eine Adressen-Variable solchermaßen:  ... * restrict p  qualifiziert ist, dann soll
jeder Zugriff auf ein Objekt mittels der in p enthaltenen jeweiligen Adresse im jeweiligen
Gültigkeitsbereich eben nur mittels  p  erfolgen, niemals auch mit anderen (Adressen-)Variablen.
Zwei Pointer können sich beispielsweise ein und dasselbe Array teilen, sollen sich dann aber bei ihren
Zugriffen nicht 'ins Gehege' kommen.
Man kann sagen,  p  hat  exklusive  Rechte, und der  p  zugeordnete Speicher ist ein restriktiver Bereich.
int ** restrict p;
Z.B. nur Zugriff mit  p[1] ist restricted, nicht aber mit  **p .

_Bool
Integertyp mit einem Wertbereich  0/1 , also ein Bit-Flag-Typ.

_Complex  _Imaginary
Komplexe Zahlen:  real+imaginär
Nur in Verbindung mit:  float,double,long double.
Z.B.:  double _Complex dcx;

Die Namen  bool,complex,imaginary  sind keine Schlüsselwörter,
sondern header-Definitionen.

[unsigned] long long
Integer-Typ mit mindestens 64 Wert-Bits.

Definitionen und Deklarationen
können offenbar auch an anderen Stellen vorgenommen werden, als nur am Anfang
von Blöcken  {    - 'on the fly'.

ANSI-Header-Dateien:

    <assert.h>    <inttypes.h>  <signal.h>    <stdlib.h>
    <complex.h> <iso646.h> <stdarg.h> <string.h>
    <ctype.h> <limits.h> <stdbool.h> <tgmath.h>
    <errno.h> <locale.h> <stddef.h> <time.h>
    <fenv.h> <math.h> <stdint.h> <wchar.h>
    <float.h> <setjmp.h> <stdio.h> <wctype.h>


Die Initialisierungsmöglichkeiten von Arrays, Strukturen und Struktur-Arrays wurden stark erweitert:

Innerhalb einer Initialisierungsliste können zu initialisierende Elemente mittels einer zusätzlichen Syntax
und Zuweisungsoperator (=) explizit angegeben werden.
Die Reihenfolge dieser Initialisierer ist egal.
Diese Initialisierer können mit den herkömmlichen Initialisiererlisten beliebig kombiniert werden.
Die explizit angegebenen Positionen haben Vorrang. Bei jedem expliziten Initialisierer wird quasi ein
Elementnummer-Zeiger neu gesetzt, so ähnlich wie bei expliziten Zuweisungen in  enum { ... } .

    char *A[]= { "abc", "def" };           /* bisher */
    char *A[]= { [1]="def", [0]="abc" }; /* neu */
    typedef struct { int quot, rem; } div_t;
    div_t S= { .quot=4, .rem=-1 };
    struct { int a[3], b; } AS[]= { [0].a={1}, [1].a[0]=2 };
    /* AS ist ein Array aus 2 Strukturen */
    int A[15]= { 1,3,5,7,9,[10]=8,6,4,2,0 };
    /* 1 3 5 7 9 0 0 0 0 0 8 6 4 2 0 */
    int A[15]= { 1,3,5,7,9,[3]=8,6,4,2,0 };
    /* 1 3 5 8 6 4 2 0 0 0 0 0 0 0 0 */
    union { /*...*/ } U= { .any=8 };

Aus den obenstehenden Möglichkeiten resultiert, daß auch 'normale' Initialisierungen flexibler
vorgenommen werden können:

    struct { int a[3], b; } AS[]= { {1}, 2 };
    struct { int a[3], b; } AS[]= { {{1,0,0}, 0}, {{2,0,0}, 0} };

AS  enthält zwei Strukturen.
Die zweite Zeile zeigt die vollgeklammerte Form.
In beiden Fällen wird mit gleichen Werten initialisiert.

    char A[2][3][4]= { 0,1,2,3,4,5,6,7,8,9,10,11, 12,13 };

Hier wird so initialisiert als ob  A[2*3*4]  vorläge.
A[0]  und die ersten zwei Elemente von Abteilung  A[1]  werden gesetzt, Rest=0.


Flexibles Array als letztes Mitglied in Strukturen:

    struct sfa { int i; char fca[]; } *sp;
    char buf[100];
    sp= (struct sfa*)buf;
    sp->fca[95]= 8;

Verhalten als hätte man definiert:
struct sfa { int i; char fca[96]; };  [sizeof(int)==4]

sizeof(struct sfa) == sizeof(int /*i*/)

Anmerkung:
Bisher wurde das so:  char fca[1];  (nicht ganz korrekt) gelöst.
Siehe <dirent.h>:   char d_name[1];  d_name liefert dennoch Pfadnamen mit Länge >500.


Zusammengesetzte Literale:

Es werden Objekte ohne Namen erzeugt, die sofort zugewiesen werden müssen:  C9X-Draft:

    int *p= (int []){2, 4};  /* global: {konst} */

    int *p; /* innerhalb Funktion */
    p= (int [2]){*p};
    /* [0]=*p, [1]=0 */

p  zeigt auf ein Array:  int[2] .

    drawline( (struct point){.x=1, .y=1},
    (struct point){.x=3, .y=4});
    drawline(&(struct point){.x=1, .y=1},
    &(struct point){.x=3, .y=4});

    (const float []){1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6}

    "/tmp/fileXXXXXX"
    (char []){"/tmp/fileXXXXXX"}
    (const char []){"/tmp/fileXXXXXX"}

.


VLA:  Variable-Länge-Array
VM:  Variabel-Modifizierter Typ

VLAs sind Arrays, deren Elementeanzahl zur Laufzeit beliebig oft dynamisch festgelegt wird.
Deshalb können VLAs nicht statisch sein (global; static), sondern nur bei/nach Funktions- und
Blockeintritten ({) erzeugt werden; beim Funktionsverlassen werden sie zerstört.
Auch können VLAs nicht in Strukturen enthalten sein (Man denke an die Konsequenzen!).
VMs sind Adressen auf VLAs, VLA-Pointer.

VLAs sind ein wesentlich verbesserter Ersatz der alloca()-Funktion, die doch sehr oft
problematisch ist und nur als compiler-interne Funktion recht sicher anwendbar ist.

In aller Regel werden VLAs im Stack-Speicher angelegt, was jeweils nur einige wenige
Prozessortakte an Zeit beansprucht.  Dies ist also eine ultraschnelle Allokation von Speicherplatz.

Deklaration bei Prototypen:

    long Fu(int n, int m, long a[n][m]);
    long Fu(int n, int m, long a[*][*]);
    long Fu(int n, int m, long a[ ][*]);
    long Fu(int n, int m, long a[ ][m]);

Falls in Prototypen keine Objektnamen angegeben sind, muß man  [*]  bei VLAs verwenden.

Beispiele aus C9X-Draft:

extern int n;
extern int m;
void fcompat(void)
{
int a[n][6][m];
int (*p)[4][n+1];
int c[n][n][6][m];
int (*r)[n][n][n+1];
p = a;
// Fehler - nicht kompatibel, da 4 != 6.
r = c; // Kompatibel, aber definiertes Verhalten
// nur falls n == 6 und m == n+1.
}
extern int n;
int A[n];
// Error - file scope VLA
extern int (*p2)[n]; // Error - file scope VM
int B[100]; // OK - file scope but not VM

void fvla(int m, int C[m][m]); // OK - VLA with prototype scope

{
typedef int VLA[m][m];
// OK - block scope typedef VLA

struct tag {
int (*y)[n];
// Error - y not ordinary identifier
int z[n]; // Error - z not ordinary identifier
};
int D[m];
// OK - auto VLA.
static int E[m]; // Error - static block scope VLA
extern int F[m]; // Error - F has linkage and is VLA
int (*s)[m]; // OK - auto pointer to VLA.
extern int (*r)[m]; // Error - r had linkage and is
// a pointer to VLA.
static int (*q)[m] = &B; // OK - q is a static block
// pointer to VLA.
}
void copyt(int n)
{
typedef int B[n];
// B hat n ints, n jetzt bewertet.
n += 1; // Nanu, vor Objektanlegen!?
B a; // a hat n ints, n ohne += 1.
int b[n]; // a and b sind unterschiedlich groß
for (int i = 1; i < n; i++)
a[i-1] = b[i];
}
{
int n = 4, m = 3;
int a[n][m];
int (*p)[m] = a;
// p == &a[0]
p += 1; // p == &a[1]
(*p)[2] = 99; // a[1][2] == 99
n = p - a; // n == 1
}

Konkretes Anwendungsbeispiel:

    void Add(int n, int m, double a[n][n*m+300], double x);

    int main()
    {
    double b[4][308];
    Add(4, 2, b, 2.17);
    return 0;
    }

    void Add(int n, int m, double a[n][n*m+300], double x)
    {
    for (int i=0; i < n; i++)
    for (int j=0, k=n*m+300; j < k; j++)
    a[i][j] += x;
    // a ist ein Zeiger auf ein VLA
    // mit n*m+300 Elementen
    }

Man beachte, daß in C Arrays niemals als Ganzes an Funktionen übergeben werden können,
sondern stets nur Adressen darauf!
Deshalb hat oben  a  den Typ:  double(*a)[n*m+300]



^

C++  (ein wenig)

C++  ist eine objektorientierte Hybrid-Sprache, die auf  C  basiert.
C++  besteht aus:

  • C .
  • C-Erweiterungen:  "Das Bessere C".
  • Zusätzliche Sprachmittel zur objektorientierten Programmierung (OOP).

Man kann ohne weiteres aus einem sauber programmierten C-Programm
ein  C++-Programm machen,
indem einfach die Datei-Endung  .c  in  .C  bzw.  .cpp  geändert wird.
In  C++  muß man also nicht unbedingt objektorientiert programmieren;
C++  ist keine 'reine' OOP-Sprache.

C++  ist aber dennoch eine eigenständige Sprache, mit eigenem ANSI-Standard.

Das "Bessere C":

  • //  Zeilenkommentar
  • Variablendefinition 'on the fly'.
  • const int i= 12;  wirkt wie  #define i 12  und ist  static .
  • = Initialisierungen und  [size]  mittels Nichtkonstanten (berechnet),
    die aber auf Konstanten zurückführbar sein müssen.
  • Typen ohne typedef
  • Namenlose union
  • Überladene Funktionsnamen ==> strikte Prototypen
  • Default Funktionsparameter
  • Funktions-Inlining
  • Referenzen:  int &iref= i;
  • Speicherallokation mit  new  und  delete

Zusätzliche Schlüsselwörter gegenüber C:

    new
    delete
    inline
    (auch in C99)
    class Wie struct, nur anderer priv./public-Default
    private
    public
    protected
    friend
    virtual
    operator
    this
    namespace
    using
    mutable
    template
    "Exceptions:" ...

Die letzten Schlüsselworte ab 'namespace' sind später hinzugekommen.
Die wirklich entscheidenden, unverzichtbaren Schlüsselworte sind 'class/struct' bis 'this'.

Zusätzliche Operatoren bzw. zusätzliche Verwendungen davon:

    ::      Auflösung, stellt einen Bezug her
    : Bei Vererbung von Klassen
    ~ Kennzeichnet ~Destruktor-Funktion()
    & Referenzen, fast wie *p
    ::* Bei Zeigern auf Klassenmitglieder
    .* dito
    ->* dito
    <typ> Bei Templates (Quellkode-Libraries)

Das, was C++ im Kern ausmacht, im Vergleich zu C, sind die sehr stark erweiterten Möglichkeiten
mit Strukturen und die Operatoren-Überladung mit  operator .
Innerhalb von Strukturen können auch Funktionen definiert werden!
Der Innenraum von Strukturen kann unterteilt werden in Bereiche: privat, geschützt, öffentlich.
Einer Struktur/Klasse zugeordnet können Operatoren überladen werden, das heißt, zu einem
Operator wird eine beliebige Funktion definiert!:  int operator + (args) { ... };

    class Kunden { ... };
    Kunden K;
    K+="Hans Mustermann,...";
    K.print(NEU);

In  C  könnte man das -wesentlich uneleganter- folgendermaßen lösen:

    static int Kunden(const char *, ...);
    Kunden("%s%s", "+=", "Hans Mustermann,...");
    Kunden("%s%i", "print", NEU);

Konkrete C++-Beispiele:
Rechnen mit beliebig langen Zahlen: .h
Rechnen mit beliebig langen Zahlen

.



^

Die ANSI-Library

Die Programmiersprache C ist sehr 'schlank' und enthält eingebaute sprachliche Mittel nur dafür,
um Kode- und Datenobjekte anlegen zu können,
diese Daten manipulieren und berechnen
und Algorithmen programmieren zu können.
Ein Indiz dafür ist schon die geringe Anzahl von nur 32 Schlüsselworten (C99: 37).

Die Sprache C enthält also beispielsweise keinerlei eingebaute Funktionen, mit zugehörigen
Schlüsselworten, die eine Eingabe/Ausgabe vornehmen können.

Aber, für all diese Zwecke -und noch mehr- sind Funktionen standardisiert worden, die von außerhalb,
aus Funktions-Bibliotheken (Libraries), beliebig -und sehr einfach- hinzugefügt werden können.
Diese Hinzufügungen beschränken sich nicht auf Funktionen, sondern es werden beispielsweise
auch systemspezifische Fehlermeldungstexte bereitgestellt.

Dieses Konzept ist extrem flexibel.
Beispielsweise kann man beliebig eigene Libraries entwickeln...
In der Regel stehen hunderte von externen Funktionen zur Verfügung,
wobei es nach oben keine Grenze gibt.

Die Standard-Bibliothek gemäß ANSI/ISO enthält zwar nicht hunderte von Funktionen,
aber die Sprache C wird durch diesen Funktionssatz doch so sehr erweitert, daß man nur
geringfügig und/oder selten auf beispielsweise den POSIX-Standard zugreifen muß.


malloc

    #include <stdlib.h>
    void *malloc(            size_t size); /* size_t meist unsigned */
    void *realloc(void *ptr, size_t size);
    void free( void *ptr);

Mit malloc kann man sich dynamisch statischen Speicherplatz vom Betriebssystem besorgen.
Dieser Speicherplatz selbst ist in einem C-Quelltext global gültig.
malloc und realloc liefern die Startadresse auf einen solchen Speicherbereich,
die NULL ist bei Fehlschlag.

    int(*Buf)[8*1024];
    Buf= (int(*)[8*1024]) malloc(sizeof(int[2][8*1024]));
    if (!Buf)  PErr("Speichermangel"), exit(2);
    /*...*/
    i= Buf[1][k];
    /*...*/
    free(Buf);

Mit genau derjenigen Adresse, die zu einem Speicherbereich zuletzt geliefert wurde, muß free
aufgerufen werden, um diesen Speicherbereich wieder freizugeben, falls er nicht mehr benötigt wird.

Mit realloc kann ein bereits bestehender Speicherbereich vergrößert oder verkleinert werden.
Es ist möglich, daß dabei ein anderer Adressenwert geliefert wird als die vorher gültige Adresse,
da realloc den alten Bereich in einen neuen umkopiert, falls der alte Bereich nicht vergrößerbar ist.
Aber der Adressenwert braucht einen in der Regel nicht zu kümmern - Hauptsache, er ist nicht NULL.


setjmp

    #include <setjmp.h>
    static jmp_buf jBuf1;
    int   setjmp(jmp_buf jBuf1);
    void longjmp(jmp_buf jBuf1, int val);

setjmp speichert alle in Frage kommenden Prozessor-Daten in eine Struktur und liefert 0 bei Erfolg.
Irgendwo später kann longjmp aufgerufen werden, mit dieser Struktur und einem Integer-Wert >=1,
und longjmp kehrt nicht zurück, sondern springt quasi ins Innere der zuvor aufgerufenen
setjmp-Funktion, die nun jedoch diesen Wert >=1 zurückgibt.

Ein longjmp-Aufruf kommt also an der zugehörigen setjmp-Stelle wieder heraus.
longjmp ist also eine Art globaler Super-goto.

Alle nichtstatischen dynamischen Objekte zwischen  setjmp...longjmp  werden durch
longjmp-->setjmp  eliminiert.


<string.h> (<memory.h>)

Die Funktionen aus diesen Header-Dateien ergänzen sehr die Möglichkeiten und ersetzen
fehlende Merkmale der Sprache C.
Beispielsweise beliebiges (Byte-)Kopieren beliebiger Inhalte von Objekten und Teilen davon.

Die Funktionen, die mit den drei Zeichen  str  beginnen (z.B.: strlen() ), arbeiten mit
null-terminierten Zeichenketten (strings).

Die Funktionen, die mit  mem  beginnen (memcpy(),...), funktionieren völlig inhaltsunabhängig,
sie benötigen daher eine Byte-Anzahl als Argument.

Die Menge dieser Funktionen ist sehr groß.
Etwa 4...8 der wichtigsten haben Compiler meistens intern fest eingebaut:  intrinsic, inline

Die von früher bekannte  <memory.h>  ist jetzt in  <string.h>  enthalten.


fopen

    #include <stdio.h>
    FILE *fopen(const char *filename, const char *mode);
    int fclose(FILE *stream);

Mit diesen Funktionen werden Dateien geöffnet und wieder geschlossen.
fopen  liefert einen Dateizeiger vom Typ  FILE* , der NULL ist bei Fehlschlag.
Nach Öffnen können Funktionen wie z.B. fprintf() benutzt werden.

mode:
"r"   Datei zum Lesen öffnen.
"w"   Datei erzeugen oder aber auf Größe 0; zum Schreiben öffnen.
"a"   Datei event. erzeugen; zum Schreiben öffnen, jedes Schreiben geschieht ans Ende.
Die Zeichen  +  und/oder  b  können jeweils hinzugefügt werden,
zum Lesen+Schreiben bzw. Schreiben+Lesen, und Öffnen im Binär-Modus.
Z.B.:  "r+b"  "rb+"
b
 wird unter Unix ignoriert.

Die drei Dateizeiger  stdin,stdout,stderr  sind vordefiniert
und beim Programmstart bereits geöffnet:  Tastatur,Bildschirm,Bildschirm
(Entsprechen den File-Handles  0, 1, 2)
Z.B.  printf(  entspricht  fprintf(stdout, .

Die Funktions-Familie, die mit  FILE*  zusammenarbeitet, verwendet interne Ein-/Ausgabe-Puffer.
Siehe:  fflush(), setbuf(), setvbuf() .


fprintf

    #include <stdio.h>
    int fprintf(FILE *stream, const char *format, ...);
    int  printf(              const char *format, ...);
    int sprintf(char *s,      const char *format, ...);

Diese Ausgabe-Funktionen können binär gespeicherte Zahlen in außerordentlich vielfältiger
Form in lesbare Zeichenketten umwandeln und ausgeben.
Bei  sprintf() erfolgt die Ausgabe in einen char-Puffer  s .
Siehe auch:  fopen()

    printf("---%d, %c, %010u\n+++\n",  111, 'A', 789);
    ---111, A, 0000000789
    +++

Die Funktionen arbeiten mit variabel vielen Argumenten (...), deren jeweiliger Typ
mit den Angaben in der format-Zeichenkette übereinstimmen muß!

Rückgabewert:  Anzahl ausgegebener Zeichen;  <0  bei Fehler.
Pro Arg maximal 4095 Zeichen als Ausgabe.

    format:  [[...][arg_ident][...]]...  (Zeichenkette)
    Argument-Identifizierer:
    %[flags][min_breite][.präzision][längen_mod]konv_spez
    - * * hh d i
    + dez_zahl dez_zahl h u
    leerz l o
    # ll x X
    0 L f F
    j e E
    z g G
    t a A
    c
    s
    p
    n
    %
    %%     Erzeugt 1 Zeichen %
    [...] Optional
    * Info aus zusätzlichem Int-Arg,
    Angabe(n) vor dem eigentlichen Arg.
    * min_breite: neg. Int-Arg-Wert entspricht '-'flag
            Darstellung   Arg-Typ        hh h  l  ll L  j  z  t
    C99
    . . . . .
    d i [-]Dezimal +/-Integer * * * * i s p
    u Dezimal us-Integer * * * * i s p
    o Oktal Integer * * * * i s p
    x X Hexadezmal Integer * * * * i s p
    f F 123.456789 Gleitkomma *
    e E 123.45e-12 Gleitkomma *
    g G fF¦eE Gleitkomma *
    a A Hexadez(C99) Gleitkomma *
    c Zeichen Integer/'x' wi
    s Zeichenkette char* wc
    p Hex(meist) void*
    n nZeichen--> &int * * * * i s p
                                         i:[u]intmax_t  s:size_t
    p:ptrdiff_t
    wi:wint_t wc:wchar_t
    hh: [unsigned] char
    h: [unsigned] short
    l: [unsigned] long
    ll: [unsigned] long long
    L: long double
    Flags
    - Linksbündig innerhalb Ausgabe-Breite
    + + vor Darstellung bei pos. Arg-Wert
    leerz Leerzeichen vor Darstellung bei pos. Arg-Wert
    # 0x¦0X vor Hex-Darstellung, 0 vor Oktal-Darst.
    # . ist stets vorhanden bei Gleitkomma
    0 Breite wird mit führenden Nullen 0 aufgefüllt
    anstatt mit Leerzeichen.

präzision:
Mindestanzahl Ziffern bei diuoxX.
Ziffern nach  .  bei  fFeEaA.
Maximalanzahl signifikante Ziffern bei  gG.
Maximalanzahl Zeichen bei  s.


fscanf

    #include <stdio.h>
    int fscanf(FILE *stream , const char *format, ...);
    int  scanf(               const char *format, ...);
    int sscanf(const char *s, const char *format, ...);

Diese Eingabe-Funktionen sind ein ziemlich genaues Spiegelbild der Ausgabe-Funktionen fprintf, etc.
Alle Argumente müssen Adressen von Objekten sein, damit die umgewandelten Eingaben
dort hinein geschrieben werden können!

Alle Eingaben müssen zu den Angaben in der format-Zeichenkette passen, damit erfolgreich
Zuweisungen zu den Argumenten vorgenommen werden können.
Es müssen dem Format entsprechende Argumente vorhanden sein, denn sonst schreiben
diese Funktionen blindlings in den Speicher hinein.

Sobald eine Eingabe nicht (mehr) zum aktuell geltenden Argument-Identifizierer paßt,
wird abgebrochen, wobei unpassende Zeichen im Input-Stream verbleiben und eventuell
bei einem weiteren Aufruf erneut zu lesen und zuzuordnen versucht werden.
Angaben außerhalb von Argument-Identifizierern sind möglich und müssen ebenfalls passen.

Rückgabewert:  Anzahl Arg-Zuweisungen (>=0), oder EOF.

    format:  [[...][arg_ident][...]]...  (Zeichenkette)
    Argument-Identifizierer:
    %[*][max_breite][längen_mod]konv_spez
    [zeichenklasse]
    [^zeichenklasse]
    *     Keine Zuweisung, kein Arg angeben
    Ansonsten siehe fprintf.

Konversions-Spezifizierer [zeichenklasse]:
Argumenttyp:  char*
Alle Eingabezeichen sind passend, die in  [abc...]  vorkommen.
Alle Eingabezeichen sind passend, die nicht (^) in  [^abc...]  vorkommen.

max_breite:
Genau die angegebene Anzahl bei  c .

Bei  s  und  [klasse]  muß als zugehöriges Argument eine Pufferadresse angegeben werden,
wobei der Puffer groß genug sein muß, um alle Eingabezeichen plus ein abschließendes '\0'-Zeichen
aufnehmen zu können.

Bei jedem Argument-Identifizierer wird zunächst über eventuell vorhandene
Zwischenraumzeichen hinweggelesen,
danach muß die Eingabe zum jeweiligen Arg.-Ident. passen.
Außerhalb von Argument-Identifizierern angegebener Zwischenraum führt
-explizit- zum gleichen Zwischenraum-Verhalten.
Außerhalb angegebene Zeichen ungleich Zwischenraum und ungleich % müssen in der Eingabe
ganz genau so auftauchen, damit die Funktion weiterliest.

    scanf(" abc%d", &i);
    TAB   ENTER      abc  7

Die zuletzt angegebene Eingabe paßt zum Format,  7  wird erfolgreich an  i  zugewiesen
und es wird  1  retourniert.


Kurzübersicht der ANSI-Standard-Bibliothek  [aus C9X-Draft]

Das meiste hiervon ist auch in C89/C90 vorhanden.

Diese Liste wirkt auf den ersten Blick überraschend lang.
Sie besteht jedoch im wesentlichen aus vielen Mathematik-Funktionen, etwa so
wie bei einem wissenschaftlichen Taschenrechner, und den fprintf- und fscanf-Funktionsfamilien,
die aus einer größeren Anzahl von Varianten dieser Grundfunktionen bestehen.


    Diagnostics <assert.h>

    NDEBUG
    void assert(int expression);

    Complex <complex.h>

    complex imaginary I
    _Complex_I _Imaginary_I
    #pragma STDC CX_LIMITED_RANGE on-off-switch
    [ ... ]

    Character handling <ctype.h>

    int isalnum(int c);
    int isalpha(int c);
    int iscntrl(int c);
    int isdigit(int c);
    int isgraph(int c);
    int islower(int c);
    int isprint(int c);
    int ispunct(int c);
    int isspace(int c);
    int isupper(int c);
    int isxdigit(int c);
    int tolower(int c);
    int toupper(int c);

    Errors <errno.h>

    EDOM EILSEQ ERANGE errno

    Floating-point environment <fenv.h>

    fenv_t FE_OVERFLOW FE_TOWARDZERO
    fexcept_t FE_UNDERFLOW FE_UPWARD
    FE_DIVBYZERO FE_ALL_EXCEPT FE_DFL_ENV
    FE_INEXACT FE_DOWNWARD
    FE_INVALID FE_TONEAREST
    #pragma STDC FENV_ACCESS on-off-switch
    void feclearexcept(int excepts);
    void fegetexceptflag(fexcept_t *flagp,
    int excepts);
    void feraiseexcept(int excepts);
    void fesetexceptflag(const fexcept_t *flagp, int excepts);
    int fetestexcept(int excepts);
    int fegetround(void);
    int fesetround(int round);
    void fegetenv(fenv_t *envp);
    int feholdexcept(fenv_t *envp);
    void fesetenv(const fenv_t *envp);
    void feupdateenv(const fenv_t *envp);

    Characteristics of floating types <float.h>

    [ ... ]

    Format conversion of integer types <inttypes.h>

    [ ... ]
    intmax_t strtoimax(const char * restrict nptr,
    char ** restrict endptr, int base);
    uintmax_t strtoumax(const char * restrict nptr,
    char ** restrict endptr, int base);
    intmax_t wcstoimax(const wchar_t * restrict nptr,
    wchar_t ** restrict endptr, int base);
    uintmax_t wcstoumax(const wchar_t * restrict nptr,
    wchar_t ** restrict endptr, int base);

    Alternative spellings <iso646.h>

    and bitor not_eq xor
    and_eq compl or xor_eq
    bitand not or_eq

    Sizes of integer types <limits.h>

    CHAR_BIT CHAR_MAX INT_MIN ULONG_MAX
    SCHAR_MIN MB_LEN_MAX INT_MAX LLONG_MIN
    SCHAR_MAX SHRT_MIN UINT_MAX LLONG_MAX
    UCHAR_MAX SHRT_MAX LONG_MIN ULLONG_MAX
    CHAR_MIN USHRT_MAX LONG_MAX

    Localization <locale.h>

    struct lconv LC_ALL LC_CTYPE LC_NUMERIC
    NULL LC_COLLATE LC_MONETARY LC_TIME
    char *setlocale(int category, const char *locale);
    struct lconv *localeconv(void);

    Mathematics <math.h>

    float_t INFINITY FP_SUBNORMAL FP_ILOGB0
    double_t NAN FP_ZERO FP_ILOGBNAN
    HUGE_VAL FP_INFINITE FP_FAST_FMA
    HUGE_VALF FP_NAN FP_FAST_FMAF
    HUGE_VALL FP_NORMAL FP_FAST_FMAL
    #pragma STDC FP_CONTRACT on-off-switch
    int fpclassify(real-floating x);
    int isfinite(real-floating x);
    int isinf(real-floating x);
    int isnan(real-floating x);
    int isnormal(real-floating x);
    int signbit(real-floating x);
    double acos(double x);
    float acosf(float x);
    long double acosl(long double x);
    double asin(double x);
    float asinf(float x);
    long double asinl(long double x);
    double atan(double x);
    float atanf(float x);
    long double atanl(long double x);
    double atan2(double y, double x);
    float atan2f(float y, float x);
    long double atan2l(long double y, long double x);
    double cos(double x);
    float cosf(float x);
    long double cosl(long double x);
    double sin(double x);
    float sinf(float x);
    long double sinl(long double x);
    double tan(double x);
    float tanf(float x);
    long double tanl(long double x);
    double acosh(double x);
    float acoshf(float x);
    long double acoshl(long double x);
    double asinh(double x);
    float asinhf(float x);
    long double asinhl(long double x);
    double atanh(double x);
    float atanhf(float x);
    long double atanhl(long double x);
    double cosh(double x);
    float coshf(float x);
    long double coshl(long double x);
    double sinh(double x);
    float sinhf(float x);
    long double sinhl(long double x);
    double tanh(double x);
    float tanhf(float x);
    long double tanhl(long double x);
    double exp(double x);
    float expf(float x);
    long double expl(long double x);
    double exp2(double x);
    float exp2f(float x);
    long double exp2l(long double x);
    double expm1(double x);
    float expm1f(float x);
    long double expm1l(long double x);
    double frexp(double value, int *exp);
    float frexpf(float value, int *exp);
    long double frexpl(long double value, int *exp);
    int ilogb(double x);
    int ilogbf(float x);
    int ilogbl(long double x);
    double ldexp(double x, int exp);
    float ldexpf(float x, int exp);
    long double ldexpl(long double x, int exp);
    double log(double x);
    float logf(float x);
    long double logl(long double x);
    double log10(double x);
    float log10f(float x);
    long double log10l(long double x);
    double log1p(double x);
    float log1pf(float x);
    long double log1pl(long double x);
    double log2(double x);
    float log2f(float x);
    long double log2l(long double x);
    double logb(double x);
    float logbf(float x);
    long double logbl(long double x);
    double modf(double value, double *iptr);
    float modff(float value, float *iptr);
    long double modfl(long double value, long double *iptr);
    double scalbn(double x, int n);
    float scalbnf(float x, int n);
    long double scalbnl(long double x, int n);
    double scalbln(double x, long int n);
    float scalblnf(float x, long int n);
    long double scalblnl(long double x, long int n);
    double cbrt(double x);
    float cbrtf(float x);
    long double cbrtl(long double x);
    double fabs(double x);
    float fabsf(float x);
    long double fabsl(long double x);
    double hypot(double x, double y);
    float hypotf(float x, float y);
    long double hypotl(long double x, long double y);
    double pow(double x, double y);
    float powf(float x, float y);
    long double powl(long double x, long double y);
    double sqrt(double x);
    float sqrtf(float x);
    long double sqrtl(long double x);
    double erf(double x);
    float erff(float x);
    long double erfl(long double x);
    double erfc(double x);
    float erfcf(float x);
    long double erfcl(long double x);
    double lgamma(double x);
    float lgammaf(float x);
    long double lgammal(long double x);
    double tgamma(double x);
    float tgammaf(float x);
    long double tgammal(long double x);
    double ceil(double x);
    float ceilf(float x);
    long double ceill(long double x);
    double floor(double x);
    float floorf(float x);
    long double floorl(long double x);
    double nearbyint(double x);
    float nearbyintf(float x);
    long double nearbyintl(long double x);
    double rint(double x);
    float rintf(float x);
    long double rintl(long double x);
    long int lrint(double x);
    long int lrintf(float x);
    long int lrintl(long double x);
    long long int llrint(double x);
    long long int llrintf(float x);
    long long int llrintl(long double x);
    double round(double x);
    float roundf(float x);
    long double roundl(long double x);
    long int lround(double x);
    long int lroundf(float x);
    long int lroundl(long double x);
    long long int llround(double x);
    long long int llroundf(float x);
    long long int llroundl(long double x);
    double trunc(double x);
    float truncf(float x);
    long double truncl(long double x);
    double fmod(double x, double y);
    float fmodf(float x, float y);
    long double fmodl(long double x, long double y);
    double remainder(double x, double y);
    float remainderf(float x, float y);
    long double remainderl(long double x, long double y);
    double remquo(double x, double y, int *quo);
    float remquof(float x, float y, int *quo);
    long double remquol(long double x, long double y,
    int *quo);
    double copysign(double x, double y);
    float copysignf(float x, float y);
    long double copysignl(long double x, long double y);
    double nan(const char *tagp);
    float nanf(const char *tagp);
    long double nanl(const char *tagp);
    double nextafter(double x, double y);
    float nextafterf(float x, float y);
    long double nextafterl(long double x, long double y);
    double nextafterx(double x, long double y);
    float nextafterxf(float x, long double y);
    long double nextafterxl(long double x, long double y);
    double fdim(double x, double y);
    float fdimf(float x, float y);
    long double fdiml(long double x, long double y);
    double fmax(double x, double y);
    float fmaxf(float x, float y);
    long double fmaxl(long double x, long double y);
    double fmin(double x, double y);
    float fminf(float x, float y);
    long double fminl(long double x, long double y);
    double fma(double x, double y, double z);
    float fmaf(float x, float y, float z);
    long double fmal(long double x, long double y,
    long double z);
    int isgreater(real-floating x, real-floating y);
    int isgreaterequal(real-floating x, real-floating y);
    int isless(real-floating x, real-floating y);
    int islessequal(real-floating x, real-floating y);
    int islessgreater(real-floating x, real-floating y);
    int isunordered(real-floating x, real-floating y);

    Nonlocal jumps <setjmp.h>

    jmp_buf
    int setjmp(jmp_buf env);
    void longjmp(jmp_buf env, int val);

    Signal handling <signal.h>

    sig_atomic_t SIG_IGN SIGILL SIGTERM
    SIG_DFL SIGABRT SIGINT
    SIG_ERR SIGFPE SIGSEGV
    void (*signal(int sig, void (*func)(int)))(int);
    int raise(int sig);

    Variable arguments <stdarg.h>

    va_list
    type va_arg(va_list ap, type);
    void va_copy(va_list dest, va_list src);
    void va_end(va_list ap);
    void va_start(va_list ap, parmN);

    Boolean type and values <stdbool.h>

    bool
    true
    false
    __bool_true_false_are_defined

    Common definitions <stddef.h>

    ptrdiff_t size_t wchar_t NULL
    offsetof(type, member-designator)

    Integer types <stdint.h>

    int8_t INT32_MIN UINT_FAST8_MAX
    int16_t INT64_MIN UINT_FAST16_MAX
    int32_t INT8_MAX UINT_FAST32_MAX
    int64_t INT16_MAX UINT_FAST64_MAX
    uint8_t INT32_MAX INTPTR_MIN
    uint16_t INT64_MAX INTPTR_MAX
    uint32_t UINT8_MAX UINTPTR_MAX
    uint64_t UINT16_MAX INTMAX_MIN
    int_least8_t UINT32_MAX INTMAX_MAX
    int_least16_t UINT64_MAX UINTMAX_MAX
    int_least32_t INT_LEAST8_MIN PTRDIFF_MIN
    int_least64_t INT_LEAST16_MIN PTRDIFF_MAX
    uint_least8_t INT_LEAST32_MIN SIG_ATOMIC_MIN
    uint_least16_t INT_LEAST64_MIN SIG_ATOMIC_MAX
    uint_least32_t INT_LEAST8_MAX SIZE_MAX
    uint_least64_t INT_LEAST16_MAX WCHAR_MIN
    int_fast8_t INT_LEAST32_MAX WCHAR_MAX
    int_fast16_t INT_LEAST64_MAX WINT_MIN
    int_fast32_t UINT_LEAST8_MAX WINT_MAX
    int_fast64_t UINT_LEAST16_MAX INT8_C(value)
    uint_fast8_t UINT_LEAST32_MAX INT16_C(value)
    uint_fast16_t UINT_LEAST64_MAX INT32_C(value)
    uint_fast32_t INT_FAST8_MIN INT64_C(value)
    uint_fast64_t INT_FAST16_MIN UINT8_C(value)
    intptr_t INT_FAST32_MIN UINT16_C(value)
    uintptr_t INT_FAST64_MIN UINT32_C(value)
    intmax_t INT_FAST8_MAX UINT64_C(value)
    uintmax_t INT_FAST16_MAX INTMAX_C(value)
    INT8_MIN INT_FAST32_MAX UINTMAX_C(value)
    INT16_MIN INT_FAST64_MAX

    Input/output <stdio.h>

    size_t _IOLBF FILENAME_MAX TMP_MAX
    FILE _IONBF L_tmpnam stderr
    fpos_t BUFSIZ SEEK_CUR stdin
    NULL EOF SEEK_END stdout
    _IOFBF FOPEN_MAX SEEK_SET
    int remove(const char *filename);
    int rename(const char *old, const char *new);
    FILE *tmpfile(void);
    char *tmpnam(char *s);
    int fclose(FILE *stream);
    int fflush(FILE *stream);
    FILE *fopen(const char * restrict filename,
    const char * restrict mode);
    FILE *freopen(const char * restrict filename,
    const char * restrict mode,
    FILE * restrict stream);
    void setbuf(FILE * restrict stream,
    char * restrict buf);
    int setvbuf(FILE * restrict stream,
    char * restrict buf,
    int mode, size_t size);
    int fprintf(FILE * restrict stream,
    const char * restrict format, ...);
    int fscanf(FILE * restrict stream,
    const char * restrict format, ...);
    int printf(const char * restrict format, ...);
    int scanf(const char * restrict format, ...);
    int snprintf(char * restrict s, size_t n,
    const char * restrict format, ...);
    int sprintf(char * restrict s,
    const char * restrict format, ...);
    int sscanf(const char * restrict s,
    const char * restrict format, ...);
    int vfprintf(FILE * restrict stream,
    const char * restrict format,
    va_list arg);
    int vfscanf(FILE * restrict stream,
    const char * restrict format,
    va_list arg);
    int vprintf(const char * restrict format,
    va_list arg);
    int vscanf(const char * restrict format,
    va_list arg);
    int vsnprintf(char * restrict s, size_t n,
    const char * restrict format,
    va_list arg);
    int vsprintf(char * restrict s,
    const char * restrict format,
    va_list arg);
    int vsscanf(const char * restrict s,
    const char * restrict format,
    va_list arg);

    int fgetc(FILE *stream);
    char *fgets(char * restrict s, int n,
    FILE * restrict stream);
    int fputc(int c, FILE *stream);
    int fputs(const char * restrict s,
    FILE * restrict stream);
    int getc(FILE *stream);
    int getchar(void);
    char *gets(char *s);
    int putc(int c, FILE *stream);
    int putchar(int c);
    int puts(const char *s);
    int ungetc(int c, FILE *stream);
    size_t fread(void * restrict ptr,
    size_t size, size_t nmemb,
    FILE * restrict stream);
    size_t fwrite(const void * restrict ptr,
    size_t size, size_t nmemb,
    FILE * restrict stream);
    int fgetpos(FILE * restrict stream,
    fpos_t * restrict pos);
    int fseek(FILE *stream, long int offset, int whence);
    int fsetpos(FILE *stream, const fpos_t *pos);
    long int ftell(FILE *stream);
    void rewind(FILE *stream);
    void clearerr(FILE *stream);
    int feof(FILE *stream);
    int ferror(FILE *stream);
    void perror(const char *s);

    General utilities <stdlib.h>

    size_t ldiv_t EXIT_FAILURE MB_CUR_MAX
    wchar_t lldiv_t EXIT_SUCCESS
    div_t NULL RAND_MAX
    double atof(const char *nptr);
    int atoi(const char *nptr);
    long int atol(const char *nptr);
    long long int atoll(const char *nptr);
    double strtod(const char * restrict nptr,
    char ** restrict endptr);
    float strtof(const char * restrict nptr,
    char ** restrict endptr);
    long double strtold(const char * restrict nptr,
    char ** restrict endptr);
    long int strtol(const char * restrict nptr,
    char ** restrict endptr, int base);
    long long int strtoll(const char * restrict nptr,
    char ** restrict endptr, int base);
    unsigned long int strtoul(
    const char * restrict nptr,
    char ** restrict endptr,
    int base);
    unsigned long long int strtoull(
    const char * restrict nptr,

    char ** restrict endptr,
    int base);
    int rand(void);
    void srand(unsigned int seed);
    void *calloc(size_t nmemb, size_t size);
    void free(void *ptr);
    void *malloc(size_t size);
    void *realloc(void *ptr, size_t size);
    void abort(void);
    int atexit(void (*func)(void));
    void exit(int status);
    char *getenv(const char *name);
    int system(const char *string);
    void *bsearch(const void *key, const void *base,
    size_t nmemb, size_t size,
    int (*compar)(const void *, const void *));
    void qsort(void *base, size_t nmemb, size_t size,
    int (*compar)(const void *, const void *));
    int abs(int j);
    long int labs(long int j);
    long long int llabs(long long int j);
    div_t div(int numer, int denom);
    ldiv_t ldiv(long int numer, long int denom);
    lldiv_t lldiv(long long int numer,
    long long int denom);
    int mblen(const char *s, size_t n);
    int mbtowc(wchar_t * restrict pwc,
    const char * restrict s,
    size_t n);
    int wctomb(char *s, wchar_t wchar);
    size_t mbstowcs(wchar_t * restrict pwcs,
    const char * restrict s,
    size_t n);
    size_t wcstombs(char * restrict s,
    const wchar_t * restrict pwcs,
    size_t n);

    String handling <string.h>

    size_t
    NULL
    void *memcpy(void * restrict s1,
    const void * restrict s2,
    size_t n);
    void *memmove(void *s1, const void *s2, size_t n);
    char *strcpy(char * restrict s1,
    const char * restrict s2);
    char *strncpy(char * restrict s1,
    const char * restrict s2,
    size_t n);
    char *strcat(char * restrict s1,
    const char * restrict s2);
    char *strncat(char * restrict s1,
    const char * restrict s2,
    size_t n);

    int memcmp(const void *s1, const void *s2, size_t n);
    int strcmp(const char *s1, const char *s2);
    int strcoll(const char *s1, const char *s2);
    int strncmp(const char *s1, const char *s2, size_t n);
    size_t strxfrm(char * restrict s1,
    const char * restrict s2,
    size_t n);
    void *memchr(const void *s, int c, size_t n);
    char *strchr(const char *s, int c);
    size_t strcspn(const char *s1, const char *s2);
    char *strpbrk(const char *s1, const char *s2);
    char *strrchr(const char *s, int c);
    size_t strspn(const char *s1, const char *s2);
    char *strstr(const char *s1, const char *s2);
    char *strtok(char * restrict s1,
    const char * restrict s2);
    void *memset(void *s, int c, size_t n);
    char *strerror(int errnum);
    size_t strlen(const char *s);

    Type-generic math <tgmath.h>

    acos sqrt fmod nearbyint
    asin fabs frexp nextafter
    atan atan2 tgamma nextafterx
    acosh cbrt hypot remainder
    asinh ceil ilogb remquo
    atanh copysign ldexp rint
    cos erf lgamma round
    sin erfc llrint scalbn
    tan exp2 llround scalbln
    cosh expm1 log10 trunc
    sinh fdim log1p carg
    tanh floor log2 cimag
    exp fma logb conj
    log fmax lrint cproj
    pow fmin lround creal

    Date and time <time.h>

    NULL _LOCALTIME time_t
    CLOCKS_PER_SEC size_t struct tm
    _NO_LEAP_SECONDS clock_t struct tmx
    clock_t clock(void);
    double difftime(time_t time1, time_t time0);
    time_t mktime(struct tm *timeptr);
    time_t mkxtime(struct tmx *timeptr);
    time_t time(time_t *timer);
    char *asctime(const struct tm *timeptr);
    char *ctime(const time_t *timer);
    struct tm *gmtime(const time_t *timer);
    struct tm *localtime(const time_t *timer);
    size_t strftime(char * restrict s,
    size_t maxsize,
    const char * restrict format,
    const struct tm * restrict timeptr);

    size_t strfxtime(char * restrict s,
    size_t maxsize,
    const char * restrict format,
    const struct tmx * restrict timeptr);
    struct tmx *zonetime(const time_t *timer);

    Extended multibyte and wide-character utilities <wchar.h>

    wchar_t wint_t NULL WEOF
    size_t struct tm WCHAR_MAX
    mbstate_t struct tmx WCHAR_MIN
    [ 112 lines ]

    Wide-character classification and mapping utilities <wctype.h>

    wint_t wctrans_t wctype_t WEOF
    int iswalnum(wint_t wc);
    int iswalpha(wint_t wc);
    int iswcntrl(wint_t wc);
    int iswdigit(wint_t wc);
    int iswgraph(wint_t wc);
    int iswlower(wint_t wc);
    int iswprint(wint_t wc);
    int iswpunct(wint_t wc);
    int iswspace(wint_t wc);
    int iswupper(wint_t wc);
    int iswxdigit(wint_t wc);
    int iswctype(wint_t wc, wctype_t desc);
    wctype_t wctype(const char *property);
    wint_t towlower(wint_t wc);
    wint_t towupper(wint_t wc);
    wint_t towctrans(wint_t wc, wctrans_t desc);
    wctrans_t wctrans(const char *property);

.



^

Die POSIX-Library

Die ANSI-Library ist in der POSIX-Library enthalten.
Der POSIX-Standard ist also übergeordnet und enthält viel mehr Funktionen.

Man kann sagen, daß POSIX alle Funktionen zur Verwendung in C-Programmen zur Verfügung stellt,
damit man alles -und noch mehr- programmieren kann, was in der Kommandozeile
des jeweiligen Betriebssytems mit dessen diversen Kommandos bewirkt werden kann:

Beispielsweise Verzeichnisinhalte lesen, Verzeichnisse anlegen und löschen,
Dateisysteme erzeugen, montieren, abmontieren, aktuelles Verzeichnis feststellen,
Systemzeit lesen und setzen, sehr erheblich erweiterte Ein-/Ausgabe-Möglichkeiten,
Prozesse und Parallel-Prozesse starten, etc.

Praktisch alle Unix-Systeme sind POSIX-Systeme.
Beispielsweise Borland-Compiler für DOS/Win enthalten ebenfalls viele POSIX-Funktionen.


open/close

    #include <fcntl.h>
    int open(const char *path, int oflags, ...);
    int close(int fd);          /* fd: file descriptor (handle) */

Funktionen zum Öffnen und Schließen von Dateien.
Jeder erfolgreiche Aufruf von open liefert einen Handle (fd) mit einem jeweils eigenständigen
damit assoziierten Dateipositionszeiger.
Retournieren  <0  bei Fehler.

Die Handles  0,1,2  sind beim Programmstart bereits geöffnet:

fd:0 Standard-Eingabe (stdin)
fd:1 Standard-Ausgabe (stdout)
fd:2 Standard-Fehlerausgabe (stderr)

    oflags:
    O_RDONLY  O_WRONLY  O_RDWR      /* Hiervon genau eines */
    O_CREAT  O_TRUNC  O_APPEND
    // ...
    // beliebig oder-ierbar
    #if defined(UNIX)
    # define O_BINARY  0
    # define O_TEXT  0
    #endif
    fdo= open(ofnam, O_RDWR¦O_CREAT¦O_TRUNC¦O_APPEND, 0644);
    if (fdo<0)  PErr("Öffnen fehlgeschlagen"), exit(2);
    /*...*/
    close(fdo);

Wenn O_CREAT angegeben ist, muß ein Dateimodus angegeben werden, damit bei eventuellem
Neuanlegen einer Datei auch sogleich ihr Modus festgelegt werden kann.
Bei bereits existierender Datei wird ein angegebener Modus als auch O_CREAT ignoriert.
Siehe:  chmod()

O_CREAT:  Erzeugt Datei mit Größe 0, falls diese nicht bereits existiert.
O_TRUNC:  Bringt Datei auf Größe 0, falls diese existiert.  Nicht bei O_RDONLY.


read/write

    #include <unistd.h>   /*<io.h>*/
    ssize_t  read(int fd,       void *buf,   size_t nbyte);
    int read(int fd, void *buf, unsigned nbyte);
        int write(int fd, const void *buf, unsigned nbyte);

Diese Funktionen lesen/schreiben aus/in Dateien und schreiben/lesen die Bytes in/aus dem Puffer  buf .
Die gewünschte (read: maximale) Anzahl Bytes wird mittels  nbyte  angegeben.
Retourniert wird die Anzahl tatsächlich gelesener/geschriebener Bytes,
oder ein Wert  <0  bei Fehlern.

Unter Unix werden diese Funktionen von  fscanf()  und  fprintf()  benutzt.
Siehe:  fread(),fwrite().

Der Dateizeiger (s. lseek) wird durch Lesen/Schreiben um die Anzahl der jeweiligen Bytes
weiterbewegt, so daß sich der jeweils nächste Vorgang bündig anschließt.

Tip:
Wenn eine Datei einmal zum Lesen und gleichzeitig zusätzlich zum Schreiben geöffnet ist,
hat man zwei voneinander unabhängige Dateizeiger zur Verfügung.   (s. open)


lseek

    off_t lseek(int fd, off_t offset, int whence);
    long lseek(int fd, long offset, int whence);

Diese Funktion setzt einen Dateipositionszeiger auf eine gewünschte Position.
Die resultierende Position, vom Dateibeginn an gemessen, wird danach retourniert.
offset  kann auch negativ sein und wird dann entsprechend berücksichtigt.

SEEK_CUR: Bewegt von der aktuellen Position ausgehend um offset Bytes.
SEEK_SET: Bewegt vom Dateibeginn ausgehend.
SEEK_END: Bewegt vom Dateiende ausgehend.
(1,0,2)

    filesize= lseek(fd, 0L, SEEK_END);

Liefert bei normalen Dateien die Dateigröße.
(0L  bei einer leeren Datei.)
Die Endposition befindet sich hinter dem letzten Datei-Byte!
Die Anfangsposition ist 0.


stat/fstat

    #include <sys/types.h>
    #include <sys/stat.h>
    int  stat(const char *path, struct stat *sp);
    int fstat(int fd,           struct stat *sp);

Diese Funktionen liefern einen vollständigen Informationssatz über Dateien.
Die Infos werden in eine Struktur geschrieben, deren Adresse übergeben wird.
Bei Erfolg wird  0  retourniert.
st_mode:  Dateityp, Zugriffserlaubnisse.

Nachfolgend die Mitglieder der Struktur:

    dev_t   st_dev;         // Filesystem-ID
    ino_t st_ino; // i-node Nummer
    mode_t st_mode; // Datei-Modus
    nlink_t st_nlink; // Anzahl Links
    uid_t st_uid; // User-ID
    gid_t st_gid; // Gruppen-ID
    dev_t st_rdev; // Geräte-ID
    off_t st_size; // Dateigröße in Bytes
    time_t st_atime; // Zeit: letzter Zugriff
    time_t st_mtime; // Zeit: letzte Modifikation
    time_t st_ctime; // Zeit: Kreation, Status

Zeiten in Sekunden GMT.
off_t, time_t:  meist  long .

Ein Aufruf:

    struct stat Stat;
    if (stat(datei, &Stat)!=0)  /* Fehler */;

Zur Auswertung von  st_mode  sind Makros in <sys/stat.h> vorhanden.

Dateiparameter können geändert werden u.a. mit:
   chmod(),chown(),chgrp(),
   chsize(),truncate(),ftruncate(),
   utime().


dup/dup2

    int  dup(int fd);
    int dup2(int fd, int fd2);

dup  dupliziert einen Handle (file descriptor).
Es wird stets der wertmäßig niedrigste noch verfügbare Handle geliefert  (<_NFILE).
Ein Dateipositionszeiger wird nicht dupliziert, sondern den teilen sich die Handles.

dup2  gestattet die Angabe eines gewünschten Handle-Wertes:  fd2 .
Falls  fd2  bereits belegt ist, wird die zugehörige Datei vor der Duplizierung von  fd  geschlossen.
fd2  wird also mit derjenigen Datei verknüft, mit der  fd  bereits verknüpft ist.

fd  muß natürlich ein gültiger Handle sein, beispw. von open().
Bei Fehlschlag/Fehlern wird  <0  retourniert.


Kurzübersicht über POSIX-, X/Open- und sonstige C-Funktionen  (SCO OpenServer 5.0.5)

Ausgewählt aus etwa 2000 Funktionen der  man-S-Kategorie.

Viele Funktionen dieser Liste arbeiten mit zugehörigen Strukturen, die (jeweils) eine Menge
verschiedenartigster Manipulationen erlauben.
Beispielsweise:  ioctl(), mallinfo(), sigaction(), termios, directory/dirent.h.


a64l, l64a           - convert between long integer and base-64 ASCII 
a64l - gets long from base-64 representation
abort - generate an abort fault
abs - return integer absolute value
acceptable_password - determine if password is cryptic
access, eaccess - determine accessibility of a file
addwstr - write a character string with attributes to the
adf_gttok - convert word to token
adf_gtwrd - get next word from string and copy to buffer
adf_gtxcd - get next text code from string and copy to buff
advance - pattern match given a compiled regular expressi
alarm - set a process alarm clock
altzone - difference in seconds between UTC and alternate
ascftime - convert date and time to string
asctime - converts a tm structure to a 26-character strin
atexit - register function to be called at termination
atof - converts ASCII to floating point numbers
atoi - converts ASCII to integers
atol - converts ASCII to long integer numbers
bessel: j0, j1, jn, y0, y1, yn - bessel functions
brk, sbrk - change data segment space allocation
bsearch - binary search a sorted table
bstring: bcopy, bcmp, bzero - bit and byte string operations
calloc - allocate unused space for an array
cftime - convert date and time to string
chdir, fchdir - change working directory
chdir - change working directory using pathname
chmod, fchmod - change mode of file
chmod - change mode of file using pathname
chown, lchown, fchown - change owner and group of a file
chown - change owner and group ID of a file
chroot - change root directory
chsize - changes the size of a file
clock - report CPU time used
close - close a file descriptor
closedir - closes the named directory stream and frees the
compile - compile string for use with advance or step
creat - create a new file or rewrite an existing one
create_file_securely - create a file using an attribute specification
creatsem - creates an instance of a binary semaphore
crmode - put terminal into CBREAK mode
ctime, localtime, gmtime, asctime, tzset - convert date and time t
ctime - converts UNIX epoch time to local time
ctype - character handling routines
curses - CRT screen handling and optimization package
cuserid - get character login name of the user
daylight - set to non-zero value if alternate time zone ex
dial - establish an outgoing terminal line connection
difftime - computes the difference between time values
directory: closedir, opendir, readdir, rewinddir, seekdir, telldir -
drand48, erand48, jrand48, lcong48, lrand48, nrand48, mrand48, srand48
drand48 - returns non-negative double-precision floating-
dup - duplicate an open file descriptor
dup2 - duplicate an open file descriptor
eaccess - check file accessibility using EUID
ecvt, fcvt, gcvt - convert floating-point number to string
elf - object file access library
encrypt - encrypts a password
end, etext, edata - last locations in program
endgrent - closes group file when processing is complete
endpwent - closes password file when processing is complet
environ - array of character pointers to the environment
erf, erfc - error function and complementary error function
errno - system error messages
exec: execl, execv, execle, execve, execlp, execvp - execute a file
exit, _exit - terminate process
fchdir - change working directory using file descriptor
fchmod - change mode of file using file descriptor
fchown - change owner and group ID of a file using a fil
fclose, fflush - close or flush a stream
fclose - writes buffered data and closes stream
fcntl - file control
ffs - find first set bit
fgetgrent - returns pointer to next group structure
fgetpasswd - read or clear a password from a file
fgetpwent - gets pointer to next matching passwd structure
field - FIELD library routines
fileno - returns integer file descriptor
flushinp - discard current typeahead characters
fnmatch - find matching filename or pathname
fork - create a new process
form - create and display a form
forms - character-based-forms package
frexp, frexpl, ldexp, ldexpl, logb, logbl, modf, modff, modfl, nextaft
fstat - returns information about an open file
fstatfs - get file system information
fstatvfs - report on a filesystem using a file descriptor
fsync - synchronize changes to a file
ftime - return time in a structure
ftok - standard interprocess communication package
ftruncate - set a file to a specified length using a file d
ftw - walk a file tree
getc, getchar, fgetc, getw - get character or word from a stream
getch - read character from terminal associated with a
getchar - return next character from stdin
getcwd - get pathname of current working directory
getdate - convert user format date and time
getenv - return value for environment name
gethz - return the frequency of the system clock in tic
getitimer, setitimer - get and set value of interval timers
getpasswd, fgetpasswd, bigcrypt, bigcryptmax - read or clear a
getpid, getpgrp, getppid, getpgid - get process, process group, and
getprpwuid - searches for numerical user ID matching uid
getpw - get user info from UID
gettimeofday, settimeofday - get or set the date and time
getuid, geteuid, getgid, getegid - get real user, effective user,
getwd - get current working directory pathname
glob, globfree - generate pathnames matching a pattern
gmtime - convert time to UTC
gsignal - raises signal identified by its argument
hsearch, hcreate, hdestroy - manage hash search tables
hypot - euclidean distance function
iconv_open, iconv_close, iconv - convert characters from one cod
ioctl - I/O control command
isastream - test a file descriptor
isatty - test for a terminal device
item - CRT menu-item routines
itimer - interval timers
kill - send a signal to a process or a group of proces
killchar - return the user's kill character
l3tol, ltol3 - convert between 3-byte integers and long intege
lchown - change owner and group ID of a symbolic link
link - link to a file
localtime - converts time pointed to by clock to tm structu
logname - return login name of user
longjmp - restores last saved environment
lsearch, lfind - linear search and update
lseek - move read/write file pointer
lstat - returns information about a symbolic link
ltol3 - converts long integers to three-byte integers
mallinfo - report allocated space usage
malloc, free, realloc, calloc, cfree, mallinfo, mallopt - allocat
malloc - allocate space for an object
mallopt - control the space allocation algorithm
memmove - copies characters between objects
memory: memccpy, memchr, memcmp, memcpy, memset - memory operatio
menus - character based menus package
mkdir - make a directory
mkfifo - make a FIFO special file
mknod - make a directory or a special or ordinary file
mktemp - make a unique filename
mktime - converts local time to calendar time
mount - mount a filesystem
nap - suspends execution for a short interval
napms - make the cursor sleep for ms milliseconds
nice - change priority of a process
open - open for reading or writing
opendir - opens a directory
opensem - opens a semaphore
panels - character based panels package
passlen - determine minimum password length of an account
pause - suspend process until signal
perror - system error messages
pfmt, vpfmt - display error message in standard format
pipe - create an interprocess channel
popen, pclose - initiate a pipe to or from a process
psiginfo - produce system signal messages
psignal, psiginfo - system signal messages
psignal - produce system signal messages
ptrace - process trace
putenv - change or add value to environment
putpwent - write password file entry
pw_nametoid, pw_idtoname, gr_nametoid, gr_idtoname - map between use
qsort - quicker sort
raise - send signal to the execution program
random - better random number generator
random, srandom, initstate, setstate - better random number ge
randomword - generate a pronounceable password
rdchk - checks to see if there is data to be read
read - read from a file
readdir - returns a pointer to the next active directory
readlink - reads a symbolic link
readv - read from a file using multiple buffers
realloc - change the size of a memory object
regcomp, regexec, regerror, regfree - regular expression matching
regex - execute a compiled regular expression against a
regexp - regular expression compile and match routines
remove - removes filename
rename - changes filename
resetty - restore terminal to previous state
rewind - sets position of next I/O operation but does no
rewinddir - resets the named directory stream to the beginn
rindex - character string operation
rint - returns nearest integer value to floating point
rmdir - remove a directory
savetty - save current state of terminal to a buffer
sbrk - add bytes to the break value
scalb - returns the quantity value* 2^exp
sc_init - scancode Application Programming Interface (API
sc_raw - turns off scancode translation and returns the
seekdir - sets the position of the next readdir operation
setitimer - sets the specified interval timer
setjmp, longjmp - non-local goto
setlocale - set or read international environment
settimeofday - set system date and time
setuid, setgid, seteuid, setegid, setreuid, setregid - set use
sigaction - detailed signal management
signal - set a signal action
sigset, sighold, sigrelse, sigignore, sigpause - signal manageme
sigsetjmp, siglongjmp - non-local jumps
sigsuspend - wait for signal(s)
sleep - suspend execution for interval
srandom - seed the better random number generator
ssignal, gsignal - software signals
stat, fstat, lstat, statlstat - returns file status
statfs, fstatfs - get file system information
statvfs, fstatvfs - get filesystem information
stime - set time
strcoll, strncoll, strnxfrm, strxfrm - handles collation of st
strftime, cftime, ascftime - convert date and time to string
string: strcat, strchr, strcmp, strcpy, strcspn, strdup, strlen, strnc
strncoll - collates two strings until nth character is rea
strptime - date and time conversion
strxfrm - transforms the string from
symlink - creates symbolic link to a file
sync - update super block
sysfs - get file system type information
system - issue a shell command
tcdrain, tcflow, tcflush, tcsendbreak - line control functions
tdelete - deletes a node from a binary search tree
telldir - returns current location associated with named
tempnam - creates a filename in a named directory
termattrs - return video attributes supported by the termin
termios: tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow,
tfind - searches for a datum in the tree and returns a
time, ftime - return time
times - get process and child process times
timezone - difference in seconds between UTC and local tim
tmpfile - create a temporary file
tmpnam, tempnam - create a name for a temporary file
truncate, ftruncate - set a file to a specified length
tsearch, tfind, tdelete, twalk - manage binary search trees
ttyname - get terminal device pathname
tzname - contains time zone names
tzset - changes values of time variables
umask - set and get file creation mask
umount - unmount a file system
uname - get name of current system
ungetc - push character back into input stream
ungetch - return a character to the input queue, to be re
unlink - remove directory entry
usleep - suspend execution for an interval
ustat - get file system statistics
utime - set file access and modification times
utimes - set file times
vfork - spawn new process in a virtual memory efficient
vidattr - display the specified characters with video att
vidputs - display the specified characters with video att
wait - wait for child process to stop or terminate
waitpid - wait for child process to change state
wordexp, wordfree - perform word expansions
write - write to a file
writev - write to a file using multiple buffers

.



^

Modul-Konzepte

Kleine C-Programme bestehen meist aus einer einzigen C-Datei.
Da wird der Compiler einfach aufgerufen: z.B.:  bcc xyz.c
und er erzeugt eine ausführbare Datei:  xyz.exe

Größere Programme bestehen leicht aus 20 und mehr einzelnen C-Dateien,
schon allein, um Übersicht und Flexibilität zu erhalten.
Sobald ein C-Projekt aus mehreren Dateien besteht, gibt es mehrere Möglichkeiten,
die Kompilierung zu handhaben.

Ich selbst bevorzuge das folgende Konzept, bei dem ganz einfach die diversen C-Dateien
in die Hauptdatei  xyz.c  hinein-inkludiert werden:

    /*       xyz.c              2.11.95 */
    #        include <stdlib.h>
    # include <stdio.h>
    #        include "xyz.h"
    # include "mod/a.c"
    # include "mod/b.c"
    # include "mod/c.c"
    struct kle { /* ... */ };
             int  main(int, char **);
    static int xyz(int, char **);
    static int vLEN;
    /* ............ */

Damit kann der Compiler genauso einfach aufgerufen werden wie bei kleinen C-Projekten.
Auch bei Benutzung von Hilfsprogrammen, wie C-Browser (cscope), hat man es nur mit
einer einzigen Quelldatei zu tun, die angegeben wird.
Weiterhin können alle Namen des gesamten Quelltextes  static  sein, also privat verkapselt;
nur  main  ist das einzige Symbol, das daraus exportiert wird.
Es können so prinzipiell keine Konflikte mit Library-Symbolen auftreten.

Obwohl bei diesem Konzept nach jeder Änderung des Quellkodes dieser komplett neu
kompiliert wird, ist das heutzutage kein Nachteil mehr, denn selbst Programme mit mehreren
10000 Zeilen werden trotz hoher Optimierung in beispielsweise nur 4 Sekunden kompiliert.

Allerdings erreicht man bei 16Bit-DOS-Entwicklung nicht selten die maximale Größe
von 64KB für den einzigen Sammel-C-Modul (Text-Segment), so daß man dann aufteilen muß.

Bei diesem Konzept reicht es vollkommen aus, ein Shell-Script im jeweiligen Projekt-Verzeichnis
zu haben, z.B.:  ccxyz.bat , um sehr komfortabel kompilieren zu können.


Allgemein am häufigsten werden wohl extra Projektmanagement-Werkzeuge verwendet,
wie  make  unter Unix, und die diversen  IDEs  unter DOS/Win.

Hiermit wird jeder C-Modul einzeln kompiliert, woraus jeweils eine Objekt-Datei  *.o  entsteht,
die einzeln nur neu kompiliert werden, falls es Änderungen in dem zugehörigen C-Modul gab.
Die  *.o/*.obj-Dateien sind fertige Module, die nur noch der Linker zusammenbinden muß.

Nachteile sind, daß in jedem C-Modul alle Informationen extra bereitgestellt sein müssen.
Desweiteren müssen alle C-Module mit  EXTRN(extern) und PUBLIC  bzw.
PUBLIC  und  EXTRN  miteinander verzahnt werden!
Weiterhin muß man mit den Projekt-Werkzeugen arbeiten und beispielsweise
die make-Syntax gut beherrschen - und schreiben!

Auf diese Arbeit verzichte ich sehr gerne!


Eine weitere Möglichkeit ist, auf die Projekt-Tools zu verzichten
und den Compiler direkt mit allen C-Modulen aufzurufen:

    cc -oxyz xyz.c mod/a.c mod/b.c ...

Ansonsten gleicht dies dem drüberstehenden Konzept, mit make/IDEs.



^

Hinweise / Tips


04.06.2002: Dieses C-Tutorial scheint das beliebteste der Welt zu sein.
Jedenfalls ist es auf http://www.google.de mit den Suchworten:
c tutorial const volatile -c# -xml
in allen drei Suchbereichen an erster Position.
http://meta.rrzn.uni-hannover.de zeigt dies Tutorial ebenfalls an erster Stelle.
Mit dem Suchwort c99 kommen diese guten Resultate noch eindeutiger.
Die C-Bibel von den Erfindern Brian Kernighan und Dennis Ritchie:
Programmieren in C · ISBN 3-446-15497-3 · Hanser Verlag · 281 S.

Die C++Bibel vom Erfinder Bjarne Stroustrup:
Die C++ Programmiersprache · ISBN 3-8273-1660-X · Addison-Wesley · 1068 S.
K&R1K&R1    K&R2K&R2

Stets den Rang von Operatoren beachten!
Und auch die zugehörige Zusammenfassungsrichtung!:

int i[2], *ip=i;
++*ip;
*++ip= 6;

Der Inhalt von  i[0]  wurde inkrementiert, um 1 erhöht.
In der letzten Zeile wurde zuerst die in  ip  befindliche Adresse von  i  um 1 erhöht
und dann diesem int-Speicherplatz  6  zugewiesen:  i[1]=6;

Bei Betrachtung eines jeden Ausdrucks überlegen:
Welchen Typ hat dieser Ausdruck?
Was will ich erreichen (damit) ?
Wie kann ich das gewünschte -möglichst voll portabel- erreichen?

Stets an die positiven und ggf. negativen Zahlenwertbereiche der Datentypen denken!
Können Wert-Bits verloren gehen, durch Überlauf, Zuweisung, Typ-casting ?

Vorzeichenbehaftete Werte werden vorzeichenerhaltend erweitert,
auch wenn sie in einen breiteren vorzeichenlosen Typ umgewandelt werden!
Deshalb sind zur Vermeidung zwei Typ-casts von rechts nach links hintereinander nötig.

   char c='ü';
   i= A[c];
Hier wird in der Regel etwas wunderbares geschehen!
Das Ausdruck-Resultat ist bei einem Index implizit zuletzt immer int.
   unsigned char c='ü';
oder
   i= A[(unsigned char)c];
sind besser.

Stets an die automatischen, impliziten Typ-Umwandlungen des Compilers denken!


Innerhalb von berechnenden (arithmetischen) Ausdrücken werden 'kleinere' Operanden automatisch
auf denjenigen Typ erweitert/umgewandelt, der den größten Zahlenwertbereich im Ausdruck hat.
int  -->  long double
Das gilt bei Betrachtung von je zwei Operanden; ggf. sukzessive nacheinander, auf dem Resultattyp
der jeweils vorhergehenden Operation basierend.

Auf  int  oder  unsigned  wird ohnehin automatisch erweitert.

Bei Zuweisungen wird automatisch erweitert/umgewandelt.

Bei Zuweisungen an einen 'kleineren' Typ wird eine Warnmeldung ausgegeben,
es sei denn, es wurde ein expliziter, verkleinernder Typ-cast angegeben.

Bei 2er-Komplement-Prozessoren entspricht das einem einfachen Abschneiden auf die Ziel-Bitbreite.

Gefährlich sind Ausdrücke, bei denen das Endresultat garantiert in den größten vorkommenden
Typ paßt, jedoch bestimmte Berechnungskombinationen auf dem Weg zum Endresultat
einen Überlauf verursachen können!
Es müssen dann Klammern und/oder erweiternde Typ-casts verwendet werden.

    i= 30000 + 20000 + 20000 - 30000 - 20000;
    /* int: -32768...+32767 */

Die tatsächliche, interne Berechnungsreihenfolge kann ein Compiler beliebig vornehmen!
(Bei älteren Compilern (<=1990) völlig beliebig.)
Es gibt hier -bei älteren Compilern- keine Regel:  von links nach rechts, oder so!

    i= (20000-30000) + 30000 + (20000-20000);
    i= (int)( 30000L + 20000 + 20000 - 30000 - 20000 );

So geht's ohne Überlauf.
Bei älteren Compilern sollte der Suffix  L  hinter jeder Zahl stehen!
Bei aktuellen Compilern gilt hier von links nach rechts:  (((a+b)+c)-d)-e

    i= a + b + c - d - 128;

Der (ältere) Compiler könnte folgendermaßen in den Überlauf addieren, abhängig von Variablenwerten:

    i= -128;
    i+= a;
    i+= b;
    i+= c;
    i-= d;

Beispiel aus der Praxis:
Man beachte die Erweiterungen von  int  auf  UL (unsigned long):

sec= SPT*(365UL*(j-1970)+(nsj+MA[t[2]]+t[3]-1+(cjsj&&t[2]>=3)))
+ (t[4]*3600UL) + (t[5]*60+t[6]);

Das meiste wird hier im [u]int-Bereich (effizienter) berechnet.
Erweiterungen wirken nicht von außen in Klammern hinein, sondern nur aus Klammern heraus
oder auf der gleichen Ebene.


Überlaufverhalten

Ein Überlauf bei vorzeichenbehafteten Werten sollte tunlichst vermieden werden.
Bei unsigned-Typen jedoch garantiert der ANSI-Standard, daß ein Überlauf nur zu dem bekannten
Auto-Kilometerzählereffekt führt:  nach 99999 kommt 00000, und umgekehrt.

unsigned char:

11111111 +   1 ==           00000000
255 + 1 == 256-256 == 0
11111111 + 100 ==           01100011
255 + 100 == 355-256 == 99
01100011 - 100 == 11111111
99 - 100 == -1+256 == 255

Wenn also ein Überlauf durch Addition passiert, wird  2^8==256  abgezogen, und zwar von einem
gedachten Ergebnis, das außerhalb des Zahlenbereichs (0...255) liegt.
Bei einem Unterlauf wird  256  addiert.

Es kann also in den Überlauf addiert werden, und falls anschließend durch Subtraktion genauso viele
Unterläufe stattfinden, paßt das Endresultat trotzdem!
Dieses Verhalten kann man sich natürlich zunutze machen.

Mit signed-Werten geht sowas zwar auch, beispielsweise mit Intel x86, aber -- es ist nicht portabel.


Duales Zahlensystem

    Wert =  b(n-1)*2^(n-1) + ... + b2*2^2 + b1*2^1 + b0*2^0

Bei 8 Bit:

b7*2^7 + b6*2^6 + b5*2^5 + b4*2^4 + b3*2^3 + b2*2^2 + b1*2^1 + b0*2^0
b7*128 + b6*64 + b5*32 + b4*16 + b3*8 + b2*4 + b1*2 + b0*1

b: BITn: Werte: [0, 1]
n: Anzahl Bits
Zahlenbasis: 2

Zahlensystem, universell

    Wert =  s(n-1)*B^(n-1) + ... + s2*B^2 + s1*B^1 + s0*B^0

B: Zahlenbasis
s: STELLEn: Werte: [0, B-1]
n: Anzahl Stellen
x^0: X hoch 0 ist immer 1.
x^1: X hoch 1 ist immer X.


    unsigned char Arr[256]= { 'K', 'L', /*...*/ };

Obenstehendes ist ein vollständiger C-Modul, der kompiliert werden kann:

    cc -c amod.c

Es entsteht ein Datei amod.o, eine Objekt-Datei, deren Objekte in beliebigen
C-Programmen verwendet werden können:

    extern char Arr[256];
    cc -oxyz xyz.c amod.o

Falls man amod.o vergißt anzugeben, gibt der Linker eine Fehlermeldung aus:
"Symbol '_Arr' not found."


C99:  Padding-Bits und Trap-Repräsentationen

Laut C99-Standard können alle Integer-Typen -nur mit Ausnahme von unsigned char-
Padding-Bits enthalten:

    pppppppsvvvvvvvvvvvvvvvvvvvvvvv

Position, Anzahl und Bedeutung sind nicht festgelegt;
Inmitten der Wert-Bits (v==value) dürfen sie aber nicht vorkommen, denn hierfür gilt eine »Pure binary representation«.
(Ein Padding-Bit könnte beispielsweise ein Paritäts-Bit sein.)

Irgendwelche unerlaubten Wertekombinationen dieser Padding-Bits nennt man Trap-Repräsentationen.
Das heißt, der Prozessor kann eine Exception auslösen, das Programm bricht ab, usw.

Es gibt hier eine Ähnlichkeit mit Gleitkomma-Variablen, die ja ebenfalls nicht jede beliebige
Bit-Kombination enthalten dürfen.

Das bedeutet weiterhin, daß beispielsweise Unionen nicht mehr so vielfältig eingesetzt werden können,
wie bisher.


Plattformen mit Prozessoren  x86  (Intel, AMD, ...)

Diese Prozessoren sind für Compiler-Entwickler und C-Programmierer
außerordentlich komfortabel und 'bequem'.
Sie können fast beliebig 'vergewaltigt' werden und nehmen kaum etwas übel:

  1. Korrektes Alignment ist nicht erforderlich.
    Der Prozessor korrigiert nötigenfalls automatisch, was im Falle des Falles
    lediglich bis zu drei Takte zusätzlich kostet.
  2. Überlauf ist bei Integer unsigned als auch bei signed beliebig möglich.
  3. Bitweises Schieben ist vollkommen beliebig möglich.
    Beispielsweise kann 100 Bits nach links (<<) geschoben werden - wenn's Spaß macht.
    (Der Prozessor schneidet intern auf 5 Bits (<=31) ab.)
  4. Negative Integer-Werte werden im 2er-Komplement dargestellt.
    Das ermöglicht einfachste Konversionen einfach durch Abschneiden/Auffüllen der Bits.
    Das vermeidet Komplikationen mit einer negativen Null, wie bei 1er-Komplement.
  5. Alle Datentypen, auch Gleitkomma, haben beim Wert  0  auch tatsächlich alle Bits auf  0 .
    Das ermöglicht sehr einfaches Löschen und sehr einfache Speicherallokation von
    nichtinitialisierten Objekten und deren Null-Setzen beim Programmstart.
  6. Der reale NULL-Pointer-Wert hat tatsächlich alle Bits==0.
  7. Sämtliche Integer-Typen und sämtliche Adressen dürfen jede beliebige Bit-Kombination
    enthalten, jedes Bit ist erreichbar und beliebig manipulierbar.
  8. Adressenvariablen dürfen jede beliebige Adresse enthalten.
    (Zugriff darf natürlich nur erfolgen, wenn die jeweilige Adresse gültig zum Programm gehört.)
  9. Ein Byte hat 8 Bit.
  10. Die Byte-Breiten der Typen sind Potenzen von 2, nur mit Ausnahme von  long double  (10Byte):
    1, 2, 4, 8 .
  11. Alle Adressen haben  int/unsigned-Breite, long/ulong bei far/huge DOS16.
  12. Die Wertigkeit der Bytes von Objekten entspricht linear den Byte-Adressen:
    Auf der niedrigsten Adresse liegt auch das niederwertigste Byte, usw.
  13. Alle Adressentypen können in beliebige andere Typen konvertiert werden und anschließend
    kann damit beliebig zugegriffen werden.
    (Funktions-Adressen können natürlich nicht in Daten-Adressen konvertiert werden, und umgekehrt,
    mit nachfolgendem Zugriff.)
  14. Bitfelder werden mit allen Integer-Typen bis int/unsigned so angelegt,
    wie man das von der Logik her vermutet, also bündig der Reihe nach von jeweils Bit 0-max.
  15. Der ANSI-Standard nennt bei annähernd 200 Manipulationen: 'undefined behaviour'
    == Undefiniertes Verhalten  als Folge dieser Manipulationen.
    Auf x86-Plattformen gilt zwar fast nichts davon,
    jedoch für voll portable Programmierung muß man das eben annehmen.

Auf Plattformen mit anderen Prozessoren muß man davon ausgehen, daß einige, viele oder alle der oben
genannten Punkte nicht zutreffen!
Andere Prozessoren sind also (teils erheblich) anspruchsvoller, ohne allerdings -in entsprechendem Maße-
schneller zu sein.  (Richtig zugeordnet; Preis, Komplexität und Aufwand berücksichtigt, etc.)

Das ist auch der Grund dafür, daß etwa ab 1979 Intel-Prozessoren bei den Ingenieuren und Programmierern
(oftmals Assembler) mehr und mehr gewonnen haben, gegenüber den Motorola-Prozessoren.
Ich sage das nicht vom Hörensagen her, sondern ich habe auch schon in den frühen 80er Jahren konkret in
Entwicklungslabors gearbeitet und weiß das schlicht und einfach klipp und klar.

Der ANSI-Standard verfolgt konsequent das Ziel, so wenig wie nur möglich festzulegen/vorzuschreiben!
Er läßt vieles einfach offen, damit C auch auf der denkbar exotischsten Plattform laufen kann.
Es wird also eine C-Welteroberungsstrategie verfolgt, die 100 exotischen Prozessoren den Vorrang
vor 500 Millionen Intel-Prozessoren einräumt und die ganze Last den C-Programmierern aufbürdet.

Beispielsweise sind nur die Zeichen "0123456789" zahlenwertmäßig um je +1 auseinander, bei allen anderen
Zeichen (z.B. "a-zA-Z") ist diesbezüglich nichts vorgeschrieben!
Auch wenn es gar keinen Zeichensatz gibt, bei dem 'b'-'a' nicht gleich 1 ist -- wer dennoch davon ausgeht,
hat gemäß ANSI-Standard ein unportables Programm geschrieben, das nach offizieller Meinung durchaus
den dritten Weltkrieg auslösen könnte!  (undefined behaviour)

C9X/C99-Text


Irrtümliche, restriktive Interpretationen des ANSI/ISO-C-Standards

Es werden im Internet auch undefinierte Verhaltensweisen von C-Programmierungen verbreitet,
die in Wahrheit eben nicht undefiniert sind.

Nachfolgend sollen die beiden Zuweisungen an  i  (die rechte Seite) undefiniert sein, weil Zeiger
nur ein Element hinter das letzte Array-Element gültig sind und genau das auch für Sub-Arrays gelte,
auch wenn diese innerhalb des gesamten Array-Adressraumes liegen und obwohl es ein 1-dimensionaler
Adressentyp ist.

    int A[3][4], *ip, i;
    i= (&A[0][0])[5];
    ip= &A[0][0];
    // Typ: int*
    i= ip[5];
        *   *
    000011112222 [3]
    012301230123 [4]
    .......... .
    0123456789 11 [12] // 1-dimensional: 3x4==12

Da bin ich anderer Meinung, weil im Standard (beim Operator ==) steht, daß die Adresse hinter dem
letzten Element eines Sub-Arrays (*) gleich ist mit der Adresse des ersten Elements des
nachfolgenden Sub-Arrays.
Wenn nun aber diese Adressen gleich sind, dann kann es nicht sein, daß Zugriff mit der einen undefiniert ist
und gleichzeitig Zugriff mit der anderen definiert ist.
Der Standard sagt weiterhin, daß Arrays und auch Sub-Arrays lückenlos sind.
Außerdem können malloc()-Adressen in jeden beliebigen Array-Adressen-Typ umgewandelt werden.

    i= ip[0];
    i= ip[1];
    /* ... */
    i= ip[11];
    ip+=12;

Folglich müssen die obenstehenden Zugriffe und zum Schluß der Zeigerinhalt auf [12] gültig sein.

.

Zur Hauptseite