Re: C-Programmierung unter Mac OS X

30/08/2008 - 04:11 von Vinzent Hoefler | Report spam
On Fri, 29 Aug 2008 14:39:25 +0200, Rainer Weikusat wrote:


Vinzent Hoefler <nntp-2008-08@t-domaingrabbing.de> writes:

On Fri, 29 Aug 2008 11:27:51 +0200, Rainer Weikusat wrote:

erfordern. Es gibt zum Beispiel ueberhaupt keinen Grund, jemanden von
'Pointerarithmetik' fernzuhalten, denn diese vereinfacht bestimmte
Dinge (zB lexikalische Analyse/ Parsing oder auch nur
Array-Traversals) erheblich. Ein Beispiel dafuer waere:

static int const some_values[] = {
3,
2,
1,
45,
-1
};

int const *pv;

pv = some_values;
while (*pv != -1) {
do_something(*pv);
++pv;
}



Das ist für einen Anfànger einfacher als beispielsweise:

|for i := Low (some_values) to High (some_values) do
| do_something (some_values[i]);

?



Ohne 'den Anfaenger', wer auch immer dass sein mag, telepathisch zu
ueberwachen, wird man das nicht ermitteln koennen.



Nunja. Ich spreche zumindest von den Erfahrungen, die ich gemacht habe.


Der Code ist
allerdings einfacher, denn er iteriert nicht ueber einen
Zahlenbereich, um in Abhaengigkeit von einer Berechung mit der momentanen
Zahl und einem Startwert 'etwas anderes' zu tun.



Du sprichst für mich in Ràtseln. Ich glaube, ich muss mich erst einmal
wieder an Deinen Stil gewöhnen. Wenn Du also bitte die Liebenswürdigkeit
besàssest, mir die Zielführung Deines Satzes noch einmal genauer
darzulegen?


C is unusual in that it allows pointers to point to anything.



Was'n das für'n Blödsinn?

Auch in anderen Sprachen, die das Konzept "Pointer" haben, dürfen die
im allgemeinen auf alles zeigen. Wenn auch nicht immer auf jedes von
diesem "anything" (Stichwort: Ada'Unchecked_Access). Notfalls muss man
halt - genauso wie in C - mit Type-Casts nachhelfen.


Pointers have a bad reputation in academia, because they are
considered too dangerous, dirty somehow. But I think they are
powerful notation, which means they can help us express
ourselves clearly.



Natürlich sind Zeiger machtvoll. Aber sie können eben - wie jede Macht -
auch leicht missbraucht werden (besonders auch in C). Inwiefern man sich
dann damit "klar" ausdrücken kann, ist im Zweifelsfall kontextabhàngig.

Besonders im Raum steht dann die Frage, ob man einem Programmier-Anfànger
genau diese Macht schon in die Hand geben möchte. Es ist aber nun mal in
C so, dass man ohne Zeiger praktisch keine sinnvollen Programme schreiben
kann.

Nehmen wir mal, so als Beispiel

|char *strcat(char *dest, const char *src);

Was sagt uns das? Eine Funktion, die zwei Zeiger erwartet. Die Daten
des zweiten Teils werden offenbar nicht geàndert (also ist die
Wahrscheinlichkeit, dass er auch NULL sein darf, eher gering, ganz
auszuschliessen ist es allerdings nicht).
Aber was wissen wir über den ersten Parameter, ausser dass es ein Zeiger
auf einen "char" ist?

Ignorieren wir mal den Rückgabewert und kreieren einen annàhernd
àquivalenten Prototypen in einem halbwegs modernen Pascal-Dialekt:

|procedure strcat (var Dest : String; const Source : String)

Diese Version kommt nun erst einmal völlig ohne (offensichtliche) Zeiger
aus und enthàlt zusàtzlich noch weitere Informationen:

(1) "Dest sollte ein String sein." (Laut C-Prototyp könnte auch ein
einzelnes Zeichen gemeint sein, dass es in Wirklichkeit ein
nullterminierter C-String sein sollte, ist nicht erkennbar.)
(2) "Keines der beiden Argumente kann NIL/NULL sein."
(3) Das erste Argument sollte einen bereits initialisierten Wert
beinhalten (sonst hàtte man als Parametermodus besser "out" angegeben.)

Mit diesen Informationen kann man schon einiges mehr anfangen und eine
Fehlbenutzung ist aus sprachlicher Sicht faktisch ausgeschlossen.

[Abgesehen davon erscheint mir "Dest := Dest + 'foo';" in diesem
Zusammenhang durchaus einfacher verstàndlich als 'strcat(s, "foo")', aber
dass das Stringhandling in C nicht gerade zu den einfachsten Dingen
gehört, dürfte allgemein akzeptiert sein, also ignorieren wir das einfach.]

Insofern: Ja, Zeiger sind (besonders in C) wichtig, aber sie werden
allgemein überschàtzt.

Und gerade Anfànger sind von der Zeigerlastigkeit von C oft überfordert.

Apropos, da fàllt mir gerade ein tolles Beispiel aus "Produktivcode" ein
(auf das relevante gekürzt):

|int *pnYear = 0;
|int *pnMonth = 0;
|int *pnDay = 0;
|
|[...]
|GetFileDate (szFileName, pnYear, pnMonth, pnDay);
|
|timestamp = *pnYear * 10000 + *pnMonth * 100 + *pnDay;

Dass das ganze auf:

"(int *) NULL * 10000 + (int *) NULL * 100 + (int *) NULL;"

verkürzbar ist, ist einige Jahre lang niemandem aufgefallen: "Compiliert
doch?" Wenn man bedenkt, dass die Software, aus dem dieses Stück Code
stammt, gute 200'000 qualitativ vergleichbare SLOC umfasst, tendenziell
sicherheitsrelevant ist (mit 200 Watt Laserleistung kann man wirklich gut
Löcher brennen) und den Fakt dazu rechnet, dass das u.a. von sich selbst so
bezeichnenden langjàhrigen "C-Programmierern" stammt, die eigentlich
einigermassen wissen sollten, was sie tun, muss man sich doch fragen, ob
das eine für Anfànger gut geeignete Sprache ist. Anfànger wissen nàmlich
eher selten, was sie da tun. Ja, ich spreche da auch aus Erfahrung.


Consider: When you have a pointer to an object, it is a name for
exactly that object and no other. That sounds trivial, but look at the
following two expressions:

np
node[i]

The first points to a node, the second evaluates to (say) the same
node.



Komisch ist nur, dass man in C den Unterschied zwischen dem Objekt "test"
und 't' mittels char* überhaupt nicht ausdrücken kann. Ein und derselbe
Zeiger kann also durchaus auf unterschiedliche Inkarnationen àhnlicher
Objekte zeigen. Ich bin also geneigt, die Aussage "ein Zeiger ist ein Name
für /genau ein/ Objekt" für schlicht unzutreffend zu halten.


But the second form is an expression; it is not so simple. To
interpret it, we must know what node is, what i is, and that i and node
are related by the (probably unspecified) rules of the surrounding
program.



Um "np" korrekt anzuwenden, braucht man auch ohne "i" etwa genau so viel
Informationen. Es stellt sich zusàtzlich die Frage, ob ein etwaiger
Ausdruck "np++" überhaupt gültig sein kann, was sich bei node[i]
in den meisten anderen Sprachen wesentlich leichter beantworten làsst, es
führt im Falle der Ungültigkeit nàmlich zu einem nicht übersetzbaren
Programm. Jetzt kann man einwenden (tut der Autor ja unten auch), dass ja
nicht bekannt ist, ob der Wert des Index "i" nun zur Laufzeit tatsàchlich
einen gültigen Index beschreibt. Allerdings ist das für "np" in den
meisten Fàllen auch nur àhnlich nicht-trivial beantwortbar.


Nothing about the expression in isolation can show that i is
a valid index of node, let alone the index of the element we want.



Nichts an dem Zeiger "np" allein deutet darauf hin, dass er auf ein
gültiges Objekt zeigt und selbst wenn, wissen wir nicht, ob es das Objekt
ist, was wir wollen.

Was muss man rauchen, um so eine Argumentation aufbauen zu können? Ich
will auch was von dem Zeug, scheint witzig zu sein.



Mit dem zusàtzlichen Effekt, dass man das Äquivalent von "do_something
(some_values)" vom Compiler gründlichst um die Ohren gehauen kriegt,
statt dem Effekt, dass aus dem Zeiger automatisch auf "int" gecastet
wird?



Wenigstens fuer C99 sollte er das nicht werden (6.5.4|3 + 6.5.16.1|1).



Mag sein. Und es geht zumindest bei meinem gcc auch so nicht warnungsfrei
ab.


Aber "Sinn oder Unsinn strikter Typ-Pruefungen" ist ein Thema, zu dem
keine einheitliche Sichtweise existiert: Wer sie gewohnt ist, haelt sie
fuer unverzichtbar oder doch wenigstens nuetzlich,



Der Vorteil ist, dass ich sie in den Fàllen, wo sie mir hinderlich ist,
explizit "wegcasten" kann. Der Nachteil bei weniger strikter Prüfung liegt
darin, dass eine Menge möglicher Fehler eben nicht schon vom
Compiler erkannt wird.

Davon abgesehen, sind vom Programmierer eingefügte Type-Casts in C
tendenziell hàufiger zu finden als in typstrengeren Sprachen. Klingt zwar
widersprüchlich, ist aber so. Wirklich erklàren kann ich's auch nicht.


wer eher Sprachen mit
dynamischer Typisierung oder vollkommen ohne welche benutzt, fuer
Quatsch, mit dem man sich nicht befassen sollte, "weil er mit dem
Problem nichts zu tun hat".



Dynamische Typisierung hat mit strikter oder weniger strikter
Typprüfung erst mal nix zu tun. Der Unterschied liegt nur darin, wann die
Typfehler auftreten (sehr beliebter Laufzeitfehler: "foo() expects two
parameters, only one given").


Pragmatisch gesehen muss man mit und ohne
arbeiten koennen, und falls bei diesem wechselseitigen Koepfeeinschlagen
mal etwas definitives herauskommen sollte, wird sich das wohl
herumsprechen.



Offenbar nicht. Zumal der Streit, was nun schwache oder strenge
Typisierung ist, noch nicht wirklich entschieden ist. ;)



Abgesehen davon ist In-Band-Signalling einfach nur böse, auch wenn es
in C oft eine technische Notwendigkeit ist.



'Boese' ist eine moralische Kategorie.



Korrekt. Ich kann technische Notwendigkeit nun mal nicht mit technischen
Argumenten kritisieren. Dass etwas nicht anders geht, bedeutet allerdings
auch nicht, dass es eine gute (Vorsicht: moralische Kategorie!) Lösung ist.


Was genau 'in-band signalling' in
einem Computerprogramm eigentlich sein soll, waere zu definieren.



Triviales Beispiel für C: Das abschliessende '\0' eines C-"Strings".


Haette man das getan, koennte man als nachteilig angesehene Nebeneffekte
'festnageln' und fuer konkrete Beispiele nachweisen, dass diese Effekte
sowohl auftreten als auch nachteilig sind.



Buffer-Overflows und die klassischen "Off-By-One"-Fehler, sind sowohl ein
bekanntes als auch ein üblicherweise als nachteilig angesehenes Problem.


Aber diese muessten
eigentlich auch nachteilig sein, ohne zu ihrer Beschreibung Begriffe aus
der Nachrichtentechnik zu entlehnen.



Wenn Dir ein besserer Begriff dafür einfàllt...


Ein Beispiel einer 'naiven' Definition waere zB die Benutzung derselben
TCP-Verbindung fuer Kommandos und Datenuebertragung und die
Schlussfolgerung waere dann, dass 'HTTP' 'boese' sei,



Inwiefern würde In-Band-Signalling auf HTTP zutreffen? Die Richtung der
Datenübertragung ist festgelegt und damit auch "Kommando-" und "Datenkanal".


Vinzent.

Supersede: Man sollte nachts um 4 keine Postings mehr schreiben, vor
allem, wenn man vergisst, dass man das, was man gerade schreibt, schon mal
geschrieben hat. :D
 

Lesen sie die antworten

#1 Rainer Weikusat
31/08/2008 - 16:40 | Warnen spam
Vinzent Hoefler writes:
On Fri, 29 Aug 2008 14:39:25 +0200, Rainer Weikusat wrote:



[...]

static int const some_values[] = {
3,
2,
1,
45,
-1
};

int const *pv;

pv = some_values;
while (*pv != -1) {
do_something(*pv);
++pv;
}









[...]

|for i := Low (some_values) to High (some_values) do
| do_something (some_values[i]);







[...]

Der Code ist allerdings einfacher, denn er iteriert nicht ueber einen
Zahlenbereich, um in Abhaengigkeit von einer Berechung mit der momentanen
Zahl und einem Startwert 'etwas anderes' zu tun.



Du sprichst für mich in Ràtseln.



Das ist eine ziemlich exakte Uebersetzung von Teilen der oa Schleife
ins Deutsche. Dadurch wird der Vorgang an sich aber nicht einfacher:
Der Computer zaehlt einen bestimmten Zahlenbereich durch und die
enthaltenen Zahlen werden im Schleifenkoerper fuer eine Berechnung
verwendet, aus der sich nach dem Muster basis + (index * elementgroesse)
letztlich die Adresse eines Feldelementes ergibt, auf das indirekt
zugeriffen werden soll. Man koennte das eine Doppelindirektion
nennen, waehrend sie im anderen Fall nur einfach ist: Die explizite
Angabe der Distanz zum Feldanfang wird nicht benoetigt, ebensowenig
wie 'der Feldanfang' (oder meinetwegen 'das Feld') selber.

Das ist die Erklaerung. Die Bemerkung waere, dass in der zweiten
Schleife zwei Variablen vorkommen, in der ersten nur eine.

C is unusual in that it allows pointers to point to anything.



Was'n das für'n Blödsinn?

Auch in anderen Sprachen, die das Konzept "Pointer" haben, dürfen die
im allgemeinen auf alles zeigen.



Standard-Pascal kennt zB keinen 'Adresse-von'-Operator. In einer
ganzen Reihe anderer Sprachen gibt es ueberhaupt keine Zeiger, sondern
bloss Referenzen auf Objekte. Ein C-Zeiger steht fuer einen Ort an
dem sich ein Objekt befindet, falls der Zeiger gueltig ist. Dahinter
befindet sich vielleicht auch noch ein Objekt und wenn dem so ist,
kann man den Zeiger inkrementieren, um auf dieses
zuzugreifen. Vielleicht gibt es auch davor noch eins usf. Was bei
Benutzung eines ungueltigen Zeigers passiert, ist bloss nicht
definiert. Dh es ist auch nicht als Fehler definiert.

[...]

Pointers have a bad reputation in academia, because they are
considered too dangerous, dirty somehow. But I think they are
powerful notation,





[...]

Natürlich sind Zeiger machtvoll. Aber sie können eben - wie jede Macht -
auch leicht missbraucht werden (besonders auch in C)



[...]

Es ist aber nun mal in C so, dass man ohne Zeiger praktisch keine
sinnvollen Programme schreiben kann.

Nehmen wir mal, so als Beispiel

|char *strcat(char *dest, const char *src);

Was sagt uns das? Eine Funktion, die zwei Zeiger erwartet. Die Daten
des zweiten Teils werden offenbar nicht geàndert (also ist die
Wahrscheinlichkeit, dass er auch NULL sein darf, eher gering, ganz
auszuschliessen ist es allerdings nicht).
Aber was wissen wir über den ersten Parameter, ausser dass es ein Zeiger
auf einen "char" ist?

Ignorieren wir mal den Rückgabewert und kreieren einen annàhernd
àquivalenten Prototypen in einem halbwegs modernen Pascal-Dialekt:

|procedure strcat (var Dest : String; const Source : String)

Diese Version kommt nun erst einmal völlig ohne (offensichtliche) Zeiger
aus und enthàlt zusàtzlich noch weitere Informationen:

(1) "Dest sollte ein String sein." (Laut C-Prototyp könnte auch ein
einzelnes Zeichen gemeint sein, dass es in Wirklichkeit ein
nullterminierter C-String sein sollte, ist nicht erkennbar.)
(2) "Keines der beiden Argumente kann NIL/NULL sein."
(3) Das erste Argument sollte einen bereits initialisierten Wert
beinhalten (sonst hàtte man als Parametermodus besser "out"
angegeben.)



Aus sich heraus sagt das beides gar nichts[*]. Dafuer braucht es
Referenzdokumentation zum Nachschlagen.

[*] "Der Verfasser der ersten Zeile mochte Sternchen und
bevorzugte einen simplen, 'unbuerokratischen' Stil. Der der
zweiten hatte die Gewohnheit, explizit und formell zu
kategorisieren"? Mit viel Phantasie koennte man noch an Luther
und Calvin denken, obwohl Herr Wirt wohl noch niemanden wegen
hartnaeckigem festhalten einer anderen Meinung oeffentlich
hat verbrennen lassen :->. Hoffe ich wenigstens.

[...]

Apropos, da fàllt mir gerade ein tolles Beispiel aus "Produktivcode" ein
(auf das relevante gekürzt):

|int *pnYear = 0;
|int *pnMonth = 0;
|int *pnDay = 0;
|
|[...]
|GetFileDate (szFileName, pnYear, pnMonth, pnDay);
|
|timestamp = *pnYear * 10000 + *pnMonth * 100 + *pnDay;

Dass das ganze auf:

"(int *) NULL * 10000 + (int *) NULL * 100 + (int *) NULL;"

verkürzbar ist, ist einige Jahre lang niemandem aufgefallen: "Compiliert
doch?" Wenn man bedenkt, dass die Software, aus dem dieses Stück Code
stammt, gute 200'000 qualitativ vergleichbare SLOC umfasst, tendenziell
sicherheitsrelevant ist (mit 200 Watt Laserleistung kann man wirklich gut
Löcher brennen) und den Fakt dazu rechnet, dass das u.a. von sich selbst so
bezeichnenden langjàhrigen "C-Programmierern" stammt, die eigentlich
einigermassen wissen sollten, was sie tun, muss man sich doch fragen, ob
das eine für Anfànger gut geeignete Sprache ist. Anfànger wissen nàmlich
eher selten, was sie da tun. Ja, ich spreche da auch aus Erfahrung.



Die 'Erfahrung', das es wenigstens eine Person gibt, die eine Menge
schlechten C-Code geschrieben hat, ist aber nicht verallgemeinerbar: C
ist eben eine relativ einfache Sprache und deswegen kann sie von sehr
vielen Leuten in einem wenigstens nutzbaren Umfang erlernt werden.

Consider: When you have a pointer to an object, it is a name for
exactly that object and no other. That sounds trivial, but look at the
following two expressions:

np
node[i]

The first points to a node, the second evaluates to (say) the same
node.



Komisch ist nur, dass man in C den Unterschied zwischen dem Objekt "test"
und 't' mittels char* überhaupt nicht ausdrücken kann.



Ein char * zeigt auf ein Objekt vom Typ 'char'. "test" waere ein
String-Literal, dass man zB zur Initialisierung eines char-Feldes
benutzen koennte, auf dessen Elemente ein char * zeigen kann. Ein
Zeiger auf ein char-Feld haette einen anderen Typ:

#include <stdio.h>

char a[][4] = {
"123",
"456",
"789"
};

char (*p)[4] = a;

int main(void)
{
do
puts(*p);
while (++p < a + sizeof(a)/sizeof(*a));

return 0;
}

Ein und derselbe Zeiger kann also durchaus auf unterschiedliche
Inkarnationen àhnlicher Objekte zeigen. Ich bin also geneigt, die
Aussage "ein Zeiger ist ein Name für /genau ein/ Objekt" für
schlicht unzutreffend zu halten.



Sehr witzig. "Eine Integer-Variable kann durchaus unterschiedliche
Werte haben, ich bin also geneigt, die Aussage, sie haben /genau
einen/ Wert schlicht fuer unzutreffend zu halten." Ich bin uebrigens
auch geneigt, die Aussage, das die Wohnung neben meiner
Einpersonenappartment sei, fuer falsch zu halten, denn es gab schon
wenigstens drei unterschiedliche Mieter ...

But the second form is an expression; it is not so simple. To
interpret it, we must know what node is, what i is, and that i and node
are related by the (probably unspecified) rules of the surrounding
program.



Um "np" korrekt anzuwenden, braucht man auch ohne "i" etwa genau so viel
Informationen. Es stellt sich zusàtzlich die Frage, ob ein etwaiger
Ausdruck "np++" überhaupt gültig sein kann, was sich bei node[i]
in den meisten anderen Sprachen wesentlich leichter beantworten làsst, es
führt im Falle der Ungültigkeit nàmlich zu einem nicht übersetzbaren
Programm.



Korrekterweise muesste man ++np (np++ passt hier nicht zu ganz) mit
node[++i] vergleichen. Das sind wieder zwei (potentiell) gleichwertige
Ausdruecken, von denen einer zwei zueinander in Beziehung stehende
Variablen enthaelt und der andere nur eine.

Jetzt kann man einwenden (tut der Autor ja unten auch), dass ja
nicht bekannt ist, ob der Wert des Index "i" nun zur Laufzeit tatsàchlich
einen gültigen Index beschreibt.



Ich denke, er weist eher darauf hin, dass es zwischen i und node nur
die Beziehung gibt, in die der Code sie stellt.


[...]

Aber "Sinn oder Unsinn strikter Typ-Pruefungen" ist ein Thema, zu dem
keine einheitliche Sichtweise existiert: Wer sie gewohnt ist, haelt sie
fuer unverzichtbar oder doch wenigstens nuetzlich,



Der Vorteil ist, dass ich sie in den Fàllen, wo sie mir hinderlich ist,
explizit "wegcasten" kann. Der Nachteil bei weniger strikter Prüfung liegt
darin, dass eine Menge möglicher Fehler eben nicht schon vom
Compiler erkannt wird.

Davon abgesehen, sind vom Programmierer eingefügte Type-Casts in C
tendenziell hàufiger zu finden als in typstrengeren Sprachen. Klingt zwar
widersprüchlich, ist aber so. Wirklich erklàren kann ich's auch
nicht.



Das koennte daran liegen, dass die meisten casts in existierenden
C-Programmen sowohl technisch als auch sprachlich ueberfluessig sind
:->.

wer eher Sprachen mit dynamischer Typisierung oder vollkommen ohne
welche benutzt, fuer Quatsch, mit dem man sich nicht befassen
sollte, "weil er mit dem Problem nichts zu tun hat".



Dynamische Typisierung hat mit strikter oder weniger strikter
Typprüfung erst mal nix zu tun.



'Dynamische Typisierung' bedeutet 'keine strikte Typ-Pruefung durch
einen Compiler'.

[...]

Abgesehen davon ist In-Band-Signalling einfach nur böse, auch wenn es
in C oft eine technische Notwendigkeit ist.



'Boese' ist eine moralische Kategorie.



Korrekt. Ich kann technische Notwendigkeit nun mal nicht mit technischen
Argumenten kritisieren. Dass etwas nicht anders geht, bedeutet allerdings
auch nicht, dass es eine gute (Vorsicht: moralische Kategorie!)
Lösung ist.



Der Punkt war (oder sollte sein), dass 'dieser Code ist boese' fuer
sich nichts bedeutet.

Was genau 'in-band signalling' in einem Computerprogramm eigentlich
sein soll, waere zu definieren.



Triviales Beispiel für C: Das abschliessende '\0' eines C-"Strings".



Das ist keine Definition. 'in-band signalling' bedeutet 'Nutzung
desselben Uebertragungskanals sowohl fuer Nutzdaten als auch (von
diesen unabhaengige) Steuernachrichten'. Das laesst sich auf 'Folge
von Zahlen mit einem Wert != 0, gefolgt von einer 0' nicht sinnvoll
uebertragen.

[...]

Aber diese muessten eigentlich auch nachteilig sein, ohne zu ihrer
Beschreibung Begriffe aus der Nachrichtentechnik zu entlehnen.



Wenn Dir ein besserer Begriff dafür einfàllt...



Wofuer?

Ein Beispiel einer 'naiven' Definition waere zB die Benutzung derselben
TCP-Verbindung fuer Kommandos und Datenuebertragung und die
Schlussfolgerung waere dann, dass 'HTTP' 'boese' sei,



Inwiefern würde In-Band-Signalling auf HTTP zutreffen? Die Richtung der
Datenübertragung ist festgelegt und damit auch "Kommando-" und
"Datenkanal".



Es gibt nur einen Kanal, der sowohl Nutzdaten als auch
Steuerinformationen uebertraegt.

Ähnliche fragen