PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] explizite Typumwandlung



Siro
06.10.2011, 09:03
Guten Morgen,
ich habe eine, oder sind es mehrere Fragen zur Typumwandlung. Zunächst ganz einfach:


Warum funktioniert folgender Code nicht wie erhofft ?

int* p = (char*)1000; /* p zeigt nun auf die Speicherstelle 1000 */
(char*)p++;

p zeigt nun auf 1004 leider nicht wie erwartet auf 1001. Das "Casten" wurde anscheinend völlig ignoriert ?

und das Folgende meckert der Compiler komplett an:
void* p = (void*)1000;

(char*)p++; /* expression must be a pointer to a complete object type */


Erklärung was ich machen möchte:
Eine universelle Funktion schreiben, der ich lediglich eine Adresse und eine Anzahl übergebe um Daten mittels RS232 zu übertragen.
VOID möchte ich benutzen, damit ich später keine explizite Typwandlung mehr benötige. Also einen Pointer auf irgendwas übergeben kann.
So soll es aussehen:

void uart_putc(char c)
{
/* ....... */
}

void uart_send(void* data, unsigned int size)
{
while(size--)
uart_putc(*(char*)data++); /* hier gibt es das Problem */
}


Ich war ja schon fleißig und habe natürlich schon einige Versionen ausprobiert und hinbekommen
So geht es zum Beispiel:

void uart_send(void* data, unsigned int size)
{ char*p=data;

while(size--)
uart_putc(*p++);
}

aber wozu die unnötige, lokale Zwischenvariable "p" , ich hab doch schon meinen Pointer "data" den muss ich doch Byteweise hochzählen können.

Ich bedanke mich schon im voraus für (meinen neuen Lerneffekt) bzw. Eure Hilfe.
Siro

PicNick
06.10.2011, 09:28
Versuch mal
( (char*)p ) ++;
ich hab's aber nicht probiert, also ohne gewähr

Gedanke dahinter:
(char*)p++ heisst ja, er soll das, was bei p++ rauskommt, als char* interpretieren.

Ich mach das meist so:
p = (int*) ((int)p + 1);
geht aber sicher eleganter, ich bin aber nicht an Forschung interessiert :-( , hauptsache, es funzt

Ceos
06.10.2011, 12:12
also wie PickNick und auch ich das sehen, ist das nur logisch, es wird ERST p++ ausgehführt und DANACH der cast auf char-pointer!
PickNicks Lösung musst du verwenden, dann sollte es gehen!

Der 2te fehler ist genau das gleiche Problem, er kann einen void-pointer nicht inkrementieren, weil der Cast erst NACH der Operation durchgeführt werden würde!

@PickNick deine 2te Lösung sieht gruselig aus, du castest die Adresse in einen Zahlenwert um, manipulierst ihn udn dann wieder zu einem Pointer ... (da schüttelt es mich XD)

PS: ich würde statt einem char* lieber einen byte* oder einen uint8* verwenden ... ist jetzt nur ne stilsache, aber char kann auch als unicode interpretiert werden und der hat 16bit!

muss mich selber zusammenreissen nicht immer char zu schreiben





void uart_send(void* data, unsigned int size) {
while(size--)
uart_putc(*(((byte*)data)++)); /* erst casten, dann inkrementen, dann dereferenzieren */
}

void uart_send(void* data, unsigned int size, unsigned int repeat = 1 ) { //keine Ahnung ob der WinAVR optionale Parameter unterstützt aber macht es noch multifunktionaler!
while(repeat--)
{
for(unsigned int i = 0; i < size; i++)
uart_putc(*(((byte*)data)++)); /* erst casten, dann inkrementen, dann dereferenzieren */
}
}



So viel Klammer muss halt sein wenn man sicher gehen will dass der compiler es auch versteht!

PicNick
06.10.2011, 12:34
Wegen Grusel und Graus : Das rumcasten erzeugt ja keinen Code, sondern zwingt den Compiler zu anderen MaschinenInstruktionen. Insofern bleibt nur die schaurige Optik in der Source, da geb' ich dir schon recht.

Mir persönlich graust mehr, wenn einer elegant die gauss'sche Osterberechnung in ein Statement quetscht

sternst
06.10.2011, 12:56
PS: ich würde statt einem char* lieber einen byte* oder einen uint8* verwenden ... ist jetzt nur ne stilsache, aber char kann auch als unicode interpretiert werden und der hat 16bit!char ist laut Definition der kleinste Typ in C (sizeof(char) ist immer 1). Wenn ein char 16 Bit hat (was durchaus sein kann), dann gibt es in der Umgebung ein uint8_t schlicht nicht. Und auch byte (was immer das konkret sein mag) kann dann nicht kleiner als 16 Bit sein.

Siro
06.10.2011, 16:48
Ein bischen weiter geholfen habt Ihr mir ja, aber Eure Lösungen scheinen nicht so zu funktionieren wie ich es wollte.

Zu PicNick:

p = (int*) ((int)p + 1);

erhöht bei mir den Zeiger um 4, hab ich grad mal ausprobiert.


Zu Ceos:

uart_putc(*(((byte*)data)++));

gibt bei mir einen Fehler: expression must be a modifiable lvalue


Zumindest hab ich jetzt verstanden, daß der Compiler anscheinend von rechts nach links auswertet.
Dann leuchtet mir ein, daß mein Lösungsweg auch nicht funktionieren konnte.


Lösung steht also noch aus......Danke Euch schonmal für die Anteilnahme

Siro

sternst
06.10.2011, 17:40
Was genau stört dich eigentlich an der Variante mit der lokalen Variable?
Das ist eine saubere und vor allem auch gut lesbare Lösung. Und weniger effizient wird sie auch nicht sein. Würde mich wundern, wenn die zusätzliche Variable auch im Binärcode als solche auftauchen würde.

Felix G
06.10.2011, 18:57
Ähm...
wieso tut ihr euch das überhaupt an!?

Pointerarithmetik ist ja schön und gut, aber in 99% aller Fälle geht es auch sehr viel bequemer:


void uart_send(void* data, uint16_t size)
{
uint16_t n;
uint8_t* data8 = (uint8_t*)data;

for(n=0; n<size; n++)
uart_putc(data8[n]);
}

oder alternativ:

void uart_send(void* data, uint16_t size)
{
uint16_t n;

for(n=0; n<size; n++)
uart_putc(((uint8_t*)data)[n]);
}


Ja, es ist eine Zählvariable dazugekommen, aber dafür ist der Code leichter lesbar und weniger fehleranfällig.
(Außerdem wird n niemals im Speicher existieren, der Compiler verwendet einfach ein Register dafür)

Siro
06.10.2011, 19:02
Hallo Stefan,
meine gefundene, oben gezeigt Lösung ist ja auch "saubere Sache" und auch "sehr gut lesbar" da stimmen wir völlig überein.
So benutze ich es zur Zeit auch.
Aber wenn ich mich schon in eine neue Programmiersprache reinarbeite, möchte ich auch möglichst viele Details kennen lernen.
Deshalb kommen halt solche Fragen auf. Ich möchte immer aufs Bit genau wissen was passiert, das liegt in der jahrelangen
Programmierung in Assembler, da stört mich jede zusätzliche Variable oder Zeile.
Optimiert ein Compiler heutzutage auch lokale Variablen weg ?. Möglich wäre es ja. Aber das wäre sicher ein anderes Thema....

Aber mal generell. Kann man denn überhaupt ein Variable "vorher" casten, die mit einem Postincrement/Postdecrement verarbeitet wird.
Denn wenn ich mir die Kommenatre hier so ansehe, führt der Compiler "zuerst das "increment" durch und dann castet er.
Wie kann ich ihm denn beibringen, dass er "erst casten" soll und dann das Postincrement. Geht das überhaupt ?
Das ist rein zum Verständnis, ich möchte da sicher keine Wissenschaft draus machen.
Mir ist anscheinend (ganz sicher sogar) noch nicht klar, wie der Compiler den Code interpretiert.
Den Kommentaren nach von rechts nach links. Das kann aber so auch nicht sein,
dann würde x=*p++; falsch laufen. Wenn er erst incrementieren würde und dann den Wert zuweist.
Also muss er sich doch erst den Wert holen um ihn x zuzuweisen und dann darf er den pointer incrementieren.
Wie soll man das also casten, daß sichergestellt ist, daß der pointer nur um eins incrementiert wird.

Auch wenns nervt :-) Ich Danke Dir
Siro

Siro
07.10.2011, 08:49
Hallo Felix G,
Oho, das funktioniert schon mal. Hab ich gleich mal ausgetestet.
Und ja, Du hast recht, für "n" wird ein Register verwendet, zumindest bei meinem Compilat.
Das man einen Pointer wie ein Array benutzen kann, war mir noch nicht klar.
Hat sich also mein Beitrag gelohnt. Ich bedanke mich.

Der Vollständigkeit halber nochmal 4 Lösungen:




void uart_send(void* data, unsigned int size)
{ char *p = data;

while (size--)
{
uart_putc(*p++);
}
}

void uart_send(void* data, unsigned int size)
{ int i;

for (i=0; i< size; i++)
{
uart_putc(((char*)data)[i]);
}
}

void uart_send(void* data, unsigned int size)
{
while (size--)
{
uart_putc(*(char*)data);
data = (char*)data+1;
}
}

void uart_send(void* data, unsigned int size)
{
while (size--)
{
uart_putc(*(char*)data);
++(*((char**)(&data)));
}
}


Siro

PicNick
07.10.2011, 09:22
Ich hab das jetzt mit dem VC (microsoft visual studio 2005) durchprobiert. Das sollte eigentlich schon auf GCC übertragbar sein.

Vorweg:


int* p = (char*)1000; // geht garnicht, verständlich, ist ja auch ein widerspruch


Klammervarianten:


int* p = (int*)1000;


(char*)p++; // kennen wir schon, liefert p+4
(int)p++; // ebenfalls
p = (int*)(int)p++; // ebenfalls

((char*)p)++; // Mag er garnicht ==> error C2105: '++' needs l-value




Was geht ? (definitiv getestet)


int tmp = (int)p;
p = (int*)++temp;




union { int* p; int iP; } un;
un.p = (int*)1000;
un.iP++ ; // (durch die union wird auch int*p mit-incrementiert


und die grausliche Variante


p = (int*)((int)p + 1);



Wie gesagt: getestet

danimath
07.10.2011, 10:05
Sooo grauslich finde ich es gar nicht: Das hier (klick) (http://www.de.ioccc.org/years.html) hatte ich schon die ganze Zeit im Hinterkopf ;)

viele Grüße
Andreas

Klebwax
07.10.2011, 20:33
void uart_putc(char c)
{
/* ....... */
}

void uart_send(void* data, unsigned int size)
{
while(size--)
uart_putc(*(char*)data++); /* hier gibt es das Problem */
}


Was mir hier nicht klar ist:

wozu ist eine Funktion gut, die Daten vom Typ "weißnichtsogenau", und das ist void, in eine andere Funktion stopft, die nur mit chars umgehen kann. Sollte data mal auf ints oder floats zeigen, kommt etweder Müll raus, oder es crasht. Und die Verwendung von void-Pointern führt nur dazu, daß einem der Compiler nicht mehr helfen kann, Fehler zu vermeiden.

Es muß also heißen: uart_send(char* data, ... , und in Zukunft für jeden void* 5€ in die Kaffekasse (oder ersatzweise eine schriftliche Begründung von mindestens einer Seite, warum so etwas aus Gründen der Programmlogik wirklich nötig ist )!

MfG Klebwax

Siro
08.10.2011, 14:08
zu PicNick
Sorry, ich muss mich korrigieren, deine Zeile

p = (int*) ((int)p + 1);

funktioniert wirklich. Erhöht den Zeiger um 1 und nicht um 4.
Hast Du völlig recht.


zu Klebwax:

wie soll man denn sonst eine universelle function schreiben ?
ich möchte verschiedene Datentypen, Strukturen und ähnliches
senden. Das Casten übernimmt bei mir die Funktion, ich brauche mich darum
nicht mehr zu kümmern. Ich übergebe nur noch die Adresse und die Anzahl Bytes.

int xx;

struct
char a,b,c,d;
int e,f,g;&lt;
} st;


und zudem finde ich daß:

uart_send(&xx,sizeof(xx));
uart_send(&st,sizeof(st));

besser aussieht als:

uart_send((char*)&xx,sizeof(xx));
uart_send((char*)&st,sizeof(st));

aber das ist sicher Geschmackssache. Funktionieren tut natürlich beides. Was die Sicherheit angeht,
denke ich mal, nehmen sich beide Varianten nichts.
anbei meine 5,-- Euro... ;-)

Habt noch ein schönes Wochenende.

Siro

Klebwax
08.10.2011, 18:09
wie soll man denn sonst eine universelle function schreiben ?
ich möchte verschiedene Datentypen, Strukturen und ähnliches
senden. Das Casten übernimmt bei mir die Funktion, ich brauche mich darum
nicht mehr zu kümmern. Ich übergebe nur noch die Adresse und die Anzahl Bytes.

int xx;

struct
char a,b,c,d;
int e,f,g;<
} st;


und zudem finde ich daß:

uart_send(&xx,sizeof(xx));
uart_send(&st,sizeof(st));

besser aussieht als:

uart_send((char*)&xx,sizeof(xx));
uart_send((char*)&st,sizeof(st));

aber das ist sicher Geschmackssache. Funktionieren tut natürlich beides. Was die Sicherheit angeht,
denke ich mal, nehmen sich beide Varianten nichts.
anbei meine 5,-- Euro... ;-)


Ich frag jetzt mal garnicht, ob du das ernst meinst. Was du willst, geht garnicht. Man kann weder ein Int noch eine Struktur einach so über die serielle Schnittstelle versenden. Dein Compiler hat schon gewußt, warum er deinen Code nicht bearbeiten wollte, und du ihn erst mit void* und casts mundtot machen musstest.

Die 5€ kannst du behalten. Leg noch was drauf und kauf dir ein Buch über C und lerne. Und mach dir mal Gedanken über Strukturen, gepackt und ungepackt, auf verschiedenen CPUs mit 8 bis 64 Bit Wortbreite sowie Big und Little Endian und dann lerne was über robusten portablen Code und gewöhne dir einen solchen Stil an.


MfG Klebwax

Siro
10.10.2011, 09:19
Ich muss hier sicher keine Rechenschaft ablegen, aber zu Klebwax:
ich möchte die Bytes so herausschicken, wie sie im Speicher stehen.
Ob sie gepackt sind oder im Intel oder Motorola Format vorliegen ist in MEINEM Falle völlig unwichtig,
Das ist lediglich ein Hexdump den ich zu Testzwecken herausschicke um SOFTWAREFEHLER AUFZUDECKEN.
Deshalb finde ich deine voreilige Beurteilung unberechtigt negativ, satt kreativ...

Dein Satz:
Was du willst, geht garnicht. Man kann weder ein Int noch eine Struktur einach so über die serielle Schnittstelle versenden.
ist in meinem Falle eine Falschaussage, denn es funktioniert ja einwandfrei.
Siro

PicNick
10.10.2011, 09:50
Offenbar sind noch ein paar Leute ohne einen Schimmer, wie man wirklich professionelle Funktionen schreibt
z.B


void * memcpy ( void * destination, const void * source, size_t num );Das sind schon mal 15 €
http://www.cplusplus.com/reference/clibrary/cstring/memcpy

(http://www.cplusplus.com/reference/clibrary/cstring/memcpyVielleicht)

Klebwax
10.10.2011, 20:35
Offenbar sind noch ein paar Leute ohne einen Schimmer, wie man wirklich professionelle Funktionen schreibt
z.B


void * memcpy ( void * destination, const void * source, size_t num );Das sind schon mal 15 €
http://www.cplusplus.com/reference/clibrary/cstring/memcpy

(http://www.cplusplus.com/reference/clibrary/cstring/memcpyVielleicht)

Warum hast du dann nicht auch gleich das zweite Beispiel von Funktionen, für die soetwas wie ein void-Pointer überhaupt erfunden wurde, genannt: die Gruppe der Memory-Management-Funktionen. Die mehr als einseitige Begründung, wozu man so etwas braucht, findest du in der gängigen C-Literatur. Des gleichen findest du dort aber dort auch die Diskussionen, das eine richtige Programmiersprache Pointer, egal welcher Art, überhaupt nicht zulassen sollte. Offensichtlich haben Leute wie Wirth den Programmierern nicht zugetraut, vernüftig mit dem Instrument Pointer umzugehen.

Um aber bei "professionelle Funktionen" zu bleiben, glaubst du ernsthaft, so etwas wie das beschriebene uart_send() würde einem professionellen Code Review standhalten ? Wie verhält sich die Funktion, bei verschiedenen Compileroptimierungen oder auch verschieden Compilern, wie auf CPUs mit unterschiedlichen Wortbreiten, wie auf einer CPU mit anderen Alignment-Regeln, und wie bei Big oder Little Endian Rechnern?

MfG Klebwax

Klebwax
10.10.2011, 21:30
Zum Thema void pointer hier ein Ausschnitt von learncpp.com: (hervorhebungen von mir)

In general, it is a good idea to avoid using void pointers unless absolutely necessary, as they effectively allow you to avoid type checking. This allows you to inadvertently do things that make no sense, and the compiler won’t complain about it.

.....

However, very occasionally, you may still find a reasonable use for the void pointer. Just make sure there isn’t a better (safer) way to do the same thing using other language mechanisms first!



Statt "Just make sure" hab ich gesagt, schreib eine ausführliche Begründung oder zahl in die Kaffekasse.

MfG Klebwax

Felix G
10.10.2011, 22:21
Hmm, wie wärs mit folgendem Szenario:

Eine Funktion deren Rückgabewert ein Funktionspointer auf sich selbst ist (bzw. ganz allgemein ein Pointer auf eine Funktion des gleichen Typs).
Wenn du das ohne void* schaffst, gehört die Kaffeekasse dir ;-)


Zugegeben, sowas braucht man eher selten, aber ich habe auch noch ein praxisrelevantes Beispiel für dich:

Nehmen wir an du schreibst einen Low-Level Treiber für irgendeine Schnittstelle, sagen wir mal SPI. An dieser Schnittstelle können unterschiedlichste Geräte hängen, die wiederum eigene Treiber benötigen welche auf dem SPI Treiber aufsetzen. Bei solchen Treibern ist es üblich mit Callback-Funktionen zu arbeiten, damit das System nicht bei jeder Übertragung blockiert wird. Und aus eigener Erfahrung kann ich dir garantieren, daß du in einer Callback-Funktion mehr Informationen haben möchtest als nur ein Flag das dir sagt ob die Übertragung erfolgreich war oder nicht. Ein void-Pointer, der vom Low-Level Treiber einfach nur mitgeführt wird, erlaubt es dir beliebige Informationen an einen Job anzuhängen die dir dann in der Callback-Funktion wieder zur Verfügung stehen.

Klebwax
10.10.2011, 22:47
Und aus eigener Erfahrung kann ich dir garantieren, daß du in einer Callback-Funktion mehr Informationen haben möchtest als nur ein Flag das dir sagt ob die Übertragung erfolgreich war oder nicht. Ein void-Pointer, der vom Low-Level Treiber einfach nur mitgeführt wird, erlaubt es dir beliebige Informationen an einen Job anzuhängen die dir dann in der Callback-Funktion wieder zur Verfügung stehen.

Und woher weißt die Callback-Funktion, wie sie die beliebigen Informationen verarbeiten soll? Wie soll der Compiler ein Type-Check machen wenn er den Type nicht kennt? Wie soll das Entwicklungsteam in Bangalore robusten Code liefern, der mit deinem Treiber zuverlässig funktioniert?

In der Praxis hast natürlich auch recht. Für ein Programm, das man selbst schreibt, wartet, und das obsolet ist, sobald man die Firma verlässt, ist das ok. Aber das Wort "professionell", das gefallen ist, passt da nicht. Wie schreibt der CCC über den Bundestrojaner: "Das hat wohl ein Praktikant geschrieben".

Meine Erfahrung sagt mir, eigentlich jeder Pfusch, den ich mal programmiert habe, ist mir wieder auf die Füße gefallen. Und jeder Pfusch, den ich hab durchgehen lassen, hat die Firma auf die eine oder andere Weise Geld gekostet.

MfG Klebwax

Felix G
10.10.2011, 23:05
Und woher weißt die Callback-Funktion, wie sie die beliebigen Informationen verarbeiten soll? Wie soll der Compiler ein Type-Check machen wenn er den Type nicht kennt? Wie soll das Entwicklungsteam in Bangalore robusten Code liefern, der mit deinem Treiber zuverlässig funktioniert?Wie die Informationen zu interpretieren sind weiß per Definition nur die Callback Funktion...

Der Witz an der Sache ist, daß der Low-Level Treiber dem Benutzer (also in diesem Fall dem High-Level Treiber, der von mir aus auch gern in Bangalore geschrieben werden kann) die Möglichkeit gibt, beliebige Zusatzinformationen mitzuführen. Welche Bedeutung diese Informationen haben ist dem Low-Level Treiber dabei völlig egal, die kennt nur der Benutzer.


Ich stimme dir zu wenn du sagst, daß man void* nicht einfach nur verwenden sollte um Compiler-Warnungen zu unterdrücken. Denn das klappt zwar wunderbar, aber dafür wundert man sich dann später woher die ganzen Bugs kommen. Aber ein paar sinnvolle Anwendungen gibt es eben doch, und daher behaupte ich einfach mal, daß ein void* nicht mal ansatzweise so "böse" ist wie etwa ein goto ;-)

Klebwax
10.10.2011, 23:39
Dann liegen wir ja nicht weit auseinander. Obwohl ich die potentiellen Probleme von void-Pointern für gravierender halte, als die von gotos. Nicht umsonst ist z.B. in c++ als Ersatz von malloc und co new eingeführt worden.

Aber mal back to topic: Die Idee, den Speicher, den eine Structur belegt, byteweise zu dumpen, ohne gleichzeitig die Informationen über etwaiges Enlignment oder eine Byteorder mit zu übertragen, bekommt bei mir eine "6 setzen". Wenn ich da falsch liege, sind die Leute die RPC oder XML entwickelt haben Ignoranten. Und das traue ich mir nicht zu, anzunehmen.

MfG Klebwax

Siro
11.10.2011, 21:36
Hallo Du Void Allergiker:

das ist doch völlig unwichtig wie oder was ich übertrage, solange es dokumentiert wird.
Ich kann eh nicht alles in eine Struktur einbauen wie die Daten übertragenen oder codiert sind.

Also braucht der Empfänger sowieso ein Dokumentation, wie oder was ich übertrage, oder in welchem Datenformat.
Und genau damit weis der Empfänger auch wie meine Struktur bzw. Daten aufgebaut sind und kann evtl. dadurch mit meinen
Daten auch was anfangen. Ich kann doch in meine Sendedaten einbauen was ich will. Wichtig ist,
daß der Empfänger weis, wie er es auswerten soll. Aber diese Information bekomme ich doch in meine
Daten oder Struktur eh nicht hinein, sondern nur durch DOKUMENTATION.

Da die ganze Diskusion ja über den Void Pointer ausgelöst wurde:
Natürlich kann ich den Void Pointer ersetzten durch z.B struct*....
aber wenn der Empfänger meine Struktur nicht kennt ist auch der Datentyp für die Katz.
da nützt auch kein "Casting" mehr etwas. Ob vorher, nachher, oder wann auch immer.
Ohne spezielle Informationen (Dokumentation) sind die Daten nutzlos.
Und ob nun die Daten Little Endian oder Big Endian oder packed oder was auch immer codiert sind,
kann ich doch meinem Datenblock eh nicht entnehmen, bzw. könnte ich schon,
wenn der Empfänger weis wie er meine Beschreibung z.B. den Datenblock zu interpretieren hat.
Deshalb:
Man sendet Daten, auch gern mit einem Void Pointer, (weil man sendet ja nur Byte für Byte)
aber ohne zusätzliche Doku sind die Daten eh "Müll" Da sind wir wohl alle einer Meinung.
Was nützt mir denn der Integer Pointer, der auf 8,16,32, oder vielleicht auf 64 Bit zeigt,
wichtig für den Empfänger ist doch, wie sind die Daten codiert. Also kann ich getrost mit einem Void Pointer senden,
ich muss eh das Format preisgeben (Dokumentieren) , sonst sind die Daten unauswertbar.
Kurz gesagt: ohne Dokumentation läuft garnichts.
Und der Void Pointer hat damit seine Berechtigung...Er zeigt irgenwo hin und ich möchte auch irgendwelche Daten senden.

Achja, ich möchte aber extra betonen, solange es die Möglichkeit gibt, den Code so zu implementieren, daß man im Vorfeld Fehler vermeiden oder aufspüren kann,
sollte jeder bestraft werden, der dieses Vorgehen vernachlässigt.

Zum Thema Goto: der "verhasste" Goto hat teilweise derart gute Dienste geleistet (ich meine damit der Übersichtlichkeit der Software beigetragen) wenn auch nur im Einzelfall,
daß ich ihn absolut nicht als böse bezeichnen würde. Wenn jemand aber damit durch die Gegend springt ist es echt fatal.

eine gute Nacht wünscht euch,
Siro