Die Programmiersprache C - Kurz und knapp

Martin Sulzmann

Übersicht

Grundlagen

#include <stdio.h>

/*

- Vorwärtsdeklarationen
- Ausgabe via printf
- Referenztypen (aka Zeiger/Pointer)
- Call-by value versus call-by reference
- Eingabe via scanf
- Strukturen

*/


//////////////////////////////////////////////////
// Vorwärtsdeklarationen


// Wir rufen "printSomething" auf.
// 1. Die Definition von "printSomething" ist (textuell) erst danach.
// 2. Wir muessen aber "printSomething" vorher deklaration.
// 3. So wissen wir, dass es "printSomething" gibt.

void printSomething();

void printHallo() {
  printSomething();
  printf("\nHallo");
}

void printSomething() {
  printf("\nSomething");
}


//////////////////////////////////////////////////
// Ausgabe via printf

void printEx() {
  int i = 5;
  float f = 3.2;
  char c = 'a';

  printf("\n i = %d", i);
  printf("\n f = %f", f);
  printf("\n c = %c", c);

  printf("\n i = %d, f = %f, c = %c", i, f, c);


 // Typinformation kodiert im Formatstring
 // Z.B. Platzhalter %f steht fuer eine float Zahl.
 // printf ist eine "variadic" function, nimmt einen Variable Anzahl von Argumenten.

}


// Beachte:
// Typen der Argumente muessen mit Platzhaltern kompatibel sein.
// Pruefung der (Typ)kompatibilitaet von Argument und Platzhalter geschieht dynamisch (zur Laufzeit).
void printEx2() {

    float f = 3.2;

    printf("\n f = %d", f);
    /*
    Die meisten Compiler sind aber in der Lage fuer obiges Beispiel, eine "compiler warning" zu liefern

    warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
   66 |     printf("\n f = %d", f);
      |                    ~^   ~
      |                     |   |
      |                     int double
      |                    %f


    */


    // Hier verwenden wir einen "dynamischen String", der Compiler ist nicht mehr in der Lage den Fehler zu erkennen.
    char str[] = "\n f = %d";
    printf(str,f);
}

//////////////////////////////////////////////////
// Referenztypen (aka Zeiger/Pointer)


// 1. "int x",     x ist eine Variable in welcher wir Dezimalwerte speichern koennen
// 2. "int* p",    p ist eine Variable in welcher wir Referenzen auf Dezimalwerte speichern koennen.
//
//                 Sprachgebrauch in C:
//                     p ist ein Zeiger/Pointer
//
//                 Operationen:
//                     & ist der Referenz/Adress-Operator
//                     * ist der Dereferenzierungs-Operator

void references() {
  int x;
  float y;
  int* p;
  float *q;

  p = &x;              // In p findet sich die Referenz auf x.
  *p = 1;              // Indirekter Zugriff auf x.
  printf("%d", x);


  /*
  // warning: Inkompatibler Zeigertyp
  q = &x;
  */

  // Vorsicht!!!
  // 1. int Zeiger wird auf float Zeiger gecastet.
  // 2. Die Referenz wird als float interpretiert.
  // 3. Kann zum Absturz fuehren.
  y = * ((float*)(p));

}

//////////////////////////////////////////////////
// Call-by value versus call-by reference

int inc(int x) {
  x = x + 1;
  return x;
}

// Call-by-reference via Zeiger.
void incRef(int* x) {
  *x = *x + 1;
  return;
}

void cbvCBR() {
  int x = 1;

  printf("\n %d", inc(x));
  printf("\n %d", x);

  incRef(&x);
  printf("\n %d", x);


}


//////////////////////////////////////////////////
// Eingabe via scanf


// scanf verwendet call-by-reference
void scanEx() {
  int i;
  char c;

  printf("\nEingabe von Zeichen:");
  scanf("%c",&c);
  printf("\nEingabe von Zahl:");
  scanf("%d",&i);

  printf("%d %c",i,c);

}


//////////////////////////////////////////////////
// Strukturen

struct Punkt {
  int x;
  int y;
};

void printPunkt(struct Punkt p) {
  printf("\n Punkt = (%d,%d)", p.x, p.y);
}


struct Punkt scanPunkt() {
  struct Punkt p;

  printf("\n Eingabe x:");
  scanf("%d", &p.x);         // &p.x entspricht &(p.x)

  printf("\n Eingabe y:");
  scanf("%d", &p.y);

  return p;
}

// In C gilt call-by value.
// Deshalb ist die Eingabe nicht von aussen sichtbar.
void scanPunkt2(struct Punkt p) {
  struct Punkt r;
  r = scanPunkt();
  p = r; // Bedeutet, p.x = r.x; p.y = r.y;
}

// Call-by-reference via Zeiger ("pointer").
void scanPunktRef(struct Punkt* p) {
  struct Punkt r;

  r = scanPunkt();

  (*p).x = r.x;  // Beachte: *p.x bedeutet Zugriff auf x und danach Dereferenzierung.
  (*p).y = r.y;
}

void punkt() {
  struct Punkt p;
  struct Punkt q;
  struct Punkt r;

  p = scanPunkt();
  printPunkt(p);

  scanPunkt2(q);
  printPunkt(q);

  scanPunktRef(&r);     // Uebergebe Referenz auf r
  printPunkt(r);
}

//////////////////////////////////////////////////
// main, wird immer am Anfang aufgerufen

int main() {
  printHallo();
  printEx();
  printEx2();
  references();
  cbvCBR();
  scanEx();
  punkt();
}

Zeigerarithmetik

#include <stdio.h>

/*

- Arrays
- Zeigerarithmetik
- Strings
- Funktionszeiger

*/


/////////////////////////////////////////
// Arrays

// Array
// 1. Elemente sind alle vom gleichen Typ
// 2. Fixe Länge
// 3. Kein "out-of-bounds" check!

void arrays() {
  int i;
  int a[5] = { 1, 2, 3, 4, 5};

  for(i=0; i < 6; i++) {
    printf("%d", a[i]);
  }


}

/////////////////////////////////////////
// Zeigerarithmetik

// 1. Arrayname ist eine Referenz auf das erste Element
// 2. Zeiger ("ponter") ist ein Datentyp welche eine Referenz auf einen Wert verwaltet.
// 3. p sei ein Zeiger dann ist
//     p + 1   die Referenz auf den Nachfolger und
//     p - 1   die Referenz auf den Vorgänger

void zeiger() {
  int i;
  int a[5] = { 1, 2, 3, 4, 5};
  int* p;

  p = a;
  // entspricht
  // p = &a[0];

  printf("%d", *p);  // Ausgabe erstes Element
  p = p + 1;         // Zeiger verweist auf zweites Element
  printf("%d", *p);  // Ausgabe zweites Element
  p = p + 2;
  printf("%d", *p);  // Ausgabe viertes Element
  p = p - 1;
  printf("%d", *p);  // Ausgabe drittes Element

}

// 1. Zeigervergleich (Vergleich der Speicheradressen)
// 2. Typ auf welcher Zeiger verweist ist wichtig fuer Dereferenzierung.
// 3. Zeiger von verschiedenem Typ sind inkompatibel.

void zeiger2() {
  int a[3] = { 1, 2, 3 };
  int* p;
  float* q;

  for(p=a; p < a+3; p++) {
    printf("%d", *p);
  }

  q = p; // warning: incompatible pointer types assigning to 'float *' from 'int *'

}


/////////////////////////////////////////
// Strings

// 1. Eine String ist ein Array von Zeichen
// 2. In C wird folgender Trick angewandt um die Laenge des strings zu verwalten.
// 3. Das Ende eines strings in C wird mit einem speziellen ASCII Code gekennzeichnet.
// Der ASCII Code 0 ("Null") wird verwendet, um das Ende des strings zu markieren.
// Dies wird auch als "Null Terminierung" bezeichnet und diesen
// speziellen ASCII Code nennen wir auch Null Terminator.


int len(char s[]) {
  int i = 0;

  while(s[i] != '\0') {
    i++;
  }
  return i;
}

// Variante.
// char s[] entspricht const char*s
// Da wir den Zeiger s inkrementieren verwenden wir char* s.
int len2(char* s) {
  int i = 0;

  while(*s != '\0') {
    i++;
    s++;
  }
  return i;
}


void strings() {

  char str[] = "Hello";

  char str2[] = "12";

  // Falls elementweise Initialisierung muss explizit der Nullterminator am Schluss stehen!
  char str3[] = { '1', '2', '\0'};

  char str4[] = { '1', '2'};


  printf("%d", len(str));
  printf("%d", len2(str));

  printf("%d", len(str3));
  printf("%d", len(str4));

}


/////////////////////////////////////////
// Funktionszeiger


// Parameter f ist ein Zeiger (Referenz) auf eine Funktion.
// Diese Funktion erwartet einen int Wert und liefert einen int Wert zurueck.
void mapInt(int* p, int len, int (*f)(int)) {
 int i;
 for(i=0; i<len; i++)
   p[i] = (*f)(p[i]);
}

// Beachte.
// Oft werden "shorthands" via typedef verwendet.

typedef int (*intFunc)(int);

void mapInt2(int* p, int len, intFunc f) {
 int i;
 for(i=0; i<len; i++)
   p[i] = (*f)(p[i]);
}

// In "modernem" C koennen wir schreiben.
void mapIn3(int* p, int len, int f(int)) {
 int i;
 for(i=0; i<len; i++)
   p[i] = (*f)(p[i]);
}



int inc(int x) { return x+1; }
int square(int x) { return x*x; }


void funktionsZeiger() {
   int x[5] = { 1,2,3,4,5 };
   mapInt(x,5,&inc);
   mapInt(x,5,&square);

   printf("%d", x[1]);
}


int main() {
  arrays();
  zeiger();
  zeiger2();
  strings();
  funktionsZeiger();
}

Speicherverwaltung


#include <stdio.h>
#include <stdlib.h> // malloc, free

/*

Speicherverwaltung:

- Stack versus Heap
- malloc und free, zu viele versus zu wenige "deletes"

*/

//////////////////////////////////////////////////
// Stack allokierter Speicher

void stack() {
  int i;
  int* p;

  i = 2;
  p = &i;   // p verweist auf Stack-allokierten Speicherbereich
            // Automatische Freigabe nach Ruecksprung aus dieser Funktion

  printf("%d", *p);
}

// "warning: address of stack memory associated with local variable 'i' returned"
int* stackMem() {
  int i;

  i = 2;
  printf("%d",i);

  return &i;
}

void stackInvalidAccess() {
  int* p;

  p = stackMem();
  printf("%d", *p);

}


//////////////////////////////////////////////////
// Heap allokierter Speicher

void heapFehlendesDelete() {
  int* p;

  p = (int*)malloc(sizeof(int));   // Heap Allokation
  //               ^^^^^^^^^^^
  //               Anzahl der Bytes
  //  ^^^^^
  //  Zeiger auf int

  *p = 2;

  printf("%d", *p);

  // Speicherleck.
  // Heap allokierte Objekte muessen explizit freigegeben werden (manuelle Speicherverwaltung)
}


void heap() {
  int* p;

  p = (int*)malloc(sizeof(int));   // Heap Allokation

  *p = 2;

  printf("%d", *p);

  free(p);                         // Speicherfreigabe
}


void heapDoppeltesDelete() {
  int* p;

  p = (int*)malloc(sizeof(int));   // Heap Allokation

  *p = 2;

  printf("%d", *p);

  free(p);                         // Speicherfreigabe
  free(p);                         // Speicherfreigabe
}


void heapZufruehesDelete() {
  int* p;

  p = (int*)malloc(sizeof(int));   // Heap Allokation

  *p = 2;

  free(p);                         // Speicherfreigabe

  printf("%d", *p);

}

int main() {
  stack();
  stackInvalidAccess();
  heapFehlendesDelete();
  heap();
  // heapDoppeltesDelete(); // fuehrt zum Absturz
  heapZufruehesDelete();    // kann zum Absturz fuehren


}

Testverfahren

#include <stdio.h>
#include <stdlib.h>


// Punkt im 2-dimensionalen Koordinatensystem
typedef struct { int x; int y; } point;



// Gleichheit zwischen Punkten
int eqPoint(point p, point q) {
  return (p.x == q.x) && (p.y == q.y);
}

// Ausgabe eines Punktes
void printPoint(point p) {
  printf("\n (x = %d, y = %d)", p.x, p.y);
}

// Koordinaten vertauschen
// Buggy !!!!!!!!!!!!
point switchPoint(point p) {
  p.x = p.y;
  p.y = p.x;

  return p;
}

//////////////////////
// Tests

// User Tests

void testUser() {
  point p = {2,2};

  printPoint(p);
  printPoint(switchPoint(p));
}

// Unit Tests

typedef struct { point input; point expected; } TestCase;

void testUnit() {
  int i;

  TestCase tests[] = {
    {{1,1}, {1,1}},
    {{2,2}, {2,2}}
  };


  for(i=0; i<2; i++) {
    if(eqPoint(switchPoint(tests[i].input),
           tests[i].expected)) {
      printf("\nOK");
    } else {
      printf("\nFAIL");
    }
  }
}

// Invarianten = Properties = Eigenschaften


int property1(point p) {
  return eqPoint(switchPoint(switchPoint(p)),
         p);
}

void testInvariante() {
  int i;
  point ps[10];

  // Generiere 10 Punkte zufaellig.
  for(i=0; i<10; i++) {
    ps[i].x = rand();
    ps[i].y = rand();
  }

  for(i=0; i<10; i++) {
    if(property1(ps[i])) {
      printf("\nOK");
    } else {
      printf("\nFAIL");
    }
  }

}

int main() {
  testUser();
  testUnit();
  testInvariante();
}

Generische Datentypen und Funktionen in C

// Generische Datentypen und Funktionen in C.

#include <stdio.h>
#include <stdlib.h>

enum Type {
  INT = 0,
  FLOAT = 1
};

// Generische Variante
// void* ist der generischer Zeiger in C.
// Siehe "Object" in Java.
// void* ist wie ein Zeiger ohne Typinformation.
struct GenArray {
  enum Type type;
  void* start;
  int len;
};

int sizeOf(enum Type type) {
  switch(type) {
  case INT: return sizeof(int);
  case FLOAT: return sizeof(float);
  }
}

struct GenArray new_GenArray(int n, enum Type type) {
  void* q = malloc(n * sizeOf(type));

  struct GenArray x = {type, q, n};

  return x;
}


// Dereferenzierung von void* nicht moeglich.
// Wir verwenden die in dem enum kodierte Typinformation,
// um auf int*, float*, etc. zu casten.
void print_Type(void* elem, enum Type type) {
  int* i;
  float* f;

  switch(type) {
  case INT:
    i = (int*)(elem);
    printf("%d", *i);
    break;
  case FLOAT:
    f = (float*)(elem);
    printf("%f", *f);
    break;
  }
}

// Pointer Arithmetik fuer generischen Zeiger.
// Die in dem enum kodierte Typinformation ist wiederum wichtig.
void* inc_pointer_Type(void* p, enum Type type) {
  int* i;
  float* f;

  switch(type) {
  case INT:
    i = (int*)(p);
    i++;
    p = i;
    break;
  case FLOAT:
    f = (float*)(p);
    f++;
    p = f;
    break;
  }
  return p;
}

void print_GenArray(struct GenArray x) {
  int i=0;
  void* p = x.start;

  for(i=0; i<x.len; i++) {
    print_Type(p, x.type);
    p = inc_pointer_Type(p, x.type);
  }
}

void update_GenArray(struct GenArray x, int pos, void* new_Val) {
  int* i; int* i_Val;
  float* f; float* f_Val;

  // "array" out-of-bounds check
  if(pos >=0 && pos < x.len) {
    switch(x.type) {
    case INT:
      i = (int*)(x.start);
      i_Val = (int*)(new_Val);
      i[pos] = *i_Val;
      break;
    case FLOAT:
      f = (float*)(x.start);
      f_Val = (float*)(new_Val);
      f[pos] = *f_Val;
      break;
    }
  }

}

void test_GenArray() {
  struct GenArray x = new_GenArray(2, INT);
  struct GenArray y = new_GenArray(3, FLOAT);
  int i = 5;
  float f = 1.0;

  update_GenArray(x,0,&i);
  update_GenArray(x,1,&i);

  update_GenArray(y,0,&f);
  update_GenArray(y,1,&f);
  update_GenArray(y,2,&f);

  print_GenArray(x);

  print_GenArray(y);
}

int main() {
  printf("\n");
  test_GenArray();
}

OO in C selbst nachgebaut

#include <stdio.h>
#include <stdlib.h>

/*

Geometric objects.

Java pseudo-code.

class Shape {
 int area();
}

class Rectangle extends Shape {
  int length;
  int width;
  int area() { return length * width; }
}

class Square extends Shape {
  int length;
  int area() { return length * length; }
}

// Annahme, frei-stehende Funktionen in Java moeglich.
int sumArea (Shape s1, Shape s2) {
  return s1.area() + s2.area();
}

 */

// Geometric objects in C

typedef struct {
  int length;
  int width;
} rectangle;

typedef struct {
  int length;
} square;


int areaRectangle(rectangle* r) {
  return r->length * r->width;
}

int areaSquare(square* s) {
  return s->length * s->length;
}


// Generic representation

typedef struct {
  void* obj;
  int (*area)(void*); // Function pointer
} shape;


// Generic function
int sumArea(shape s1, shape s2) {
  return s1.area(s1.obj) + s2.area(s2.obj);
}

// Generic functionc (same as above)
int sumArea2(shape s1, shape s2) {
  return (*(s1.area))(s1.obj) + s2.area(s2.obj);
}

// Points to note.
// In modern C, there's no need to dereference a function pointer.
// "." binds tighter than "*".
// Hence, the expression
//     (*(s1.area))(s1.obj)
// is equivalent to
//     s1.area(s1.obj)

// Some examples
void testShape() {
  rectangle r = {1,2};
  square s = {3};
  shape s1 = {&r, (int (*)(void *))(&areaRectangle)};
  shape s2 = {&s, (int (*)(void *))(&areaSquare)};
  shape s3 = {&s, (int (*)(void *))(&areaRectangle)};   // (P)
  shape s4 = {&r, (int (*)(void *))(&areaSquare)};      // (Q)

  printf("\n %d", sumArea(s1,s2));

  printf("\n %d", sumArea(s3,s4));
}

/*

Points to note.

We emulate here "OO" in C. The user needs to precisely follow some coding guidelines.

Consider (P) and (Q).
The "object" (structure) does not match the "method" (function).
The coding guidelines for emulating OO in C are broken.
This may lead to some run-time errors.

*/


int main() {
  testShape();
}