Notizen zum Intensivkurs C [Notizen zum Intensivkurs C] (Notizen zum Intensivkurs C), Lektion, Seite 722302
https://www.purl.org/stefan_ram/pub/notizen_zum_intensivkurs_c (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Notizen zum Intensivkurs C

Kombinationen von Reihungen, Zeiger und Strukturen

Reihungen

Reihungen von Reihungen

Reihungen von Reihungen wurde schon im Rahmen der Behandlung zweidimensionaler Reihungen behandelt.

Reihungen von Zeigern

Reihungen von Zeigern wurden schon am Beispiel von »argv« (dem zweiten Parameter von »main«) behandelt.

Reihungen von Strukturen

struct complex { double real; double image; } z[ 10 ];

Strukturen

Reihungen in Strukturen

struct complex { double x[ 2 ]; };

Zeiger in Strukturen

Zeiger in Strukturen wurden schon bei den Listen behandelt.

Strukturen in Strukturen

struct dlist { struct dlist * prev; struct dlist * next; };
struct nlist { struct dlist header; int number; };
struct clist { struct dlist header; struct complex z; };
...
struct nlist * this;
...
... ( struct dlist * )this ...

Zeiger

Zeiger auf Reihungen

Zeiger auf Reihungen wurde auch schon bei den zweidimensionalen Reihungen behandelt. Sie sind aber selten, da Reihungen meistens durch Zeiger auf deren erste Komponente repräsentiert werden.

Zeiger auf Zeiger

Zeiger auf Zeiger wurden ebenfalls schon in Zusammenhang mit »argv« diskutiert, das ja als »char ** argv« definiert werden kann.

Zeiger auf Strukturen

Zeiger auf Strukturen wurden schon öfter behandelt, etwa in Zusammenhang mit verketteten Listen.

Fortsetzung

Ältere Notizen

Zu Zeigern
  • Nach »int x;«: welchen Datentyp hat der Ausdruck »&x« und was bedeutet er? Was kann man zu »&&x« sagen? Was zu »&5«?
  • Enthält »int * x; *x = 0;« einen Fehler?
  • Enthält »int * x; scanf( "%d", x );« einen Fehler?
  • Nach »int * x;«: welchen Datentyp hat der Ausdruck »*x« und was bedeutet er?
  • Enthält »int a = 2; int * b = &a; *b = 2;« einen Fehler?
  • Enthält »int a = 2; *&a = 2;« einen Fehler?
  • Enthält »int a = 2; &*a = 2;« einen Fehler?
  • Wie wird nach »int a = 5; int * b = &a; int ** c = &b;« der Wert in »a« (also »5«) angegeben, wenn dazu nur »c« verwendet werden darf? Oder ist dies gar nicht möglich?
  • Nach »char const * x = "test";«: Was ist der kürzeste Ausdruck um das erste Zeichen des Textes anzugeben? Was ist der kürzeste Ausdruck um das zweite Zeichen des Textes anzugeben?
  • Nach »char const * x = "test";«: Welchen Wert hat »*++x«?
  • Nach »int x[2];«: Welchen Wert hat »( char * )( x + 1 )-( char * )x«?
  • Nach »int x[2];«: Ist die Zuweisung »( char * )x = 2;« erlaubt?
  • Was könnte »*( char * )100« bedeuten?
offene Fragen:
  • Systemaufrufe aus welchem Bereich (z.B. Dateizugriffe?)
  • Welche Gebiete von Listen/Bäumen werden neben den Grundlagen noch benötigt?
Bisher nur teilweise behandelte Themen/Punkte:
  • Vorgehensweisen bei der Erstellung größerer Programme, wie strukturierte Programmierung
Bisher noch offene Themen/Punkte:
  • Linux -Systemaufrufe

Behandlung von Laufzeitfehlern/Ressourcen in C 

Muster 0: Mangelhaft, aber manchmal ausreichend: keine Prüfungen, keine Befreiungen
struct a *a = malloc( sizeof *a );
a->val = 1;

Zu Muster 0: Wenn undefiniertes Verhalten in bestimmten Fällen bewußt in Kauf genommen werden soll, dann kann auf meistens  nicht benötigten Aufwand verzichtet werden. Man sagt auch das Programm sei “quick and dirty ” programmiert oder „mit heißer Nadel gestrickt“. Es ist nicht perfekt, aber erfüllt oft schon seinen Zweck.

Muster 1: Programmabbruch, manchmal akzeptabel in Anwendungsprogramm je nach Aufgabenstellung
struct a *a = malloc( sizeof *a );
if( !a )abort();
a->val = 1;
/* später eventuell free(), je nach Aufgabenstellung */

Zu Muster 1: Etwas besser als das Muster 0 ist das Muster 1, da es undefiniertes Verhalten vermeidet. Ob ein Programmabbruch aber immer akzeptabel ist, das kann nicht allgemein beantwortet werden, sondern muß an Hand der spezifischen Anforderungen eines Software-Projekts ermittelt werden.

Muster 2: „richtige“ Behandlung, nötig in Programmbibliotheken, statischer Fall
struct a *a = malloc( sizeof *a );
if( a )
{ a->val = 1;
/* ... hier a verwenden ... */
free( a ); }

Zu Muster 2: Wenn ein Programmteil zu einer Bibliothek gehören soll, dann ist es normalerweise unpassend, wenn er – wie bei Muster 0 – undefiniertes Verhalten zeigen oder – wie bei Muster 1 – das Programm abbrechen würde.

Die Entscheidung über ein Programmende oder einen Programmabbruch sollte von der Anwendung (dem Hauptprogramm) getroffen werden und nie von einem Teil einer Bibliothek.

Im Englischen sagt man dazu auch manchmal, daß eine Bibliothek “policy neutral ” sein müsse.

Daher ist es dann richtig, den Aufrufer über das Scheitern einer Anforderung zu informieren und diesen entscheiden zu lassen, wie darauf reagiert wird.

Muster 3: „richtige“ Behandlung, nötig in Programmbibliotheken, dynamischer Fall
struct a *a = malloc( sizeof *a );
if( a )
{ a->val = 1;
/* ... hier a verwenden ... */ } ... if( a )free( a )

Zu Muster 3: Im statischen Fall wird eine Ressource zurückgegeben, wenn eine Anweisung (»free( a );« im Muster 2) ausgeführt wird, die nur nach dem Erfolg der entsprechenden Anforderung ausgeführt wird (»if( a )« im Muster 2). Beim Aufbau komplizierterer Datenstrukturen reicht diese Vorgehensweise nicht mehr aus. Dann muß die Information über den Erfolg einer früheren Anforderung den Datenstrukturen selber entnommen werden, wie dies mit dem – in Vergleich zum Muster 2 zusätzlichem – »if( a )« im Muster 3 geschieht.

Verstecken hinter API: „richtige“ Behandlung hinter API versteckt
struct a *new_a()
{ struct a *a = malloc( sizeof *a );
if( a )a->val = 1;
return a; } void dispose_a( struct a *const a )
{ free( a ); } ...

Verstecken hinter API: Wenn Programmteile, die Ressourcen anfordern, hinter einer API versteckt werden, sind meist zwei Funktionen nötig: Eine zum Anlegen einer Einheit und eine weitere zum Freigeben. Diese beiden Funktionen bilden selber wieder eine neue Ressource: Die erste muß dem Aufrufer zurückmelden, ob sie erfolgreich war, und dieser muß in diesem später die zweite Aufrufe, um diese Ressource wieder freizugeben.

Verschachtelte Anforderungen in Bibliothek: „richtige“ Behandlung mit einer „Transaktion“
struct a *new_a()
{ int error = 1;
struct a *a = malloc( sizeof *a );
if( a )
{ struct b *b = malloc( sizeof *b );
if( b )
{ a->b = b; error = 0;
/* ... eventuell weitere Ressourcen ... */
if( error )free( b ); }
if( error )free( a ); }
return a; } void dispose_a( struct a *const a )
{ /* eventuell weitere zur Freigabe nötige Aktionen */
free( a->b ); free( a ); }

Verschachtelte Anforderungen: Wenn mehrere Ressourcen benötigt werden, so ist besondere Sorgfalt nötig. Die erstellende Funktion wird ja entweder erfolgreich gewesen sein oder gescheitert sein. Es kann aber sein, daß sie bei mehreren nötigen Anforderungen einige schon erhalten hat, wenn dann eine Anforderung schließlich scheitert. In diesem Fall müssen genau die bisher erfolgreich angeforderten Ressourcen wieder zurückgegeben werden (oben: »if( error )free( a );«), dann der Aufrufer erwartet nach einem Scheitern, daß keine Ressourcen von ihm mehr freizugeben sind.

Das obige Programmbeispiel nimmt an, daß eine neue Struktur »a« selber noch einen Zeiger auf eine neue Struktur »b« benötigt. Es sind dann drei Fälle richtig zu behandeln:

Die Verwendung einer Hilfsvariablen hilft hierbei die Übersicht zu wahren: Diese (oben: »error«) wird erst auf 0 gesetzt, wenn alle  Anforderungen erfolgreich waren. Ist sie am Ende eines Blocks nicht 0, so wird die Ressource wieder freigegeben, deren erfolgreiche Anforderung zuvor zur Ausführung dieses Blocks führte.

Diese Vorgehensweise ist jedoch nur in Bibliotheken  nötig. In einem Hauptprogramm  (einer Anwendung) kann der Rest des Programms, der die Ressourcen benötigt, dann direkt aufgerufen werden.

Verschachtelte Anforderungen in Hauptprogramm: 
int main( void )
{ int result = EXIT_FAILURE;
struct a *a = malloc( sizeof *a );
if( a )
{ struct b *b = malloc( sizeof *b );
if( b )
{ a->b = b; result = use( a, b ); /* Rest des Programms */
free( b ); }
free( a ); }
return result; }

Besitz (“ownership ”) Manchmal nennt man einen Programmteil, der für die Freigabe einer Ressource verantwortlich ist, den „Besitzer “ dieser Ressource. Besitz in diesem Sinne ist also Verpflichtung, eine Last. Wenn ein Ressource  eine Referenz auf eine andere Ressource enthält, dann nennt man die erste Ressource den „Besitzer “ der zweiten Ressource, falls bei einer Freigabe der ersten Ressource auch die zweite freigegeben werden soll. (In anderen Zusammenhängen kann „Besitz“ auch etwas andere Bedeutungen haben, wie etwas den Besitz an Schreib- oder Leserechten für eine Ressource.)

Besitzerwechsel (“transfer of ownership ”) Wenn eine Referenz auf eine Ressource an einen anderen Programmteil übergeben wird oder in eine andere Ressource geschrieben wird, dann kann damit auch ein Besitzerwechsel vereinbart werden, demzufolge nun diese Ressource oder der andere Programmteil der Besitzer der Ressource wird. Wenn »malloc« einen Zeiger auf eine Objekt zurückgibt, dann übergibt es damit den Besitz an diesem Objekt beispielsweise an den Aufrufer.

Objektressourcen Bei Objekten (also Speicher) verkompliziert sich die Handhabung noch dadurch, daß es auch Objekte gibt, die nicht freizugeben ist, nämlich Objekte mit statischer oder automatischer Lebensdauer. Wenn also vereinbart wird, daß eine bestimmte Funktion den Besitz an einem Objekt erhält, wenn ihr ein Zeiger übergeben wird, dann darf dies im allgemeinen kein Zeiger auf ein Objekt mit statischer oder automatischer Lebensdauer sein (oder solche Objekte müssen gekennzeichnet werden).

Alleinbesitzer Im einfachsten Fall hat jede Ressource genau einen Besitzer. Andere Programmteile oder Objekte könnten auch eine Referenz auf diese Ressource verwenden oder enthalten, aber nur ein Programmteil ist für die Freigabe zuständig. Dies sollte wird natürlich so eingerichtet werden, daß die Freigabe durch den Besitzer erst dann erfolgt, wenn die Ressource auch von keinem anderen Programmteil mehr benötigt wird.

Dokumentation In der Dokumentation von Programmteilen oder Datenstrukturen sollte immer klargestellt werden, wer der Besitzer einer Ressource ist und wann Besitzerwechsel (etwa bei der Übergabe von Argumenten oder Rückgaben von Ergebnissen) stattfinden.

Übergabe an Aufrufer Bei der Übergabe des Besitzes an den Aufrufer gibt eine Funktion eine Referenz auf eine von ihr erzeugte Ressource und den Besitz an dieser Ressource an den Aufrufer zurück. Beispiel: »malloc( 10 )«, wenn es nicht 0 ist. Dies ist damit verträglich, daß die Kontrolle nach der Ausführung des Aufrufs im allgemeinen ja nicht mehr an die aufgerufene Funktion zurückkehrt, so daß diese auch gar nicht in der Lage wäre, die Ressource später wieder freizugeben.

Verbleib beim Aufrufer Bei Aufruferbesitz übergibt ein Aufrufer eine (Referenz auf )eine von ihm besessene Ressource an einen aufgerufenen Programmteil, aber bleibt weiterhin im Besitz dieser Ressource. Beispiel: »printf( "%p", pointer );«, wenn »pointer« auf eine Ressource zeigt. Dies ist damit verträglich, daß die Kontrolle nach der Ausführung des Aufrufs ja an den Aufrufer zurückgeht, so daß er dann auch die Möglichkeit hat, die Ressource freizugeben.

Übergabe an aufgerufene Funktion Der Besitz an einer Ressource kann aber auch an eine aufgerufene Funktion übergeben werden. Beispiel: »free( pointer );«, wenn »pointer« auf ein Objekt mit allozierter Lebensdauer zeigt. Dies ist für den Aufrufer eine Möglichkeit, den Besitz dauerhaft aufzugeben, sich also von seinem Besitz wieder zu entlasten.

Mehrere Besitzer, geteilte Ressourcen In machen Fällen kann es erwünscht sein, daß eine Ressource mehrere Besitzer  hat (etwa, wenn zwei Bäume einen gemeinsamen Unterbaum teilen oder zwei Programmteile eine Datenbankverbindung nutzen). Dann muß sie erst dann freigegeben werden, wenn keiner dieser Besitzer ihrer nicht mehr bedarf. Hierzu kann ein „Referenzzähler“ in der Ressource verwendet werden (“reference counting ”), der bei jedem Erlangen eines Besitzes an dieser Ressource inkrementiert wird und dekrementiert wird, wenn ein bisheriger Besitzer den Besitz aufgeben will. Die Ressource wird genau dann freigegeben, wenn dieser Zähler den Wert 0 erreicht.

Automatische Ressourcenverwaltung Die Verwendung von Referenzzählung ist fehlerträchtig und oft nicht mehr möglich, wenn auch zirkuläre Referenzbezüge möglich sind, weil dann zwei Ressourcen, die sonst gar nicht mehr benötigt werden, es durch gegenseitige Verweise verhindern, daß ihre Referenzzähler jemals 0 werden. Eine automatische Ressourcenverwaltung ermitteln zu bestimmten Zeitpunkten, welche Ressourcen von einem Programm überhaupt noch erreichbar sind und gibt alle andere frei. Ein Beispiel für solche eine automatische Ressourcenverwaltung ist eine automatische Speicherverwaltung (“memory manager ”, “garbage collector ”).

Refaktor „Extraktion einer ‚inneren Funktion‘“ (“Extract Method ”)

Bei der strukturierten Ressourcenbehandlung sind die inhaltlich nahe zusammengehörigen Teile des Quellcodes zur Ressourcenverwaltung manchmal weit voneinander entfernt, wie in dem folgenden Programmbeispiel die Anforderung mit »malloc« und die Freigabe mit »free«. Dies ist besonders bei größeren Methoden und der Verschachtelung solcher Anweisungsstrukturen unübersichtlich und wird manchmal als Argument gegen die strukturierte Programmierung und zugunsten des Verlassens von Blöcken mit Sprunganweisungen vorgebracht.
Monolithische strukturierte Ressourcenbehandlung
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };

int main( void )
{ /* Verwaltung */
int result = EXIT_FAILURE;

struct account * const p = malloc( sizeof *p );
if( p )
{ /* Verwendung */
p->name = "Hans"; p->balance = 3.14;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance ); /* Verwaltung (Fortsetzung) */
free( p );

result = EXIT_SUCCESS; }
return result; }
Doch tatsächlich ist das Problem bei dieser Vorgehensweise gar nicht die strukturierte Programmierung, sondern die monolithische Programmierung. Die monolithische Funktion kann durch Herauslösen einer „inneren Funktion“ in zwei Funktionen zerlegt werden, von denen die eine sich nur mit der Verwaltung einer Ressource beschäftigt und die andere diese Ressource nur verwendet, ohne sie zu verwalten.
Modulare strukturierte Ressourcenbehandlung
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; }; int use( struct account * const p )
{ p->name = "Hans"; p->balance = 3.14;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance );
return EXIT_SUCCESS; } int main( void )
{ int result = EXIT_FAILURE;
struct account * const p = malloc( sizeof *p );
if( p )
{ result = use( p );
free( p ); }
return result; }
Man sieht in dem voranstehenden Programm die Gleichheit der Deklaration des Parameters »p« mit dem Anfang der Deklaration der Variablen »p«. Um den Parameter richtig zu deklarieren, kann also einfach ein Teil der Variablendeklaration kopiert werden.
Der Erfolgsstatus  eines Programms kann vom Erfolgsstatus der „inneren Funktion“ abhängen, so daß dieser in der Methode »main« als Ergebnis festgelegt wird, obwohl er bei diesem Programm derzeit stets »EXIT_SUCCESS« ist.

Der extrahierte Teil wird hier „innere Funktion“ genannte, weil er hier aus dem Inneren  einer Anweisungsstruktur stammt, in der es sowohl vor ihm als auch nach ihm noch etwas steht.

Notizen zum Lernen

Es folgen zwei Quellenangaben als Nachtrag zu entsprechenden früheren mündlichen Äußerungen.

Zur Lerngeschwindigkeit (Langzeitgedächtnis)

Zur Frage wieviele Bit pro Sekunde das Langzeitgedächtnis aufnehmen kann, findet man unterschiedliche Angaben. Beispielsweise

Die Speicherung erfolgt sehr langsam mit 0,05 Bit/s 

http://www.medienwissenschaft.hu-berlin.de/vlz/Ged%E4chtnisNetz.pdf

Wenn diese eine gehirnphysiologische Gesetzmäßigkeit sein sollte, dann kann ein Kurs also nicht beliebig „intensiviert“ werden, um einen noch höheren Informationsfluß zu erreichen.

Zu Übungsaufgaben, die nicht richtig gelöst werden

Zum Lernen durch Fehlversuche:

getting the wrong answer helps us remember the right one 

http://scienceblogs.com/cortex/2009/10/learning_from_mistakes.php

Weitere, ältere Notizen

Funktionszeiger

Aufbau von Software-Projekten

Zielsetzung der Lehrveranstaltung

In den obigen Notizen schon berücksichtigt

In den obigen Notizen erst teilweise berücksichtigt

In den obigen Notizen noch nicht berücksichtigt

Wünsche zur Lehrveranstaltung

Nach den ersten beiden Terminen

Motivation zu C

Obwohl die C -Programmierung manchmal schwierig und fehlerträchtig ist, ist es doch schwierig, eine bessere Alternative zu C  zu finden.

“Here's the thing: C is everywhere. Recently Tim Bray made basically the same point; all the major operating systems, all the high-level language runtimes, all the databases, and all major productivity applications are written in C.”

http://girtby.net/archives/2008/08/23/in-defence-of-c/

Nach einer Statistik wird C  heute deutlich häufiger eingesetzt als C++ , dabei hat C  einen positiven und C++  einen negativen Trend. C  könnte sogar Java  bald (wieder) als die populärste Sprache ablösen.

http://www.tiobe.com/content/paperinfo/tpci/index.html

Auch für Perl 6 (Parrot) spielt C eine wichtige Rolle:

=head2 What language is Parrot written in?
C.
=head2 For the love of God, man, why?!?!?!?
Because it's the best we've got.

http://search.cpan.org/src/SFINK/parrot-0.0.11.2/docs/faq.pod

Web-Quellen zu C

Vorläufige Notizen zu weiteren Themen (Fragment)

Zeiger

Listen

Bäume

Graphen

Weitere Aspekte der Programmiersprache C

Weitere Aspekte des Programmierens

Weitere Aspekte der Linux-Programmierung

Einige Quellen

C-FAQs

http://www.faqs.org/faqs/C-faq/abridged/

http://www.lysator.liu.se/c/c-faq/c-10.html

Auf den ersten Blick ganz gute Quellen:

http://www.mers.byu.edu/docs/standardC/index.html#Table%20of%20Contents

http://publications.gbdirect.co.uk/c_book/

Einige Werkzeuge

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722302 stefan_ram:722302 Notizen zum Intensivkurs C Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722302, slrprddef722302, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten.
https://www.purl.org/stefan_ram/pub/notizen_zum_intensivkurs_c