Tutoriumsinhalt

  • Bits & Bytes
  • Command-line Arguments
  • Dynamischer Speicher
  • sizeof
  • Strukturen
  • File I/O
  • Pipes
  • Ausbesserungsbeispiel

Bits & Bytes

  • AND (auf uint8_t)
    • output = output & 0x80;
    • output &= (1 << 7);
  • OR (auf uint8_t)
    • output = output | 0x10;
    • output |= (1 << 4);
  • XOR (auf uint8_t)
    • output = output ^ 0x01;
    • output ^= (1 << 0);

Command-line Arguments

int main (int argc, char *argv[])
{
  int count;
  printf ("This program was called with \"%s\".\n",argv[0]);

  if (argc > 1)
  {
    for (count = 1; count < argc; count++)
    {
      printf("argv[%d] = %s\n", count, argv[count]);
    }
  }
  else
  {
    printf("The command had no other arguments.\n");
  }
  return 0;
}
  

Dynamischer Speicher

  • Wird zur Laufzeit angefordert
  • Hat keinen »Namen«, nur eine Adresse → Zugriff über Pointer
  • Angeforderter Speicher muss auch wieder freigegeben werden
  • void* malloc(size_t size) fordert Speicher der Größe size an (in Bytes angegeben)
  • void free(void* ptr) gibt angeforderten Speicher wieder frei
  • void* realloc(void* ptr, size_t new_size) vergrößert einen angeforderten Speicherbereich

Beispiel

1int main() { 2 int* mem = (int*)malloc(sizeof(int) * 2); 3 if(mem == NULL) return 0; 4 *mem = 1; 5 *(mem + 1) = 2; 6 mem[0] = 3; 7 mem[1] = 4; 8 free(mem); return 0; }
0x1000
0x1004
0x1008
0x100C
0x1010
0x1000
0x1004
0x1008
0x100C
mem   0x10100x1000
0x1000
0x1004
0x1008
0x100C
mem   0x10100x1000
0x10001
0x1004
0x1008
0x100C
mem   0x10100x1000
0x10001
0x10042
0x1008
0x100C
mem   0x10100x1000
0x10003
0x10042
0x1008
0x100C
mem   0x10100x1000
0x10003
0x10044
0x1008
0x100C
mem   0x10100x1000
0x10003
0x10044
0x1008
0x100C
mem   0x10100x1000

Häufige Fehler / Best Practice

  • Pointer auf NULL initialisieren
  • Pointer nach free auf NULL setzen
  • malloc/realloc auf NULL überprüfen
  • Bei realloc Hilfspointer verwenden:
    char* text = (char*)malloc(8 * sizeof(char));
    char* new_text = (char*)realloc(text, 16 * sizeof(char));
    if(new_text != NULL)
    {
      text = new_text;
    }
    else
    {
      free(text);
      printf("Out of memory!\n");
    }
    

Warum nicht einfach?

1int* x = (int*)malloc(3 * sizeof(int)); 2x = (int*)realloc(x, 16); 3if(x == NULL) { 4 free(x); 5 printf("Out of memory!"); }
x   0x10000x1008
0x1004
0x1008
0x100C
0x1010
x   0x1000NULL
0x1004
0x1008
0x100C
0x1010
x   0x1000NULL
0x1004
0x1008
0x100C
0x1010
x   0x1000NULL
0x1004
0x1008
0x100C
0x1010
x   0x1000NULL
0x1004
0x1008
0x100C
0x1010
stdoutOut of memory!

Deshalb mit Hilfspointer

1int* x = (int*)malloc(3 * sizeof(int)); 2int* new_x = (int*)realloc(x, 16); 3if(new_x != NULL) { x = new_x; } 4else { 5 free(x); 6 printf("Out of memory!\n"); }
x   0x10000x1008
0x1004
0x1008
0x100C
0x1010
x   0x10000x1008
new_x   0x1004NULL
0x1008
0x100C
0x1010
x   0x10000x1008
new_x   0x1004NULL
0x1008
0x100C
0x1010
x   0x10000x1008
new_x   0x1004NULL
0x1008
0x100C
0x1010
x   0x10000x1008
new_x   0x1004NULL
0x1008
0x100C
0x1010
x   0x10000x1008
new_x   0x1004NULL
0x1008
0x100C
0x1010
stdoutOut of memory!

Der sizeof Operator

  • Operator (keine Funktion), nur für Größen, die zur Kompilierzeit bekannt sind

Funktioniert

  • int charsize = sizeof(char);
  • int ptrsize = sizeof(int*);
  • int size = sizeof("Hallo");
  • int x[20];
    int len =
      sizeof(x) / sizeof(x[0]);
    

Funktioniert nicht

  • int* x = (int*)malloc(100);
    int len = sizeof(x);
  • int x[20];
    int* y = x;
    int len =
      sizeof(y) / sizeof(y[0]);
    
  • char* hallo = "Hallo";
    int size = sizeof(hallo);
    

Verwendung von sizeof

  • Soll bei malloc/realloc verwendet werden, da diese die Größe in Byte verlangen
  • Beispiel: Integer Array mit 10 Elementen
  • Falsch
    int* x = (int*)malloc(10);
    
  • Richtig
    int* x = (int*)malloc(10 * sizeof(int));
    

Valgrind

  • Tool zum Finden von
    • Memory Leaks
    • unitialiserten Variablen
    • Off-by-one Fehlern
    • Segmentation Faults
  • Testsystem verwendet Valgrind → Valgrind Fehler geben Punkteabzug!
  • Nur für Linux

Beispiel

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

int main()
{
  char* text = (char*)malloc(6 * sizeof(char));
  strcpy(text, "Hallo!");
  printf("%s\n", text);
  return 0;
}
Kompilieren:
clang -Wall -g -o valgrind_test valgrind_test.c
Valgrind:
valgrind --leak-check=full ./valgrind_test

Weitere Valgrind-Beispiele

Strukturen

  • Zusammenfassung mehrer Variablen zu neuem Datentyp
  • Zugriff mit ».« bzw. »->« bei Pointern
  • #include <stdio.h>
    struct _Person_
    {
      char* name_;
      int age_;
    };
    
    int main()
    {
      struct _Person_ petra;
      petra.name_ = "Petra";
      petra.age_ = 17;
      return 0;
    }
    

Strukturen

  • Kürzere Schreibweise mit typedef:
    [...]
    
    typedef struct _Person_
    {
      char* name_;
      int age_;
    } Person;
    
    [...]
    
    Person petra;
    petra.name_ = "Petra";
    petra.age_ = 17;
    
    [...]
    

Strukturen dynamisch anlegen

  • Strukturen können ebenso wie andere Daten dynamisch angelegt werden
  • [...]
    typedef struct _Person_
    {
      char* name_;
      int age_;
    } Person;
    
    [...]
    Person* petra = (Person*)malloc(sizeof(Person));
    petra->name_ = "Petra";
    petra->age_ = 17;
    
    [...]
    free(petra);
    

Selbstreferenzierende Strukturen

  • Struktur kann Pointer auf Objekte vom eigenem Typ enthalten
  • struct _Person_
    {
      char* name_;
      int age_;
      struct _Person_* father_;
      struct _Person_* mother_;
    };
    
  • Funktioniert, da Pointer immer gleich groß sind (4 bzw. 8 Byte)
  • Für viele Datenstrukturen benötigt (z.B. Baum, Linked List)

Strukturen - Bitfelder

  • Zusammenfassung von Bits
  • Sinnvoll, wenn Funktion mehr als einen "Zustand" zurückgeben muss
  • #include <stdio.h>
    typedef struct _FileParserReturn_
    {
      char file_present_ : 1;
      char file_open_failed_ : 1;
      char ignored_ : 6;
    } FileParserReturn;
    
    FileParserReturn parseConfigFile( ... )
    {
      FileParserReturn return_value;
      
      ...
      return_value.file_present_ = 1;
    
      return return_value;
    }
    

Enums

  • "Zusammenfassung von #defines"
  • Sinnvoll, wenn Funktion mehrere verschiedene Werte zurückgibt
  • #include <stdio.h>
    typedef enum _FileParserReturn_
    {
      FILE_PRESENT,
      FILE_OPEN_FAILED
    } FileParserReturn;
    
    FileParserReturn parseConfigFile( ... )
    {
      ...
    
      return FILE_PRESENT;
    }
    

File I/O

  • Textdateien und Binärdateien
  • Textdateien
    • enthalten nur Text
    • können mit dem normalen Editor gelesen werden
    • z.B. .txt Dateien
  • Binärdateien
    • enthalten beliebige binäre Daten
    • sind nicht mit dem normalen Editor lesbar
    • z.B. Bilder, Musik

File I/O

  • Datei öffnen
    • fopen(const char * filename, const char * mode);
    • read
    • write
    • append
  • Daten lesen
    • fread(void * dst, size_t size, size_t items, FILE * file); 
  • Daten schreiben
    •  fwrite(const void * data, size_t size, size_t items, FILE * file); 
  • bzw. alle Funktionen, die mit "f" beginnen (z.B. fprintf, fscanf, fgets)

struct in Datei speichern

  • structs können in Binärdateien gespeichert werden
  • [...]
    struct _Phone_
    {
      int country_code_;
      int number_;
    }__attribute__((packed));
    
    [...]
    struct _Phone_ number;
    number.number_ = 1234567;
    number.country_code_ = 43;
    
    FILE* file = fopen("phone_numbers", "wb"); // wb = write binary
    fwrite(&number, sizeof(struct _Phone_), 1, file);
    fclose(file);
    

struct aus Datei lesen

  • structs können aus Binärdateien geladen werden
  • [...]
    struct _Phone_
    {
      int country_code_;
      int number_;
    }__attribute__((packed));
    
    [...]
    struct _Phone_ number;
    
    FILE* file = fopen("phone_numbers", "rb"); // rb = read binary
    fread(&number, sizeof(struct _Phone_), 1, file);
    fclose(file);
    

__attribute__((packed)) ?

  • wichtig wenn struct in Datei gespeichert werden soll
  • entfernt unnötige Padding-Bytes
struct _Code_ {
0:char symbol;
1:uint8_t bits;
2:0 (Padding)
3:0 (Padding)
4:char* code;
}
struct _Code_ {
0:char symbol;
1:uint8_t bits;
2:char* code;
}__attribute__((packed))

Dateigröße ermitteln

  • Keine C Funktion um die Dateigröße zu ermitteln
  • Folgende Vorgehensweise:
    1. Dateizeiger an das Ende der Datei setzen
    2. Abfragen, wo der Dateizeiger steht
    3. Dateizeiger wieder auf den Anfang der Datei setzen
  • Der Code dazu:
    long size;
    fseek(file, 0, SEEK_END);
    size = ftell(file);
    fseek(file, 0, SEEK_SET);
    

Pipes

Möglichkeit, die Ausgabe der einzelnen Befehle umzuleiten bzw. an andere Befehle weiterzuleiten.

  • Umleiten der Ausgabe in Datei
  • Standardeingabe von Datei lesen
  • Ausgabe als Eingabe für anderes Programm

Pipes

Ausgabe umleiten

./programm > ausgabe.txt

Eingabe umleiten

./programm < eingabe.txt

stderr umleiten

./programm 2> error.txt

Pipes

Beliebig kombinierbar

./programm < input.txt 1> output.txt 2> error.txt

Stdout als stdin eines anderen programms

./programm | tail

Returncode des letzten Programms

echo $?

Ausbesserungsbeispiel

  • Gute Übung!
  • Nur abgeben, wenn es benotet werden soll! (+ E-Mail an Tutor)
  • Beispiel mit wenigsten Punkten wird ersetzt
  • Aber: Punkteverlust möglich!

Schöne Feiertage!