Möchte gerade noch mal das Beispiel aus dem anderen Thread aufgreifen.
Beitrag "Re: Was ist die Ausgabe?"
Darin wird unter 2) behauptet
" Selbst wenn man es syntaktisch zu einem C-Programm macht,
ist die 1. Zeile laut C-Standard undefiniert. "
bezogen auf
> char* str = "abc";
Was bitte soll daran "undefiniert" sein?
Laut meiner Kenntnis ist das eine übliche Definition für einen Zeiger
auf eine konstante Zeichenkette.
Undefiniert ist nur, wenn man versucht nachträglich den Inhalt der
konstanten Zeichenkette zu ändern. Das ergibt beispielsweise unter
PellesC eine Exeption.
Gegenteilige Meinungen? Bestätigungen?
Andreas (Gast) schrieb:
> Hallo,> an der definition ist nichts konstant, natürlich kann man den String> ändern.> Andreas
Du meinst was ich schrieb ist falsch?
Skeptiker schrieb:>> char* str = "abc";>> Was bitte soll daran "undefiniert" sein?
Die Zeile ist deshalb Quark (um es etwas volkstümlicher auszudrücken),
weil man einen Zeiger vom Typ char * hat, über den man ein oder mehrere
Zeichen ändern könnte, aber dieser Zeiger auf Speicher zeigt, der const
ist (ein Stringliteral).
Solange man nicht versucht, den String über den Zeiger zu ändern, wird
das nicht auffallen.
Aber wenn du z.B. später schreibst: *str=x dann versuchst du, das 'a'
durch ein 'x' zu ersetzen.
Was wird passieren? Kommt ganz drauf an...
Je nach System und Compiler und Vollmondstand kann es
- klappen, das a wird überschrieben
- das Programm stürzt ab (wenn der String wirklich zur Laufzeit in einem
schreibgeschützten Speicher steht
- die ISS stürzt ab
- ...
Skeptiker schrieb:> Laut meiner Kenntnis ist das eine übliche Definition für einen Zeiger> auf eine konstante Zeichenkette.
"üblich" heißt nicht richtig.
Unter C-Programmierern, die wisse was sie tun, ist es üblich, so etwas
zu schreiben:
Verwirrter schrieb:> Kannst Du denn mal erklären was char* bedeuten soll, oder bin ich der> einzige der das nicht kennt?
Das kann jedes C-Buch.
Es ist ein Datentyp, und zwar "Zeiger auf char".
Nebenbei: nicht "Zeiger auf const char"...
Verwirrter schrieb:> Kannst Du denn mal erklären was char* bedeuten soll, oder bin ich> der> einzige der das nicht kennt?
Ein Zeiger auf ein char, der * bedeutet immer "Zeiger".
Klaus Wachtler schrieb:> Nebenbei: nicht "Zeiger auf const char"...
Das kann vom Compiler nicht autom. zu "const" gemacht werden, muss aber
nicht, deswegen ist es wohl nicht definiert was passiert wenn man
versucht das zu String literal zu aendern.
Klaus Wachtler (mfgkw) schrieb:
> Solange man nicht versucht, den String über den Zeiger zu ändern, wird> das nicht auffallen.> Aber wenn du z.B. später schreibst: *str=x dann versuchst du, das 'a'> durch ein 'x' zu ersetzen.
Ich verstehe diese Argumentation nicht. Das man mit etwas richtigem
etwas falsches anstellen kann ist doch usus in vielen
Programmiersprachen. Ich kann auch auf irgend ein anderes (legales)
Konstrukt falsch zufreifen und erhalte im günstigsten Fall eine
aussagekräftige Fehlermeldung und in etlichen anderen Fällen
undefinierte Zustände (event. eine Exeption). Wenn ich beispielsweise
(in C) über die Feldgrenze eines Array hinaus Werte verändere begehe ich
auch heftige Fehler (mit unkalkulierbaren Seiteneffekten). Deshalb ist
doch aber das Array nicht daran schuld (eher die Freiheit des C
Dialekts).
Verwirrter schrieb:> Ja sicher aber doch wohl kaum so:>>> char* str = "abc";>> wenn dann ja wohl so:>> char *str = "abc";
Iss egal in C, beides legal und gleichwertig ;)
Klaus Wachtler (mfgkw) schrieb:
> Unter C-Programmierern, die wisse was sie tun, ist es üblich, so etwas> zu schreiben:> const char *str1 = "abc";
Da stimme ich dir zu.
> Es ist ein Datentyp, und zwar "Zeiger auf char".> Nebenbei: nicht "Zeiger auf const char"...
Stimmt auch, aber dennoch zeigt der Zeiger auf eine konstante
Zeichenkette!
Der Pferdefuss an
char *str = "abc";
ist, dass es der Sprachlogik folgend eigentlich nicht zulässig sein
sollte. Weil "abc" in einem nicht modifizierbaren Bereich des Speicher
landet bzw. landen darf, folglich eigentlich vom Typ const char * sein
müsste.
Wenn dies aber der Fall wäre, dann würden allerlei antike C Programme
auf die Nase fallen, die noch aus der Zeit stammen, als es kein "const"
gab. Folglich ist "abc" vom Typ char *, aber nur eigentlich const.
Die ursrüngliche Frage war übrigens, warum so ein Konstrukt
char *str = "abc";
per se undefiniert sein soll.
Ich halte das für eine unzutreffende Aussage.
Das man besser ein const davor schreibt ist klar, ändert aber nichts
daran.
Skeptiker schrieb:> Ich halte das für eine unzutreffende Aussage.
Das Statement ist zulässig. Wie eben beschrieben ist eine solche
String-Konstante aus historischen Gründen nicht "const". Dazu C99 im
Rationale:
"String literals are not required to be modifiable. This specification
allows implementations to share copies of strings with identical text,
to place string literals in read-only memory, and to perform certain
optimizations. However, string literals do not have the type array of
const char in order to avoid the problems of pointer type checking,
particularly with library functions, since assigning a pointer to const
char to a plain pointer to char is not valid."
Hallo,
char *str = "xyz";
ist nicht undefiniert.
Ein const davor schreiben ändert nur die Definition des Pointers,
der Inhalt des Srings ist nach wie vor änderbar.
"xyz" ist ein Literal, und nicht automatisch konstant.
Bei
const char const *str = "xyz";
wäre der String konstant.
Andreas
Nicht zulässig ist das im verwiesenen Thread gezeigte:
char* str = "abc";
str[2] = 'd'; // <-----------
printf("%s", str);
weil hier der String modifiziert wird.
A. K. (prx) schrieb:
> Der Pferdefuss an> char *str = "abc";> ist, dass es der Sprachlogik folgend eigentlich nicht zulässig sein> sollte. Weil "abc" in einem nicht modifizierbaren Bereich des Speicher> landet bzw. landen darf, folglich eigentlich vom Typ const char * sein> müsste.
Wird aber von verschiedenen Compilern ohne Warnmeldung compiliert.
Wie's beim gcc ist weiß ich gerade nicht.
Skeptiker schrieb:> Möchte gerade noch mal das Beispiel aus dem anderen Thread aufgreifen.>> Beitrag "Re: Was ist die Ausgabe?">> Darin wird unter 2) behauptet>> " Selbst wenn man es syntaktisch zu einem C-Programm macht,> ist die 1. Zeile laut C-Standard undefiniert. ">> bezogen auf>>> char* str = "abc";>> Was bitte soll daran "undefiniert" sein?
Vielleicht war das einfach nur ein Versehen, und die zweite Zeile war
gemeint. Die versucht nämlich, diesen Zeiger zu benutzen, um den String
zu ändern. Und das ist tatsächlich undefiniert.
Verwirrter schrieb:> Ja sicher aber doch wohl kaum so:>>> char* str = "abc";>> wenn dann ja wohl so:>> char *str = "abc";
Völlig egal, ob du
1
char*str
1
char*str
1
char*str
1
char*str
oder auch
1
char
2
*
3
str
schreibst. Ist alles das gleiche.
Hans schrieb:> Mladen G. schrieb:>>> wenn dann ja wohl so:>>>>>> char *str = "abc";>> ja, das ist logischer. Geht aber beides.
Es ist eher unlogischer, aber es ist so, wie's die Erschaffer von C sich
gedacht haben.
Skeptiker schrieb:> Wird aber von verschiedenen Compilern ohne Warnmeldung compiliert.
Ja. Eben das versuchte ich damit zu erklären. Hab ich mich zu
unglücklich ausgedrückt?
Rolf Magnus schrieb:> Es ist eher unlogischer, aber es ist so, wie's die Erschaffer von C sich> gedacht haben.
Das wird erst verständlich, wenn man sich vor Augen hält, dass es
"const" erst seit ANSI C gibt. Strings gab es aber schon vorher.
Andreas schrieb:> Ein const davor schreiben ändert nur die Definition des Pointers,> der Inhalt des Srings ist nach wie vor änderbar.
Nein, eine Änderung des Strings ist nicht generell zulässig. C99
erwähnt dies jedoch als gängige Erweiterung (J.5.5).
Das Beispiel findet sich übrigens auch im "Programmieren in C",
Kernighan/Ritchie, Zweite Ausgabe ANSI.
S. 101
char *pmessage = "now is the time"; /* ein Zeiger */
Zitat
" ... Andererseits ist pmessage ein Zeiger der so initialisiert ist, daß
er auf eine konstante Zeichenkette zeigt; der Zeiger kann später so
verändert werden, daß er auf etwas anderes zeigt. Versucht man aber, den
Inhalt zu ändern, ist das Resultat undefiniert."
Ich finde das eigentlich sehr aussagekräftig.
A. K. schrieb:> Rolf Magnus schrieb:>> Es ist eher unlogischer, aber es ist so, wie's die Erschaffer von C sich>> gedacht haben.>> Das wird erst verständlich, wenn man sich vor Augen hält, dass es> "const" erst seit ANSI C gibt. Strings gab es aber schon vorher.
Ähm, ich hab mich da auf was völlig anderes bezogen. Hans hat da wohl
eine Zeile zu wenig zitiert. Es ging um "char* str" vs. "char *str".
Skeptiker schrieb:> Möchte gerade noch mal das Beispiel aus dem anderen Thread aufgreifen.>> Beitrag "Re: Was ist die Ausgabe?">> Darin wird unter 2) behauptet>> " Selbst wenn man es syntaktisch zu einem C-Programm macht,> ist die 1. Zeile laut C-Standard undefiniert. "
Sollte heißen: Die 2. Zeile in Zusammenspiel mit der 1. Zeile.
6.5.4 String Literals $7
1
It is unspecified whether these arrays are distinct provided their
2
elements have the appropriate values. If the program attempts to
A. K. (prx) schrieb:
> Nicht zulässig ist das im verwiesenen Thread gezeigte:> char* str = "abc";> str[2] = 'd'; // <-----------> printf("%s", str);> weil hier der String modifiziert wird.
Ja genau. Ergibt sowohl in PellesC als auch im VisualC++ 2008 (Express)
eine unbehandelte Ausnahme (Exeption).
Johann L. (gjlayde) schrieb:
bezogen auf das 1. Posting im Thread hier
> Sollte heißen: Die 2. Zeile in Zusammenspiel mit der 1. Zeile.> 6.5.4 String Literals $7> It is unspecified whether these arrays are distinct provided their> elements have the appropriate values. If the program attempts to> modify such an array, the behavior is undefined.
Danke für die Klarstellung!
Irgendwie verleitet C/C++ generell dazu schnell in irgend ein
Fettnäpfchen zu treten.
;-)
Skeptiker schrieb:> Ja genau. Ergibt sowohl in PellesC als auch im VisualC++ 2008 (Express)> eine unbehandelte Ausnahme (Exeption).
Weil der Speicher dafür read-only ist und ein schreibender Zugriff
darauf die Exception auslöst. Bei AVRs hingegen wird es funktionieren.
A. K. schrieb:> Skeptiker schrieb:>> Ja genau. Ergibt sowohl in PellesC als auch im VisualC++ 2008 (Express)>> eine unbehandelte Ausnahme (Exeption).>> Weil der Speicher dafür read-only ist und ein schreibender Zugriff> darauf die Exception auslöst. Bei AVRs hingegen wird es funktionieren.
Allerdings kann es auch da zu unerwarteten Nebenwirkungen kommen. Denn
das da:
A. K. schrieb:> share copies of strings with identical text
macht GCC. Er geht auch so weit, daß z.B. "Hello World" und "World" sich
den Speicher teilen können. Wenn also nochmal irgendwo ein identischer
String vorkommt oder einer, der dem Ende des geänderten Strings
entspricht, kann's halt sein, daß sich die Änderung auf den auch mit
auswirkt.
Skeptiker schrieb:> Was bitte soll daran "undefiniert" sein?
Nichts.
Der Typ hat keine Ahnung von C und phantasiert.
Er schreibt über einen char Zeiger in Speicher, in den er eugentlich
nicht schreiben darf.
Ein guter C Compiler hätte das bemerkt (zuweisung von const char an
char), ein guter Programmiere hätte das verhindert (durch const) und ein
guter Rechner hätte den Speicher geschützt, aber auf einem uC kann man
das nicht unbedingt erwarten.
Verwirrter schrieb:> Kannst Du denn mal erklären was char* bedeuten soll, oder bin ich der> einzige der das nicht kennt?
Meinst du dass in dem Thread jeder mitmachen muss der kein C kennt?
MaWin schrieb:> Ein guter C Compiler hätte das bemerkt (zuweisung von const char an> char),
Nein. "abc" ist nicht vom Typ const char *, sondern char *.
Andreas schrieb:> Bei> const char const *str = "xyz";> wäre der String konstant.
Ein const reicht für das char. Eins der beiden kannst Du weg lassen.
Falls der Zeiger auch noch const sein soll, dann muss ein const zwischen
dem *chen und und str stehen.
Guckst Du Deklarationen in C ziemlich am Ende der Seite.
MaWin schrieb:> Der Typ hat keine Ahnung von C und phantasiert.
Der "Typ" ist der mit C recht gut vertraute Johann L. und hat am
-fwritable-strings erkennbar die zweite Zeile gemeint, aber
versehentlich von der ersten Zeile geschrieben.
> zuweisung von const char an char
Ist zulässig. Erst bei Pointern nicht mehr.
A. K. schrieb:> Nein. "abc" ist nicht vom Typ const char *, sondern char *.
Auch nicht, sondern vom Typ char [4].
Wo bleibt eigentlich der c_hater? So ein Thread ist doch massig Wasser
auf seine Mühlen :)
A. K. (prx) schrieb:
MaWin schrieb:
>> Der Typ hat keine Ahnung von C und phantasiert.> Der "Typ" ist der mit C leidlich vertraute Johann L. und hat am> -fwritable-strings erkennbar die zweite Zeile gemeint, aber> versehentlich von der ersten Zeile geschrieben.
Deswegen hatte ich mich auch gewundert wenn jemand mit viel Erfahrung
sowas schreibt und keiner widerspricht. Aber das ist jetzt aufgelärt.
Dennoch interessant wieviel Gesprächsbedarf dieser Thread zutage
fördert.
;)
Verwirrter schrieb:> Ja sicher aber doch wohl kaum so:>>> char* str = "abc";>> wenn dann ja wohl so:>> char *str = "abc";
Nö, kommt auf deine Code-Convention an.
Bei uns in der Firma "gehört der Stern zum Datentyp" (char* var) und
"nicht zur variablen" (char *var)
fibonacci (Gast) schrieb:
> http://home.fhtw-berlin.de/~junghans/cref/CONCEPT/pointers.html> main()> {> int *Width;> *Width = 34;> }>> was haltet ihr hiervon?
Ich würde sagen, da hat der Herr G. Junghanns von der fhtw-Berlin, der
diesen C-Kurs aus dem englischen Original von Martin Leslie (Release
1.07) übersetzt hat, nicht aufgepasst.
Besser sich das Original mal kurz betrachten. Das ist neuer (Rel. 1.09)
und die Stelle ist jetzt etwas anders erklärt.
Übersicht
http://gd.tuwien.ac.at/languages/c/cref-mleslie/cref.htmlhttp://gd.tuwien.ac.at/languages/c/cref-mleslie/CONCEPT/pointers.html
main()
{
int *Width; /* 1 */
Width = (int *)malloc(sizeof(int)); /* 2 */
*Width = 34; /* 3 */
}
ABER: Dass der Autor hier eine Funktion verwendet ohne den
erforderlichen Header einzubinden #include <stdlib.h> spricht nicht
gerade für ihn bzw. deutet auf weitere wohl zu erwartende
Schlampigkeiten hin.
Fazit: Es gibt bessere Lernhilfen.
TriHexagon (Gast) schrieb:
> Wenn dann schon richtig richtig:
Fehlt aber im Original (aber er benutzt ja auch printf ohne die stdio.h
einzubinden und stärt sich nicht dran).
> Soll das echt Lernmaterial sein?
Also der zentrale Informatikdienst der TU-Wien sieht das wohl so.
http://gd.tuwien.ac.at/languages/c/
Rolf Magnus schrieb:> Hans schrieb:>> Mladen G. schrieb:>>>> wenn dann ja wohl so:>>>>>>>> char *str = "abc";>>>> ja, das ist logischer. Geht aber beides.>> Es ist eher unlogischer, aber es ist so, wie's die Erschaffer von C sich> gedacht haben.
Kernighan, Ritchie 2.Auflage, Seite 84:
Kaj schrieb:> Bei uns in der Firma "gehört der Stern zum Datentyp" (char* var) und> "nicht zur variablen" (char *var)
Das Problem mit dieser Schreibweise ist, dass sie dann gar nicht mehr
so logisch ist, wenn man mehrere Elemente auf einmal deklariert:
1
char*var1,var2;
"var1" ist vom Typ "Zeiger auf char", "var2" jedoch vom Typ "char".
Klar, man kann Mehrfachdeklarationen/-definitionen nun wieder per
lokalem Regelwerk verbieten, aber wirklich sinnvoll ist das auch
wieder nicht.
Bei der alternativen Schreibweise:
1
char*var1,*var2;
ist offensichtlicher, dass man den Stern für zwei Zeigervariablen auch
zweimal schreiben muss.
Jörg Wunsch schrieb:> Klar, man kann Mehrfachdeklarationen/-definitionen nun wieder per> lokalem Regelwerk verbieten, aber wirklich sinnvoll ist das auch> wieder nicht.
C-Programmierer sind von Natur aus schreibfaul - wenn man das im
Hinterkopf behält, wird (fast) immer ein Schuh draus.
Die Syntax der Deklarationen sollte wie die Verwendung in Expressions
aussehen. So hatte Ritchie sich das ursprünglich gedacht. Das Ergebnis
war und ist freilich einfach nur eine nackte Katastrophe. Mit der wir
seit Jahrzehnten leben müssen.
Kaj schrieb:> Bei uns in der Firma "gehört der Stern zum Datentyp" (char* var) und> "nicht zur variablen" (char *var)
"Gehören" tut in
1
char*var;
der "*" nach dem C-Standard eindeutig zu "var" und nicht zu "char".
"char" ist dabei der type-specifier und "* var" der declarator.
Diese Syntaxdefinition macht sich – wie bereits von Jörg aufgezeigt –
besonders bei Mehrfachdeklarationen bemerkbar.
Außerdem:
Wenn man den "*" an das "char" klebt
1
char*var;
müsste man konsequenterweise bei Array- und Funktionsdeklarationen die
Größenangabe bzw. die Argumentliste ebenfalls direkt hinter das "char"
schreiben, also nicht so
1
chararray[4];
2
charfunction(intn);
sondern so:
1
char[4]array;
2
char(intn)function;
Diese Schreibweise hätte tatsächlich den Vorteil der klaren Trennung
zwischen Typ und Indentifier:
1
// ___ Typ ___ _ Identifier _
2
// / \ / \ ;
3
char*var;
4
char[4]array;
5
char(intn)function;
Aber so ist die C-Syntax nun einmal nicht spezifiziert, und die beiden
letzten Zeilen sind im Gegensatz zur ersten nicht nur syntaktisch
ungenau, sondern sogar fehlerhaft. Also verfährt man am besten in allen
Fällen einheitlich und packt die ganze Symbolik aus *, [] und () zum
Identifier, genau so, wie es weiland Meister Ritchie schon gemacht hat,
ob es nun schön aussieht oder nicht.
Wenn man das nicht mag, muss man halt Pascal nehmen. ;-)
1
var name1, name2, name3: type;
Da ist es wirklich sauber getrennt.
Aber es war halt nicht Niklaus Wirth, der damals in der Situation war,
den Assembler auch für die unteren Ebenen eines Betriebssystems ablösen
zu wollen, sondern es waren Dennis Ritchie und seine Kollegen. Sie
hatten vielleicht nicht so akademisch ausgefeilte Vorstellungen von
einer
angenehmen Syntax der Sprache, dafür aber ganz offensichtlich recht
gute Ideen, wie man so eine Sprache aufbauen muss, damit sie auch
wirklich eine Alternative zur Assemblernutzung auch in diesem Bereich
ist, ohne dass man zu viel Performance einbüßt. Konstrukte wie *cp++
oder --*sp ließen sich direkt auf die PDP-11 abbilden.
TriHexagon schrieb:> Wenn dann schon richtig richtig:
richtig richtig richtig wäre es, wenn man den Rückgabewert von malloc()
prüfen würde und entsprechend ragiert ....
Die Typ-Deklarations-Syntax von C und umsomehr C++ ist leider ziemlich
verkorkst. Seit C++11 kann man sich da ein bisschen behelfen wie z.B. so
http://ideone.com/mxUPZt - die Verschachtelung der einzelnen "Modifier"
wie cnst, ary etc. macht eher klar was wann kommt und wie
zusammengesetzt ist als die übliche Aneinanderreihung von *&[]() ...
Egal ob C, oder C++ - wer diese Programmiersprachen beherrscht, fragt
nicht nach Syntax-Unklarheiten. Umso mehr wunderst es mich, dass hier so
oft nachgefragt wird. Sind das nur "Abschreiber" oder Copy&Paster...
Mark Brandis schrieb:> D. V. schrieb:>> C-Programmierer sind von Natur aus schreibfaul>> Schlechte Programmierer sind von Natur aus schreibfaul.
Nur bedeutet viel zu schreiben nicht immer gut programmieren.
Dr. Sommer schrieb:> D. V. schrieb:>> Egal ob C, oder C++ - wer diese Programmiersprachen beherrscht, fragt>> nicht nach Syntax-Unklarheiten.> Auch wenn man sowas sieht:const int& (*f) (const int* [7], int* (&y)> [7]);sofort absolut klar was gemeint ist?!
Es gibt auch andere Sprachen, die nicht jeder beherrscht. Ich behaupte
auch nicht, Französisch wäre Scheiße, blos weil ich vor 35 Jahren zu
faul war, Vokabeln zu lernen. Wem C nicht gefällt, der könnte doch
einfach einer der vielen Alternativen verwenden und solche Threads
lassen!
Bastler schrieb:> Es gibt auch andere Sprachen, die nicht jeder beherrscht. Ich behaupte> auch nicht, Französisch wäre Scheiße, blos weil ich vor 35 Jahren zu> faul war, Vokabeln zu lernen.
Man kann aber behaupten dass französisch scheiße weil unnötig
kompliziert ist. Man kann auch akzeptieren dass einige Dinge in C und
C++ vermurkst sind und sie dennoch verwenden, und wenn man ein Problem
hat im Forum fragen...
> Wem C nicht gefällt, der könnte doch> einfach einer der vielen Alternativen verwenden und solche Threads> lassen!
Für C, vielleicht. Für C++ gibt es keine ähnlich mächtige Alternative.
Dr. Sommer schrieb:> D. V. schrieb:>> Egal ob C, oder C++ - wer diese Programmiersprachen beherrscht, fragt>> nicht nach Syntax-Unklarheiten.> Auch wenn man sowas sieht:const int& (*f) (const int* [7], int* (&y)> [7]);sofort absolut klar was gemeint ist?!
Ja, es ist völlig klar, dass das absoluter Blödsinn ist. Wer solche
Funktionszeiger in C++ benutzt hat den Schuss nicht gehört ;)
Grüsse
Bastler schrieb:> Mark Brandis schrieb:>> D. V. schrieb:>>> C-Programmierer sind von Natur aus schreibfaul>>>> Schlechte Programmierer sind von Natur aus schreibfaul.>> Nur bedeutet viel zu schreiben nicht immer gut programmieren.
Richtig. Die Wahrheit liegt, wie so oft, in der Mitte.
A. K. schrieb:> Das wird erst verständlich, wenn man sich vor Augen hält, dass es> "const" erst seit ANSI C gibt. Strings gab es aber schon vorher.
Huch, hab ich da was verpaßt?
Nee, hab ich nicht. C kennt keine Strings.
Es kennt nur ein Agreement, wonach bei einem Array aus char's dann
Schluß ist, wenn ein char mit dem numerischen Wert 0 auftaucht. Also
KEINE Strings, auch nicht bei ANSI-C.
Gäbe es bei C einen Datentyp 'String', dann könnte man ihn auch zuweisen
und sowohl als Argument als auch als Resultat als auch in
Ergibtanweisungen verwenden verwenden. Aber so wie es ist, werden
milliardenfach immer wieder Arrays nach einem Nullbyte durchsucht, immer
wieder und wieder. Und selbst bei konstanten Literalen, wo man deren
Size bereits zur Compilezeit hätte festlegen können. strlen läßt
milliardenfach grüßen.
Jörg Wunsch schrieb:> Das Problem mit dieser Schreibweise ist, dass sie dann gar nicht mehr> so logisch ist, wenn man mehrere Elemente auf einmal deklariert:> char* var1, var2;
Ja, Jörg, das ist mal wieder eine der vielen Kurzsichtigkeiten von K&R
gewesen. Trotzdem ist
typbez* bezeichner;
die weitaus logischere Schreibweise (die ich auch bevorzuge), denn im
Klartext steht (und soll ja so auch stehen): "Hier deklariere ich einen
Zeiger auf den Typ 'typbez' und nenne ihn 'bezeichner'." und da gehört
der Stern zum 'typbez' dazu. Das, was du da als Stolperfalle aufzeigst,
ist eigentlich ein schwerer Bug, denn eine Typfestlegung sollte für
alle aufgezählten Variablen gelten und nicht nur für den ersten und dann
nur noch partiell für die nachfolgenden. Jahaha, ich weiß, "es ist eben
so, basta!" aber der Logiker in mir erklärt die Urheber dieser Sprache
für Leute, die überhaupt keinen Weitblick hatten.
Deine Vorliebe kann ich ja verstehen, aber sie ist eher unlogischer,
denke mal an so eine recht beliebte Sentenz:
*bezeichner++ = *andererbezeichner++;
da ist klar, daß der Stern am Anfang des Bezeichners zum
Dereferenzieren benutzt wird, also zum eigentlichen Erfüllen der
Zeige-Funktion eines Zeigers. Aber bei der Deklaration eines Zeigers
will man ja einen Zeiger vereinbaren und nicht einen Zeiger
dereferenzieren. Hab ich mich klar genug ausgedrückt oder nicht?
Abgesehen davon frage ich mich, wozu solche Konstrukte wie
char* str = "abc";
überhaupt hingeschrieben werden. Ein
const char str[] = {"abc"};
sollte doch ausreichen, um damit ein sinnvolles Programm schreiben zu
können.
W.S.
MaWin schrieb:>> "abc" ist nicht vom Typ const char *, sondern char *.>> Das bestimmt der Compiler.
Den Datentyp lexikalischer Strings definiert der C Standard eindeutig
als char[], nicht const char[].
C99+C11: "For character string literals, the array elements have type
char"
C99 Rationale: "However, string literals do not have the type array of
const char in order to avoid the problems of pointer type checking"
Der Compiler bestimmt, was passiert, wenn man drauf zu schreiben
versucht. Andere Baustelle. Das ist nicht schön, aber historisch
bedingt.
W.S. schrieb:> Hab ich mich klar genug ausgedrückt oder nicht?
Haha, was ein Pamphlet. Ich glaube Du hast Dich genau so ausgedrückt,
wie Du auch verstehst.
Wer C mit dem antiqiertem Overhead nicht klarkommt, und das scheinen die
meisten Newcomer hier zu sein, der orientiere sich bitte anderwärtig. Es
bibt genug Alternativen, als sich mit den antiquaren-Make-Files
runzuärgern.
W.S. schrieb:> Huch, hab ich da was verpaßt?
Offenbar.
> C kennt keine Strings.
Wenn der C99 Standard explizit "6.4.5 String literals" anführt, dann
gibt es offensichtlich Strings. Als lexikalisches Element, nicht als
Datentyp. Von einem entsprechenden Datentyp war auch nie die Rede.
W.S. schrieb:> Trotzdem ist>> typbez* bezeichner;>> die weitaus logischere Schreibweise (die ich auch bevorzuge), denn im> Klartext steht (und soll ja so auch stehen): "Hier deklariere ich einen> Zeiger auf den Typ 'typbez' und nenne ihn 'bezeichner'." und da gehört> der Stern zum 'typbez' dazu.
Wenn man deine in Anführungszeichen geschriebene Aussage wortwörtlich in
(wenn auch syntaktisch nicht ganz korrektes C) zurückübersetzen würde,
stände da
1
&typbezbezeichner;
Das wäre für mich eine logisch klingende Variablendeklaration.
Die von Ritchie gewählte Schreibweise
1
typbez*bezeichner;
ist eben so zu lesen: "Hier deklariere ich eine Variable mit Namen
'bezeichner', die dereferenziert den Typ 'typbez' ergibt.
Im obigen Fall deklariere ich die Variable explizit, d.h. ich sage dem
Compiler, was die Variable sein soll, nämlich ein Zeiger.
Im unteren Fall deklariere die Variable implizit, d.h. ich sage dem
Compiler, was ich mit der Variable tun können möchte, nämlich
dereferenzieren.
Beide Alternativen sind für mich logisch konsistent, wenngleich die
implizite Deklaration nach Ritchie in Prosa übersetzt etwas holprig
klingt.
Bei der Stroustrup-Schreibweise hingegen
1
typbez*bezeichner;
ändert das *-Symbol plötzlich alle seine Eigenschaften: Aus dem Präfix
wird ein Postfix, und aus der Bedeutung "dereferenziere" wird "Zeiger
darauf". Und der Versuch, diese Schreibweise auf Funktions- oder
Array-Zeiger anzuwenden,
1
typbez(*bezeichner)(void);
2
typbez(*bezeichner)[4];
ist vollends zum Scheitern verurteilt, weil das Klammerpaar den * ganz
klar an 'bezeichner' und nicht an 'typbez' bindet. Da hilft auch das
Verschieben des Leerzeichens wenig.
Die Stroustrup-Schreibweise ist für mich einfach nur eine Vergewaltigung
der C-Syntax. Diese mag vielleicht hässlich sein, man muss sie deswegen
aber nicht auch noch zum Krüppel schlagen.
W.S. schrieb:> daß der Stern am Anfang des Bezeichners zum Dereferenzieren benutzt> wird, also zum eigentlichen Erfüllen der Zeige-Funktion eines Zeigers.> Aber bei der Deklaration eines Zeigers will man ja einen Zeiger> vereinbaren und nicht einen Zeiger dereferenzieren.
Ja, und? Deklaration und Benutzung sind zwei völlig verschiedene
Dinge. Auch Pascal benutzt für beides ein @. Dort ist nur die
Trennung des Typs vom Variablennamen dank des Doppelpunktes und der
anderen Reihenfolge übersichtlicher. Außerdem kann man dort nur
Variablendeklarationen aneinanderreihen, die alle den gleichen Typ
benutzen.
D. V. schrieb:> Wer C mit dem antiqiertem Overhead nicht klarkommt, und das scheinen die> meisten Newcomer hier zu sein, der orientiere sich bitte anderwärtig. Es> bibt genug Alternativen, als sich mit den antiquaren-Make-Files> runzuärgern.
Wer C und Makefiles durcheinanderwürfelt, mit dem muss man eigentlich
hier nicht weiter diskutieren. Das einzige, was diese beiden Sprachen
gemeinsam haben, ist der Zuweisungsoperator "=".
Mit dem ursprünglichen Thema hat das alles sowieso nichts mehr zu tun.
W.S. schrieb:> Abgesehen davon frage ich mich, wozu solche Konstrukte wie>> char* str = "abc";>> überhaupt hingeschrieben werden. Ein>> const char str[] = {"abc"};
Weil das erste Konstrukt etwas völlig anderes bedeutet, als das zweite?
Klaus Wachtler schrieb:> TriHexagon schrieb:>> Wenn dann schon richtig richtig:>> richtig richtig richtig wäre es, wenn man den Rückgabewert von malloc()> prüfen würde und entsprechend ragiert ....
... und zum Schluss wieder frei gibt.
Yalu X. schrieb:> Die Stroustrup-Schreibweise ist für mich einfach nur eine Vergewaltigung> der C-Syntax. Diese mag vielleicht hässlich sein, man muss sie deswegen> aber nicht auch noch zum Krüppel schlagen.
In C++ ist die C-Syntax ja leider nicht konsequent weitergeführt worden,
denn bei einer Referenz gilt diese "implizite Schreibweise" nicht mehr:
1
typbez&bezeichner;
Wenn ich im Code &bezeichner schreibe, ergibt sich ein typbez* und nicht
ein typbez.
Abgesehen von Mehrfachdeklarationen, auf die man problemlos verzichten
kann, gibt es als C++-Programmierer aus meiner Sicht keinen Grund, die
Schreibweise
1
typbez*bezeichner;
gegenüber
1
typbez*bezeichner;
zu bevorzugen.
Man braucht die Typen ja nicht nur bei alleinstehenden
Variablendeklarationen, sondern z.B. auch als Templateparameter oder bei
Casts. Da ist imo
1
typbez*bezeichner=(typbez*)bezeichner2;
besser lesbar als ein
1
typbez*bezeichner=(typbez*)bezeichner2;
Da es wie gesagt in C++ für mich keinen logischen Grund für oder gegen
eine der Schreibweisen gibt, nehme ich die besser lesbare und benutze
sie auch in "reinem C".
Hans schrieb:> Kernighan, Ritchie 2.Auflage, Seite 84:> The declaration of the pointer ip,>> int *ip;>> is intended as a mnemonic; it says that the expression *ip is an int.>> und das ist für mich logischer.
Ich kenne die Argumentation. Ob das nun logischer ist, ist wohl
Ansichtssache. Für mich heißt das Sternchen dort schlicht "Pointer auf
das, was links davon steht". int ist ein Integer, int* ist ein
Integer-Pointer. Und da der Typ eben "Integer-Pointer" und der Name "ip"
ist und nicht etwa der Typ "Integer" und der Name "Pointer-ip", gehört
der Stern zum int. Er ist Teil des Typs und nicht Teil des Namens.
Für mich hat der Stern hier eine ganz andere Bedeutung als bei *ip = 5.
Einmal heißt es "mach einen Pointer auf den Typ, der links davon steht"
und einmal heißt es "dereferenziere das, was rechts davon steht".
A. K. schrieb:> Die Syntax der Deklarationen sollte wie die Verwendung in Expressions> aussehen. So hatte Ritchie sich das ursprünglich gedacht. Das Ergebnis> war und ist freilich einfach nur eine nackte Katastrophe. Mit der wir> seit Jahrzehnten leben müssen.
Da gibts ja so einige Sachen, die sie für intuitiv gehalten haben, über
die aber tatsächlich inzwischen etliche Generationen von
C-Programmierern beim Erlenen der Sprache reihenweise gestolpert sind.
Yalu X. schrieb:> müsste man konsequenterweise bei Array- und Funktionsdeklarationen die> Größenangabe bzw. die Argumentliste ebenfalls direkt hinter das "char"> schreiben, also nicht so> char array[4];> char function(int n);>> sondern so:> char[4] array;> char(int n) function;
Das sieht aus heutiger Sicht natürlich etwas gewöhnungsbedürftig aus,
wäre aber meiner Ansicht nach besser gewesen.
Dr. Sommer schrieb:> Auch wenn man sowas sieht:const int& (*f) (const int* [7], int* (&y)> [7]);sofort absolut klar was gemeint ist?!
Die Definition der Standard-C-Funktion signal() sieht ohne Zuhlifenahme
einer Typedef-Krücke auch schon nett aus:
W.S. schrieb:> milliardenfach immer wieder Arrays nach einem Nullbyte durchsucht, immer> wieder und wieder. Und selbst bei konstanten Literalen, wo man deren> Size bereits zur Compilezeit hätte festlegen können. strlen läßt> milliardenfach grüßen.
Also wenn ein Compiler das nicht optimiert bekommt, such dir einen
anderen.
Yalu X. schrieb:> Bei der Stroustrup-Schreibweise hingegen> typbez* bezeichner;>> ändert das *-Symbol plötzlich alle seine Eigenschaften: Aus dem Präfix> wird ein Postfix, und aus der Bedeutung "dereferenziere" wird "Zeiger> darauf".
Das war er schon immer. Bei der Definition wird nichts dereferenziert,
sondern das Sternchen sagt dem Compiler, daß er einen Zeiger machen muß.
Eine Dereferenzierung ist hier nur hypothetisch vorhanden a la "was
wäre, wenn ich dereferenzieren würde".
markus schrieb:> Hi, koenntet Ihr ein paar Links auf die C Standarts posten, damit man> genau nachlesen kann?http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=57853http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2fIEC+9899%3a2011Yalu X. schrieb:> Die Stroustrup-Schreibweise ist für mich einfach nur eine Vergewaltigung> der C-Syntax. Diese mag vielleicht hässlich sein, man muss sie deswegen> aber nicht auch noch zum Krüppel schlagen.
Das hat C schon selbst hinbekommen. Bei einem konstanten Zeiger paßt die
Sache dann nämlich auch schon nicht mehr:
int i;
int * const ip = &i;
Rolf Magnus schrieb:> Das sieht aus heutiger Sicht natürlich etwas gewöhnungsbedürftig aus,> wäre aber meiner Ansicht nach besser gewesen.
Linear statt mit einer verschachtelten Kombination aus Präfix und
Postfix Operatoren und Operator-Vorrang. Vorzugsweise indem man mit der
Variablen anfängt und sich zum Grundtyp durchschlägt, orientiert an
Pascal
var a: array [10] of pointer to integer;
oder wenns sein muss kryptischer als
auto a [10]*int;
Der C Philosophie folgend wärs die umgekehrte Reihenfolge:
int*[10]a;
die allerdings mit dem Problem kämpft, dass wie im real existierenden C
der Typname als Keyword für den Anfang einer Deklaration steht
(ersatzweise extern/static/...). Am Anfang war das harmlos, bis typedef
erfunden wurde und der Logik hinter der Syntax einen Tritt in den
Allerwertesten verpasste. Mit obligatorischer storage class hätte man
dies immer noch retten können, also
auto int*[10]a;
Wobei da aber immer noch die für die erste Übersicht des Quellcodes
wichtige Schlüsselinformation bestens versteckt wird, nämlich der Name.
Structs so zu schreiben, dass sie sauber aussehen, ist ja nicht grad
einfach. Je länger der Typ wird, desto weiter rechts rutscht der Name,
was schaurig aussieht, wenn man die Namen streng untereinander schreiben
will.
D. V. schrieb:> Egal ob C, oder C++ - wer diese Programmiersprachen beherrscht, fragt> nicht nach Syntax-Unklarheiten.
Kannst es auch anders ausdrücken: Wer zählt schon all die Verletzten und
Leichen, die unterwegs beim Versuch, in C und erst recht in C++ voll
durchzublicken, aus den diversen Kurven flogen.
Ironisch ist das deshalb, weil diese Verschwurbelung der Typen der
Motivation entsprang, es dem Anwender einfach zu machen. Aber "gut
gemeint" ist bekanntlich das Gegenteil von "gut gemacht".
Übrigens wirst du hier im Forum feststellen können, dass nicht nur
Anfänger über C stolpern, sondern auch Fortgeschrittene und Experten
sich über mache Aspekte trefflich streiten und Neues lernen können.
D. V. schrieb:> Egal ob C, oder C++ - wer diese Programmiersprachen beherrscht, fragt> nicht nach Syntax-Unklarheiten.
Wo wir grad bei C++ sind. Was bedeutet eigentlich
class T { ... };
T(a);
Ist das
(1) die Deklaration der Variablen a vom Typ T,
(2) oder der Aufruf eines Konstruktors von T,
und weshalb?
Beides ist etwas unperfekt. In (1) sind die Klammern unnötig und in (2)
fliegt das Ergebnis gleich wieder weg. Aber beide Interpretationen sind
von der Syntax her zulässig.
A. K. schrieb:> D. V. schrieb:>> Egal ob C, oder C++ - wer diese Programmiersprachen beherrscht, fragt>> nicht nach Syntax-Unklarheiten.>> Wo wir grad bei C++ sind. Was bedeutet eigentlich> class T { ... };> T(a);> Ist das> (1) die Deklaration der Variablen a vom Typ T,> (2) oder der Aufruf eines Konstruktors von T,> und weshalb?
Es handelt sich um die Definition einer Variablen. Bei der Erzeugung
wird automatisch der Konstruktor aufgerufen. Explizit einen Konstruktor
aufrufen kann man in C++ nicht. Er wird immer als Teil der
Objekterzeugung implizit aufgerufen.
Interessanter finde ich diesen Fall:
1
Tt(x);
Ist das die Definition eines Objekts vom Typ T, das mit einer Variablen
namens x initialisiert wird oder die Deklaration einer Funktion, die ein
T zurückgibt und einen Parameter von Typ x erhält?
A. K. schrieb:> Rolf Magnus schrieb:>> Explizit einen Konstruktor aufrufen kann man in C++ nicht.>> Aber sicher kann man das.struct T { T(int); int operator+ (T); };> int f(int a, int b)> {> return T(a) + T(b);> }> enthält 2 Konstruktoraufrufe. Von mir aus kannst du das freilich auch> die Erzeugung von 2 temporären Varablen
temporären Objekten
> mit Konstruktoraufruf nennen.
So nenne ich es. So nennt es auch ISO C++.
Egal wie umständlich du das nennst,
T(a);
ist folglich syntaktisch(!) eine gültige expression, wie auch
int(a);
oder
a;
Dass es eine Deklaration ist, ist reine Vereinbarung. Weil es in C
eindeutig ist.
> temporären Objekten
Stimmt.
> Interessanter finde ich diesen Fall:
Auch nicht übel, weil komplett ohne Redundanz. Es wird aber eindeutig,
weil C++ wie C Typnamen nicht als normale Identifier behandeln kann und
damit von x bereits auf syntaktischer Ebene bekannt ist, ob es ein
Typname ist oder nicht.
Die lexikalische Klassifizierung von Namen in nomale Identifier und in
Typnamen ist in C und C++ sowohl von der Bedeutung des Namens als auch
vom syntaktischen Kontext abhängig. Das ist nix für Freunde sprachlicher
Eleganz.
A. K. schrieb:> Egal wie umständlich du das nennst,> T(a);> ist folglich syntaktisch(!) eine gültige expression, wie auch> int(a);> oder> a;>> Dass es eine Deklaration ist, ist reine Vereinbarung.
Nein, denn es wird schließlich der Speicher für das Objekt zur Verfügung
gestellt, und das macht nicht der Konstruktor. Der initialisiert es nur
bzw. anderes ausgedrückt: Seine Aufgabe ist es, aus dem ihm zur
Verfügung gestellten rohen Speicher ein Objekt zu machen. Und die
einzige Möglichkeit, diese beiden Aktionen überhaupt getrennt
auszuführen, ist "placement new".
> Auch nicht übel, weil komplett ohne Redundanz. Es wird aber eindeutig,> weil C++ wie C Typnamen nicht als normale Identifier behandeln kann und> damit von x bereits auf syntaktischer Ebene bekannt ist, ob es ein> Typname ist oder nicht.
Spätestens bei Templates und von template-Parametern abhänigen Namen muß
C++ dann aber doch kapitulieren:
1
template<typenameT>
2
voidfunc()
3
{
4
Tt(T::x);
5
}
Um zu kennzeichnen, daß x ein Typ sein soll, muß man dem Compiler auf
die Sprünge helfen:
1
Tt(typenameT::x);
Und bei Templates gibt's noch viel lustigere Sachen. Da ist C echt
Kinderkram dagegen.
Rolf Magnus schrieb:> einzige Möglichkeit, diese beiden Aktionen überhaupt getrennt> auszuführen, ist "placement new".
Ich glaube wir reden aneinander vorbei. Mir ging es nur darum, dass
"T(a);" syntaktisch auch eine gültige expression ist. Das aber ist
völlig unabhängig davon, ob du Objekterzeugung und Initialisierung
getrennt betrachtest oder vereinfachend zusammenziehst, weshalb mich
dieser spezielle Aspekt nicht interessierte.
> Und bei Templates gibt's noch viel lustigere Sachen. Da ist C echt> Kinderkram dagegen.
Yep.
A. K. schrieb:> Ich glaube wir reden aneinander vorbei. Mir ging es nur darum, dass> "T(a);" syntaktisch auch eine gültige expression ist. Das aber ist> völlig unabhängig davon, wie man das Kind tauft.
Das hab ich schon verstanden. Aber du hast auf meine Aussage, daß man
Konstruktoren nicht explizit aufrufen kann, geantwortet, daß man das
sehr wohl könne. Was du angegeben hast, ist natürlich syntaktisch
korrekt, aber es ist kein expliziter Konstruktor-Aufruf, sondern die
Erzeugung eines Objekts, was natürlich unter anderem einen
Konstruktor-Aufruf beinhaltet, aber eben nicht auf diesen beschränkt
ist.
A. K. (prx) schrieb:
> Übrigens wirst du hier im Forum feststellen können, dass nicht nur> Anfänger über C stolpern, sondern auch Fortgeschrittene und Experten> sich über mache Aspekte trefflich streiten und Neues lernen können.
Den Eindruck habe ich auch. Dass alleine die Zuordnung des Asterisk (zur
Variablen oder zum Datentyp) so viel Diskussionsbedarf auslöst ...
Rolf Magnus (Gast) schrieb:
W.S. schrieb:
>> milliardenfach immer wieder Arrays nach einem Nullbyte durchsucht, immer>> wieder und wieder. Und selbst bei konstanten Literalen, wo man deren>> Size bereits zur Compilezeit hätte festlegen können. strlen läßt>> milliardenfach grüßen.> Also wenn ein Compiler das nicht optimiert bekommt, such dir einen> anderen.
Naja, ich finde sein Argument durchaus nachvollziehbar. Auch ein
optimierter Vorgang wird das Nullbyte nicht "erraten" können, sondern
muss danach suchen und das immer und immer wieder. Pascal hat die Anzahl
der Zeichen im ersten Byte gespeichert (zumindest im Ur-Pascal;
Inzwischen gibts auch andere Ausführungen des Stringtyps). Was war daran
so schlecht, außer dass sie dafür leider nur ein Byte spendiert hatten
und Strings damit nur 255 Zeichen lang sein konnten? Warum nicht die
Länge gleich mit ablegen, z.B. in den ersten 4 Bytes? Das müsste doch
einiges an Ausführungszeit bei der Verarbeitung von Zeichenketten
einsparen können oder nicht? Gibt es Programmiersprachen die so
vorgehen?
Skeptiker schrieb:> Gibt es Programmiersprachen die so> vorgehen?
Alle außer C... C++, ruby, PHP, Python, Java, etc. etc. ... insbesondere
alle OOP-Sprachen, in denen man sich leicht seine eigene String-Klasse
bauen kann, die die Länge und alles elegant kapselt. In diesen Sprachen
kann man dann auch typischerweise sowas schreiben:
Skeptiker schrieb:> Warum nicht die Länge gleich mit ablegen, z.B. in den ersten 4 Bytes? Das> müsste doch einiges an Ausführungszeit bei der Verarbeitung von> Zeichenketten einsparen können oder nicht?
Kommt drauf an, wie of man wirklich die Länge braucht. Beim bloßen
Iterieren durch den String kann die C-Methode sogar etwas effizienter
sein. Generell wäre es nicht nur bei Strings, sondern allgemein bei
Arrays nicht verkehrt gewesen, die Länge gleich mitzugeben. Das ist
nämlich ein weiterer großer Stolperstein für C-Anfänger. Ich weiß gar
nicht, wie oft ich schon so eine Frage in Foren und Newsgroups gelesen
habe:
"Ich wollte die Größe des Arrays mit sizeof ermitteln, aber bekomme
immer 4 raus, obwohl das Array 100 Elemente hat:
1
voidfunc(intarr[])
2
{
3
printf("%d\n",(int)sizeof(arr));
4
}
". Gefühlt gehört sie zu den Top-Fragen von C-Anfängern überhapt. Hätte
man aber die Länge mit ins Array verpackt, wäre natürlich die
fast-Gleichbehandlung von Arrays und Zeigern generell nicht mehr zu
halten gewesen. Das hätte der Klarheit der Sprache aber vermutlich sogar
geholfen.
Der Zusammenhang (und auch die Unterschiede) zwischen Arrays und Zeigern
gehören zu den am schwersten zu verstehenden Kapiteln beim Erlernen von
C.
Jörg Wunsch (dl8dtl) (Moderator) schrieb:
Skeptiker schrieb:
>> Auch ein optimierter Vorgang wird das Nullbyte nicht "erraten" können> Doch, bei einem Stringliteral schon
Erklär bitte mal ..
Skeptiker schrieb:>>> Auch ein optimierter Vorgang wird das Nullbyte nicht "erraten" können>> Doch, bei einem Stringliteral schon> Erklär bitte mal ..
In
char *s = "abc";
int n = strlen(s);
kann ein Compiler, der strcmp verinnerlicht hat, ohne grosse Not direkt
int n = 3;
draus machen, wenn sich s nicht zwischendrin ändert.
A. K. (prx) schrieb:
Skeptiker schrieb:
>>> Auch ein optimierter Vorgang wird das Nullbyte nicht "erraten" können>> Doch, bei einem Stringliteral schon> Erklär bitte mal ..> In> char *s = "abc";> int n = strlen(s);> kann ein Compiler, der strcmp verinnerlicht hat, ohne grosse Not direkt> int n = 3;> draus machen, wenn sich s nicht zwischendrin ändert.
Liegts etwa nur am eingeschalteten Debugger, dass trotz eingeschalteter
Optimierung beim Aufruf von strlen zig mal ein 'byte ptr [edx+eax],0'
ausgeführt wird?
So jedenfalls finde ich das nicht gerade effizient.
;)
@ Jörg Wunsch (dl8dtl) (Moderator)
Mir ist schon klar, dass ein C-Compiler bereits beim Übersetzen
ermitteln kann wie lang ein String Literal ist und dann einfach die
Länge nach eax lädt und mit einem ret die Funktion beendet. Ich hab nur
spassenshalber das mal in der IDE vom PellesC laufen lassen (debug mode)
und siehe da, er klappert Zeichen für Zeichen ab, führt jedesmal ein
compare aus, vergleicht mit jne usw. Trotz eingeschalterer Optimierung.
Und so schlecht ist der Pelles C Compiler nicht (auch wenn er nicht
gegen den gcc ankommt).
Oder ist das in der Release anders?
Skeptiker schrieb:> Oder ist das in der Release anders?
Da musst du schon deinen Compiler befragen.
GCC kann auch im Debugmodus (fast) alle Optimierungen aktivieren.
Andere Compiler können das ggf. nicht.
Von Pelles C ist zu lesen, dass der vom Princeton LCC abstammt. Und der
hat grad mal gute 10K Zeilen Quellcode. Viel Optimierung würde ich von
dem nicht erwarten. Da ist strlen schlicht eine Funktion wie jede
andere.
Rolf Magnus schrieb:> Explizit einen Konstruktor> aufrufen kann man in C++ nicht.
Und das ist doch auch gut so! Es kommt ja gar nicht so selten vor, dass
in einem Konstruktor dynamisch Speicher angefordert wird, der mit dem
Destruktor wieder freigegeben wird. Und wenn jetzt jeder Konstruktor so
geschrieben werden müsste, dass es sicher wäre ihn ein 2tes Mal aufrufen
zu können ....
A. K. (prx) schrieb:
> Von Pelles C ist zu lesen, dass der vom Princeton LCC abstammt. Und der> hat grad mal gute 10K Zeilen Quellcode. Viel Optimierung würde ich von> dem nicht erwarten. Da ist strlen schlicht eine Funktion wie jede> andere.
Pelle Orinius hat da schon einiges reingepackt über die Jahre
http://www.smorgasbordet.com/pellesc/about.htm
den Debugger z.B. finde ich klasse und die IDE sowieso. Ist halt ein
reiner C Compiler, also nix mit C++, ist schnell installiert und frei
verwendbar. Ideal um eben mal schnell was in C auszuprobieren, gerade
wenn man sich noch in der ewigen "C-Lernphase" befindet oder auch weil
er mehr kann (C99, C11), als der C-Compiler vom VisualC. Und er ist
schnell installiert.
http://www.smorgasbordet.com/pellesc/index.htm
Jörg Wunsch (dl8dtl) (Moderator) schrieb:
> GCC kann auch im Debugmodus (fast) alle Optimierungen aktivieren.> Andere Compiler können das ggf. nicht.
Aktiviert hatte ich die Compileroption schon. Das geht in der IDE sehr
schön übersichtlich. Der Unterschied war auch zu merken, aber so wie der
gcc hat er sich dennoch nicht verhalten. Naja, ist nicht schlimm. Der VC
C++ 2008 Express hat mir die Optimierung sogar verweigert, weil sie sich
(laut Fehlermeldung) mit einer anderen Optimierung nicht verträgt. Muss
erst mal schauen woran das jetzt wieder genau liegt.
lalala schrieb:> Und das ist doch auch gut so! Es kommt ja gar nicht so selten vor, dass> in einem Konstruktor dynamisch Speicher angefordert wird, der mit dem> Destruktor wieder freigegeben wird. Und wenn jetzt jeder Konstruktor so> geschrieben werden müsste, dass es sicher wäre ihn ein 2tes Mal aufrufen> zu können ...
Kann man eben doch, wie oben angedeutet, mit Placement new. Den
Destruktor im übrigen auch. Schlau ist das natürlich im allgemeinen
nicht; manchmal ists es aber gut für Optimierungen und "Low-Level"
Container - eine typische Implementation für z.B. std::vector verwendet
so etwas um blockweise Speicher allokieren zu können.
Dr. Sommer schrieb:> eine typische Implementation für z.B. std::vector verwendet> so etwas um blockweise Speicher allokieren zu können.
Alle Standard-Container nutzen das, da die ihren Speicher immer über
Allokatoren anfordern. Die kümmern sich aber eben nur um den Speicher.
Rolf Magnus schrieb:> Alle Standard-Container nutzen das, da die ihren Speicher immer über> Allokatoren anfordern. Die kümmern sich aber eben nur um den Speicher.
Stimmt ja, bei der Verwendung von Allokatoren bleibt einem ja gar nichts
anderes übrig.
Jörg Wunsch schrieb:>> Auch ein optimierter Vorgang wird das Nullbyte nicht "erraten" können>> Doch, bei einem Stringliteral schon.
Jörg, du hast die Sache WIRKLICH NICHT verstanden.
Sinn und Zweck von Strings aller Art ist deren Verwendung - und nicht
die Rückgabe eines einzelnen strlen Ergebnisses wie in deiner
Demonstration.
Normalerweise werden Strings als Argumente weitergegeben oder aneinander
gereiht oder sonstwie verarbeitet. Da ist dann immer strlen mit von der
Partie, entweder direkt im Usercode oder innerhalb strcat und Konsorten.
ich mach dir mal nen Vorschlag:
Programmiere doch mal in C ein Äquivalent zu sowas
const
foo1 = 'hallo';
foo2 = ' du da'#13#10;
procedure Drucke_Aus ( S: String);
begin
...
end;
begin
Drucke_Aus(foo1+foo2);
end.
Und dann unterhalten wir uns über die Anzahl der expliziten und/oder
impliziten Aufrufe von strlen. OK?
W.S.
Und was soll das jetzt? Wenn die Länge nicht bekannt ist, kann kann die
Compiler sie auch nicht per Kristallkugel bestimmen. Glaubst du, Jörg
wäre das nicht klar?
Wenn dir sein minimales Beispiel nicht zur Demonstration recht, was
gemeint war: Ich habe ein Beispiel gegeben, was etwas näher an der
Praxis ist: Testen eines Strings auf eine bestimmte Prefix.
W.S. schrieb:> Jörg, du hast die Sache WIRKLICH NICHT verstanden.
Und du hast meine Antwort aus dem Kontext gerissen, in dem sie
gegeben war. Was soll's also? Wolltest du einfach nur unbedingt
was erwidern?
W.S. schrieb:> Sinn und Zweck von Strings aller Art ist deren Verwendung - und nicht> die Rückgabe eines einzelnen strlen Ergebnisses wie in deiner> Demonstration.
strlen ist nur das einfachste Beispiel. Ein etwas besseres Beispiel:
strcpy(..., "abc");
Hier kann ein Compiler, der strcpy verinnerlicht hat, den String
kopieren ohne zur Laufzeit nach dem Ende suchen zu müssen.
Sämtliche String-Funktionen verschwinden bei gcc -O2:
1
main:
2
pushq %rbp
3
movabsq $2921832436294688, %rax
4
movq %rsp, %rbp
5
subq $32, %rsp
6
leaq 15(%rsp), %rdi
7
andq $-16, %rdi
8
movq %rax, 5(%rdi)
9
movl $1819042152, (%rdi)
10
movb $111, 4(%rdi)
11
call print
12
xorl %eax, %eax
13
leave
14
ret
Die Strings selbst verschwinden übrigens auch, d.h. sind nur in den
Immediates wiederzufinden.
Damit du nicht ganz leer ausgehst, lässt clang dir ein strlen übrig:
(Nicht unterbrechen lassen, hier folgt eine Zwischenbemerkung zum Thema
Geschichte zu Zeichenketten und PASCAL.)
Es wurden zuvor Vorzüge bei Pascal bezüglich explizit gespeicherter
String-Länge im ersten Byte erwähnt.
Im "Ur" -PASCAL (z. B. 1974, 1984, ISBN 3-540-96048-1) gab es nur
"PACKED ARRAY [1 .. ?] OF CHAR", also Zeichenketten fester Länge
[üblicherweise mit Leerzeichen am ungenutzen rechten Ende {space
padded}, kompatibel zu z. B. COBOL-Datentyp "PIC X(?)" oder alte
FORTRAN: "CHARACTER"...], also ohne explizit gespeicherte Länge. Dies
war gut für konstanten Offset in Records {Lochkarten, Files}. {Früher
verschwendete man Speicher durch Leerzeichen, heute mit XML.}
Das String-Konzept fehlte gemäss meiner Ansicht in dieser Sprache Pascal
eher noch mehr als bei C.
Für Konvertierungen oder Datenbank-Importe mussten diese trailing spaces
(von rechts her) abgeschitten werden. Dies erinnert an die übliche
C-Suche (von links her) nach dem '\0'-Terminator bei C.
Praktischerweise, aber ohne standard, fügten Hersteller Erweiterungen
(auch mit unsicheren Konkatenationsoperatoren) hinzu, z. B. wie folgt.
° Borland mit Turbo Pascal, Datentyp "STRING" (== "PACKED ARRAY [*0* ..
255] OF CHAR"), erstes Byte speichert explizite Länge, die Kapazität war
255.
° VAX-PASCAL bot zusätzlich einen eigenen Datentyp "STRING" an.
Wirth nutzte mit MODULA-2 ebenfalls den CHR(0)-Terminator (sofern das
letzte verfügbare Zeichen nicht besetzt ist) und bot String-Operationen
in einem Standard-Modul an.
Generell: Für mich ist die String-Geschichte nicht abgeschlossen. Wenn
ich mitbekomme, welch unbeachteter Wirbelwind in Java manchmal im
Hintergrund bei String-Operationen stattfindet, wundert es mich nicht,
dass dann gelegentlich von Perfomance-Problemen berichtet wird (->
"StringBuilder" verwendet?).
OK, rechte Klammer geschlossen zu diesem Thema :).
schoenen Abend
EDIT:
Hi *A. K.*, wollte Deinen interessanten Beitrag nicht unterbrechen, hab
gerade an meinem lange rumgeschrieben und dies abgesendet, dann Deinen
Beitrag inzwischen erst gesehn. Deine Tipps werd ich bereits morgen in
meiner eigenen C-Umgebung anwenden können, Danke!
Xeraniad X. schrieb:> Im "Ur" -PASCAL (z. B. 1974, 1984, ISBN 3-540-96048-1) gab es nur> "PACKED ARRAY [1 .. ?] OF CHAR", also Zeichenketten fester Länge
Noch schöner: Die Referenzimplementierung hatte eine Vorliebe für den
Typ ALFA, vordefiniert als PACKED ARRAY [1..10] OF CHAR. Grund: 6-Bit
Zeichensatz und eine 60-Bit CDC6600.
Danke @ A. K. für den Hinweis auf diese historische Architektur
http://en.wikipedia.org/wiki/CDC_6600 !
"... The Central Processor had eight general purpose 60-bit registers X0
through X7, ...". Ein CERN-User wird damals kreativ inspiriert worden
sein.
Wer seine C bzw. C++ Kentnisse an diversen Bugs überprüfen und schärfen
möchte, dem lege ich die Site
http://www.gimpel.com/html/bugs.htm
nahe.
Jede Menge kurze Codestücke, mit einer kleinen Fehlerbeschreibung. Jeder
Bug dreht sich um irgendeine Besonderheit, die man gerne übersieht.
Darunter dann immer die Lösung in der Form, wie sie von der von gimpel
verkauften Lint Lösung gemeldet wird.
Also: nicht schummeln - erst selber über dem Code brüten und dann
nachsehen, ob man richtig lag.
Mit einem meinen Ex-Kollegen war das lange Zeit ein monatlicher
'Wettkampf': Wer findet als erster die Problemstelle und kann auch
begründen, worin das Problem liegt?
Karl Heinz schrieb:> Mit einem meinen Ex-Kollegen war das lange Zeit ein monatlicher> 'Wettkampf': Wer findet als erster die Problemstelle und kann auch> begründen, worin das Problem liegt?
Das Problem ist da wohl oft der verkorkste Code in dem sich hervorragend
Bugs verstecken lassen... Würde man von vorneherein ordentlich Coden
sähe das oft anders aus.
Kindergärtner schrieb:> Würde man von vorneherein ordentlich Coden> sähe das oft anders aus.
Ja, oh ja,
dein Wort in die Ohren all der immer wieder nachwachsenden
Programmier-Schüler!
Aber die Realität sieht ganz anders aus.
Siehe:
for (const char * p = "Das ist ein Test!\n" ; c = *p; p++) {
SPI.transfer (c);
}
(heute hier gefunden: "Beitrag "Verständnisproblem";)
A. K. schrieb:> strcpy(..., "abc");> Hier kann ein Compiler, der strcpy verinnerlicht hat, den String> kopieren ohne zur Laufzeit nach dem Ende suchen zu müssen.
Du schreibst wirr. Kein Compiler kann zur Laufzeit irgend etwas tun -
mit Ausnahme von Skriptsprachen, die ja sowieso jedesmal zur Laufzeit
übersetzt werden müssen.
Ansonsten liegst du aber auch ein bissel daneben. Hätte ich nicht
Drucke_Aus(foo1+foo2);
sondern
Drucke_Aus(IntToStr(linenumber)+': '+foo1+IntToStr(aVariable)+foo2);
geschrieben, wäre der von unserem Mod erwähnte Sonderfall außen vor
geblieben. Jörg neigt zu Kommentaren "ich kenne da aber einen
Sonderfall, wo das alles viel einfacher geht.. weil ich schlauer bin..."
- aber das ist eben ein Sonderfall und nicht die Regel.
Ansonsten haben wir ja das Thema C mal wieder soweit abgegrast und
wissen mal wieder, was wir eigentlich schon lange gewußt haben: C hat
mehr Stolpersteine als Features und jedes Jahr auf's neue fällt deswegen
eine neuer Generation von Programmieranfängern auf die Nase. Bei manchen
fällt es gleich auf, das ist gut, aber bei vielen fällt das erst viel
später auf.
Wie geht's weiter? Wie würden wir uns wünschen, daß es weiterginge?
Die Inflation an Skriptsprachen in den letzten Jahren zeigt eigentlich
ganz deutlich, daß es durchaus einen erheblichen Leidensdruck bei den
Programmierern gibt, aber aus der eingetrichterten C-Denke kommen die
Leute offenbar nicht wieder heraus.
Und? Weiter auf dem bewährten C-Weg? Mit immer mächtigerer Hardware
immer komplexere Software weiter in C und seinen Geschmacksvarianten
schreiben? In 100 Jahren immer noch strlen und Pointerdeklarationen, die
die meisten Programmierer nicht mehr verstehen?
W.S.
W.S. schrieb:> Aber die Realität sieht ganz anders aus.
Augenkrebs... Ach ja, C++ ist schön:
1
std::stringmyString="Cheese!";
2
for(charc:myString){
3
tuwas(c);
4
}
W.S. schrieb:> Mit immer mächtigerer Hardware> immer komplexere Software weiter in C und seinen Geschmacksvarianten> schreiben? In 100 Jahren immer noch strlen und Pointerdeklarationen, die> die meisten Programmierer nicht mehr verstehen?
Was anderes wird dem gemeinen E-Techniker ja nicht beigebracht, daher
hat er keine Wahl...
W.S. schrieb:>> Hier kann ein Compiler, der strcpy verinnerlicht hat, den String>> kopieren ohne zur Laufzeit nach dem Ende suchen zu müssen.>> Du schreibst wirr. Kein Compiler kann zur Laufzeit irgend etwas tun
Also gut, die korrekte Fassung: Hier kann ein Compiler, der strcpy
verinnerlicht hat, Code erzeugen, der den String kopiert ohne nach dem
Ende suchen zu müssen.
> Ansonsten liegst du aber auch ein bissel daneben. Hätte ich nicht
Klar doch. Die C Stringdarstellung und -verarbeitung ist nicht wirklich
gut. Aber ich hatte doch nur exakt das getan, was du vorgeschlagen
hattest. ;-)
W.S. schrieb:> wäre der von unserem Mod erwähnte Sonderfall außen vor geblieben
Nochmal: schau dir den Kontext an, aus dem du meine Bemerkung heraus
gerissen hast. Nur um den ging es, und nicht darum, was wo wie alles
noch anders sein kann.
Davon abgesehen, die Überschrift heißt "C", dein Beispiel kann gar
kein C sein. Dass man mit C++-Klassen alles eine Portion komplexer
hat, ist logisch. Aber auch da sind die Compiler mittlerweile nicht
so schlecht mit dem Optimieren.
Jörg Wunsch schrieb:> Nochmal: schau dir den Kontext an, aus dem du meine Bemerkung heraus> gerissen hast.
Ach Jörg, du hast manchmal ne ausgesprochen rechthaberische Art drauf.
Ja, ich habe den gesamten Kontext gelesen, habe ihn jedoch zwecks
Platzersparnis nicht komplett zitiert - den restlichen Platz brauch ich
üblicherweise für meinen eigenen Text...
Um es nochmal auf den Punkt zu bringen: Texte aller Art, auch konstante
Texte, werden in fast allen Programmen heutzutage reichlich verwendet
und ebenso reichlich werden die darin enthaltenen Chars nach dem
Nullchar durchkämmt - immer wieder und immer wieder und zwar zur
Laufzeit. Sämtliche hier angeführten Sonderfälle fristen ein
bescheidenes Dasein weit abseits dessen, womit der Mainstream an
Programmen die vorhandene Rechenleistung vergeudet. Ja, wir benutzen C
auch weiterhin, weil es sich inzwischen zur Monokultur entwickelt hat
und für die meisten Einsatzfälle keine wirkliche Alternative existiert.
Und so werden auch dieses Jahr wieder massenweise Programmier-Eleven auf
ihre Nasen fallen und mittels C schlechte Programme schreiben.
Aber das alles heißt noch lange nicht, diese Krücke auch noch schön zu
finden oder sich zu bemühen, sie sich schönzusaufen..
Apropos: in ein paar Tagen ist mal wieder Weinmesse in Berlin. Ich
meine, das ist zur Abwechslung mal ein angenehmeres Thema als sich über
krötige Details von C zu streiten.
Prost
W.S.
W.S. schrieb:> Um es nochmal auf den Punkt zu bringen: Texte aller Art, auch konstante> Texte, werden in fast allen Programmen heutzutage reichlich verwendet> und ebenso reichlich werden die darin enthaltenen Chars nach dem> Nullchar durchkämmt - immer wieder und immer wieder und zwar zur> Laufzeit.
Um es mal auf den Punkt zu bringen: Na und?
Oliver
Oliver schrieb:>> und ebenso reichlich werden die darin enthaltenen Chars nach dem>> Nullchar durchkämmt - immer wieder und immer wieder und zwar zur>> Laufzeit.>> Um es mal auf den Punkt zu bringen: Na und?
Aufwand und Zeit. Es ist schon vorteilhaft, vorher zu wissen, wie lang
ein String ist. Zumindest wenn der Prozessor deutlich aufwändiger ist
als ein AVR oder ARM7.
A. K. schrieb:> Aufwand und Zeit. Es ist schon vorteilhaft, vorher zu wissen, wie lang> ein String ist. Zumindest wenn der Prozessor deutlich aufwändiger ist> als ein AVR oder ARM7.
Ach je, und das bei einer Sprache, die für ihren gringen Overhead und
die Systemnähe bekannt ist.
Ja, die "Nicht-Strings" in C sind unbestritten nicht perfekt, und bei
der Programmierung fehleranfällig. Das ist deren größtes Problem.
Aber ob da nun in den Bibliotheken der Stringfunktionen auf Null am Ende
gerüft, oder mit bekannter Länge gearbeitet wird, ist sowas von egal,
egaler geht's nicht.
Oliver
W.S. schrieb:> Ach Jörg, du hast manchmal ne ausgesprochen rechthaberische Art drauf.> Ja, ich habe den gesamten Kontext gelesen, habe ihn jedoch zwecks> Platzersparnis nicht komplett zitiert - den restlichen Platz brauch ich> üblicherweise für meinen eigenen Text...
Dann zitiere ich ihn nochmal:
===========================================================
>> Auch ein optimierter Vorgang wird das Nullbyte nicht "erraten" können> Doch, bei einem Stringliteral schon.
===========================================================
Weil danach eine Rückfrage kam, wie das denn funktionieren sollte, hatte
ich ein Beispiel geliefert.
Um nicht mehr und nicht weniger als genau diese Aussage handelte sich
das Beispiel. Nirgends habe ich dabei eine Wertung abgegeben, ob
vielleicht eine andere Implementierung praktischer gewesen wäre oder
nicht, ob ich die in C so eingebürgerte gut finde oder nicht oder
etwas dergleichen.
Von mir aus hätte die Welt auch durch einen Pascal-Nachfolger gerettet
werden dürfen, ich habe zu CP/M-Zeiten viel damit gemacht und fand es
(in seiner Inkarnation als Turbo-Pascal) so schlecht nicht. Aber es
hat sich eben nicht so ergeben, also lebe ich mit dem Resultat, wie es
ist.
Davon abgesehen: wenn wir heute alle in einem Pascal-Dialekt arbeiten
würden, ich bin mir sicher, es hätte auch einen Obfuscated Pascal
Contest gegeben und eine Sammlung von diversen Dingen, die dort nicht
so ganz optimal gelaufen sind. Das fängt ja schon mit der etwas
ungeschickten Operatorenrangordnung an, die es einem nicht gestattet,
dass man schreibt:
1
if a > 3 and a < 10 then
2
begin
3
{ ... }
4
end;
sondern man muss dort zwingend klammern, denn das "and" bindet stärker
als die Vergleiche.
Wäre es nicht mal Zeit einen C-Standard zu entwerfen (oder C ähnliche
Sprache), die diese ganzen Fallstricke und Designfehler mal behebt?
Kompatibilität ist gut, aber deswegen Fehler Jahrzehnte lang
herumschleppen nicht.
TriHexagon schrieb:> Wäre es nicht mal Zeit einen C-Standard zu entwerfen (oder C ähnliche> Sprache), die diese ganzen Fallstricke und Designfehler mal behebt?> Kompatibilität ist gut, aber deswegen Fehler Jahrzehnte lang> herumschleppen nicht.
Programmiersprachen gibt's wie Sand am Meer, auch C ähnliche Sprachen. C
ist aber nun mal sowas wie ein de facto Standard. Welche andere Sprache
ist für praktisch jeden x-beliebigen Prozessor verfügbar?
Mark Brandis schrieb:> Welche andere Sprache> ist für praktisch jeden x-beliebigen Prozessor verfügbar?
Nicht nur das. Man kann wirklich alles damit machen. Ich brauche
nichts anderes (ok, ab und zu mal Assembler). Es gibt sehr brauchbare
Libs (zB SQLite) uvam.
Mark Brandis schrieb:> Programmiersprachen gibt's wie Sand am Meer, auch C ähnliche Sprachen.
D beispielsweise. Verbreitung? Eher gering.
Am ehesten sind es dann noch Scriptsprachen wie Python, Perl oder Ruby,
die sich als Alternativen durchsetzen konnten.
Die vorhandenen C ähnlichen Sprachen unterscheiden sich aber grundlegend
vom Konzept. Nur C ist nativ, einfach (kleiner Sprachumfang) und bemüht
sich um 100% Kontrolle und Freiheit. Nur C++ kommt an weitesten heran,
ist aber komplex. Ansonsten läuft alles auf einer VM (Java, C#...), hat
keine 100% Kontrolle (GCC, keine Zeigerarithmetik, ...) oder ist komplex
(C++).
Es gibt also keine Sprache die C in diesem Bereich Konkurrenz machen
kann (außer C++). Und so gesehen, gibt es keine C ähnliche Sprache, nur
die Syntax ist ähnlich.
TriHexagon schrieb:> Es gibt also keine Sprache die C in diesem Bereich Konkurrenz machen> kann
Wenn du alle an C orientierten Sprachen ausschliesst, weil sie aus guten
Gründen nicht genau wie C sind, dann wirst du auch keine finden. ;-)
A. K. schrieb:> Wenn du alle an C orientierten Sprachen ausschliesst, weil sie aus guten> Gründen nicht genau wie C sind, dann wirst du auch keine finden. ;-)
Da hast du natürlich recht :). Ein C Standard der sich endlich von den
Altlasten befreit, wäre mir aber auch ehrlich gesagt lieber.
Joachim Drechsel schrieb:> Nicht nur das. Man kann wirklich alles damit machen.
Webprogrammierung macht damit bestimmt mächtig Spaß. Auch
Benutzeroberflächen sind die reine Freude.
> Altlasten befreit...
Nur, daß es ohne die Altlasten halt dann kein C mehr ist.
Wenn die Leute nicht mit Zeigern sinnvoll umgehen können,
könnte man ein C ohne Zeiger machen - aber ist es dann C?
Ganz abgesehen davon, daß alle, die schlechte C-Programme
schreiben, auch in anderen Sprachen keine genialen
Programme machen.
Ihre Fehler fallen dann nur später auf ...
Klaus Wachtler schrieb:>> Altlasten befreit...>> Nur, daß es ohne die Altlasten halt dann kein C mehr ist.> Wenn die Leute nicht mit Zeigern sinnvoll umgehen können,> könnte man ein C ohne Zeiger machen - aber ist es dann C?
Nein das verstehst du falsch. Mit Altlasten meine ich nicht Zeiger etc..
C soll weiterhin seinem Konzept treu bleiben. Aber es gibt in C einige
Fallstricke (z.B. Operatorpriorität) und Designfehler (z.B. umstrittene
break Geschichte in switch case), die nur noch aus kompatibilitäts
Gründen bestehen. Ein weiteres Beispiel wäre, dass ein Stringliteral
nicht const char* ist. Soll der Typ, der seinen 30 Jahren alten Code
kompilieren möchte, doch einfach mit jenem Standard kompilieren.
TriHexagon schrieb:> Aber es gibt in C einige Fallstricke (z.B. Operatorpriorität)
Die unglücklich gewählte Vorrangreglen, die die Vergleichsoperatoren
(==, > usw.) Vorrang über die Bit-Operatoren (&, | und ^) stellen, sind
ein schönes Beispiel dafür, wie sich solche Fehlentscheidungen manchmal
über Jahrzehnte festsetzen, ohne dass es jemand wagt, etwas daran zu
ändern.
Die ursprüngliche Entscheidung in C (1972) war ja noch wenigstens ein
Bisschen nachvollziehbar:
http://www.lysator.liu.se/c/dmr-on-or.html
C++ und Objective-C (1983) sollten weitgehend abwärtskompatibel zu C
werden, weswegen hier die Fehlentscheidung übernommen werden musste.
Spätestens bei der Entwicklung von Java (1995), das weder zu C noch zu
C++ abwärtskompatibel ist, hätte diesbezüglich aufgräumt werden können.
Die nächste Chance ist bei der Entwicklung von C# (2000) verpasst
worden, aber selbst D (2001) verwendet noch die Vorrangregeln von C.
Von den C-ähnlichen Programmiersprachen hat erst Go (2009) mit dieser
Tradition gebrochen.
37 Jahre hat's also gedauert :)
TriHexagon schrieb:> Ein weiteres Beispiel wäre, dass ein Stringliteral> nicht const char* ist.
Es gibt noch mehr... z.B.:
* Integer-Literale ohne Typangabe (ala "7") sind immer mindestens "int"
groß, auch wenn sie in kleinere Typen passen würden.
* Das Ergebnis eines beliebigen arithmetischen Operators ist ebenfalls
immer mindestens ein int; ((char) 7) ist vom Typ char, +((char) 7) ist
int...
* Die Funktion "void foo ()" nimmt beliebig viele Parameter; das braucht
man quasi gar nicht mehr und verursacht nur Probleme
* keine Funktions-Überladungen
etc...
Kindergärtner schrieb:> Ach ja, C++ ist schön:> std::string myString = "Cheese!";> for (char c : myString) {> tuwas(c);> }
Aber auch noch nicht so lange. Davor sah das alles andere als schön aus:
Dr. Sommer schrieb:> Es gibt noch mehr... z.B.:> * Integer-Literale ohne Typangabe (ala "7") sind immer mindestens "int"> groß, auch wenn sie in kleinere Typen passen würden.> * Das Ergebnis eines beliebigen arithmetischen Operators ist ebenfalls> immer mindestens ein int; ((char) 7) ist vom Typ char, +((char) 7) ist> int...
Ich sehe da kein Problem drin.
> * Die Funktion "void foo ()" nimmt beliebig viele Parameter; das braucht> man quasi gar nicht mehr und verursacht nur Probleme
Das wurde in C99 zwar für obsolet erklärt, aber immer noch unterstützt.
> * keine Funktions-Überladungen
Die würden bei jedem Compiler, der das unterstützen soll, eine
ABI-Änderung erzwingen, und damit wären sämtliche Bibliotheken nicht
mehr binärkompatibel.
Rolf Magnus schrieb:> Aber auch noch nicht so lange. Davor sah das alles andere als schön aus:
Das stimmt. aber jetzt gehts.
> Ich sehe da kein Problem drin.
Ich schon - C++ musste diese Regeln wohl oder übel erben, und daher
ergeben sich dort lustige Überraschungen:
1
std::cout<<'0'<<" "<<(+'0');
Das überträgt sich dann auch auf andere Funktionen die sich Aufgrund des
Paramtertyps unterscheiden:
1
voidtest(int){}
2
voidtest(char){}
3
4
intmain(){
5
test('0');// Aufruf von test(char)
6
test(+'0');// Aufruf von test(int)
7
}
> Das wurde in C99 zwar für obsolet erklärt, aber immer noch unterstützt.
und generiert daher immer noch Verwirrung.
>> * keine Funktions-Überladungen>> Die würden bei jedem Compiler, der das unterstützen soll, eine> ABI-Änderung erzwingen, und damit wären sämtliche Bibliotheken nicht> mehr binärkompatibel.
Ja sowas nachträglich zu ändern ist natürlich schwierig; man hätte es
halt gleich richtg machen sollen...
Dr. Sommer schrieb:> Ja sowas nachträglich zu ändern ist natürlich schwierig; man hätte es> halt gleich richtg machen sollen...
Das ist leicht hingesagt, wenn der Compiler in einige zig Kilo(!)bytes
passen soll und manche Plattformen externe Namen auf 6-8 Zeichen
begrenzen.
A. K. schrieb:> Das ist leicht hingesagt, wenn der Compiler in einige zig Kilo(!)bytes> passen soll und manche Plattformen externe Namen auf 6-8 Zeichen> begrenzen.
Dann eben als optionales Feature mit standardisierten Symbolnamen, damit
die "größeren" Compiler das kompatibel hätten machen können...
Alternativ ohne die Namensfummelei und stattdessen mit
Binärinformationen. Naja egal, ist ein paar Jahre zu spät jetzt :)
Naja, was soll's: wer Überladung haben will, nimmt halt C++ und lässt
den Rest davon weg, wenn er sich dran stört.
Einfach statt *.c *.cpp schreiben, und schon fertig.
Klaus Wachtler schrieb:> Naja, was soll's: wer Überladung haben will, nimmt halt C++ und lässt> den Rest davon weg, wenn er sich dran stört.> Einfach statt *.c *.cpp schreiben, und schon fertig.
Das versuch ich den Leuten auch immer zu sagen wenn sie C verwenden und
ein bestimmtes C++ Feature ihre Probleme lösen würde; aber dann kommen
immer so Antworten dass C++ grundsätzlich ineffizient und zu kompliziert
sei und so was komisches :-/
A. K. schrieb:> Google Go hat ein paar nette Aspekte.
In der Tat hat es die.
Noch eine kleine Anmerkung:
Es ist insbesondere gegenüber den zahlreichen Nicht-Google-Mitarbeitern,
die an Go, den Implementierungen und der Standardbibliothek mitarbeiten
nicht sonderlich nett, wenn die Sprache ständig als 'Google Go'
bezeichnet wird. Go ist, im Gegensatz zu etwa Android, kein
Google-Projekt und den Unterschied merkt man auch.
Rob Pike, Ken Thompson und Robert Griesemer arbeiteten bei Google als
sie mit der Entwicklung der Sprache begonnen haben. Viele Go-Entwickler
arbeiten bei Google. Trotzdem ist die Sprache kein Google-Produkt.
Weiß auch nicht, woher das immer kommt. Auch vor der Standardisierung
hat doch niemand C als 'Bell Labs C' bezeichnet, nur weil Dennis Ritchie
dort gearbeitet hat.
In Anbetracht dessen, dass es sogar Bücher zu 'Google Go'(*) gibt, ist
dir da sicher kein Vorwurf zu machen. Dennoch wollte ich es mal
anmerken.
(*) die übrigens ähnlich "gut" sind, wie die ganzen "C von A-Z in 24
Stunden"-Dinger
Dr. Sommer schrieb:>
1
voidtest(int){}
2
>voidtest(char){}
3
>
4
>intmain(){
5
>test('0');// Aufruf von test(char)
6
>test(+'0');// Aufruf von test(int)
7
>}
Ist das nicht eher ein Beispiel dafür, dass das Überladen von Funktionen
der Code-Lesbarkeit nicht unbedingt zuträglich ist? Gerade wenn dann wie
im Fall von C++ auch noch benutzerdefinierte Typkonvertierungsoperatoren
ins Spiel kommen? Ebenso finde ich es schön, wenn eine Funktion 'f' auch
tatsächlich dem Symbol 'f' (meinetwegen <namespace>.f) im generierten
Objektcode entspricht und nicht bis zur Unkenntlichkeit gemanglet wurde.
working directory schrieb:> Dr. Sommer schrieb:> Ist das nicht eher ein Beispiel dafür, dass das Überladen von Funktionen> der Code-Lesbarkeit nicht unbedingt zuträglich ist?
Der Fehler liegt hier nicht in der Überladung, sondern an der
sinnlosen Integer Konvertierung an dieser Stelle. Überladungen sind
etwas sehr sinnvolles das in vielen Sprachen vorhanden ist; selbst in
der minimalistischen Anfänger-Sprache Java.
> Gerade wenn dann wie> im Fall von C++ auch noch benutzerdefinierte Typkonvertierungsoperatoren> ins Spiel kommen?
Man muss bei deren Definition eben aufpassen, und nur sinnvolle solche
Operatoren definieren. Wenn man alles in alles konvertieren lässt hat
man was falsch gemacht.
> Ebenso finde ich es schön, wenn eine Funktion 'f' auch> tatsächlich dem Symbol 'f' (meinetwegen <namespace>.f) im generierten> Objektcode entspricht und nicht bis zur Unkenntlichkeit gemanglet wurde.
Das findet der Linker aber nicht.
Wenn du mit objdump GCC-erzeugten Code disassemblisierst, kannst du "-C"
angeben, um statt "_ZN7CANopen4RPDO9onReceiveEN5STM323CAN6RXFifoE" im
Ausgabe-Code "CANopen::RPDO::onReceive(STM32::CAN::RXFifo)" zu erhalten;
das ist doch ganz lesbar.
Dr. Sommer schrieb:> working directory schrieb:>> Dr. Sommer schrieb:>> Ist das nicht eher ein Beispiel dafür, dass das Überladen von Funktionen>> der Code-Lesbarkeit nicht unbedingt zuträglich ist?> Der Fehler liegt hier nicht in der Überladung, sondern an der> sinnlosen Integer Konvertierung an dieser Stelle. Überladungen sind> etwas sehr sinnvolles das in vielen Sprachen vorhanden ist; selbst in> der minimalistischen Anfänger-Sprache Java.
Ich würde sagen, das Problem liegt eher darin, dass man in C und C++ mit
Zeichen rechnen kann. Ein Zeichen ist nun einmal etwas völlig anderes
als eine Integer-Zahl. Ausdrücke wie
1
'a'+'b'
2
3*'a'
3
'a'/5
4
-'a'
und damit eben auch +'a' sind doch völliger Unfug. Ich kenne auch keine
nicht von C abgeleitete Sprache, in der diese Form von Arithmetik
erlaubt wäre.
Halbwegs sinnvoll sind in meinen Augen allenfalls folgende arithmetische
Operationen:
1
char+int->char
2
int+char->char
3
char-char->int
also bspw.
1
intn;
2
charziffer;
3
4
n=3;
5
ziffer='0'+n;
6
7
ziffer='8';
8
n=ziffer-'0';
Bei der Zeigerarithmetik gibt es entsprechende Einschränkungen, warum
also nicht auch bei den Zeichen? Als Integer-Datentyp für gewöhnliche
Berechnungen hätte man zusätzlich zu "long" und "int" noch das "byte"
einführen können.
Auf der anderen Seite:
Wer kommt schon auf die Idee,
Yalu X. schrieb:> Halbwegs sinnvoll sind in meinen Augen allenfalls folgende arithmetische> Operationen:
Auch davon wäre im Sinn dieser Betrachtungen abzuraten, weil es zu sehr
suggeriert, dass man mit 'I'+1 zu 'J' kommt. Mit expliziten
Konvertierungen wird man da deutlicher drauf gestossen.
working directory schrieb:> Weiß auch nicht, woher das immer kommt. Auch vor der Standardisierung> hat doch niemand C als 'Bell Labs C' bezeichnet, nur weil Dennis Ritchie> dort gearbeitet hat.
Möglicherweise hängt das damit zusammen, dass es vor Go schon eine
andere Programmiersprache mit fast gleichem Namen gegeben hat:
http://en.wikipedia.org/wiki/Go!_%28programming_language%29
Go! kennt zwar fast keiner (ich selber kenne es auch nur aus dem
entsprechenden Hinweis im Wikipedia-Artikel zu Go), aber es scheint
damals bei der Namensgebung von Go eine heftige Diskussion zwischen
dem Go!-Entwickler und Google gegeben zu haben.
A. K. schrieb:> Yalu X. schrieb:>> Halbwegs sinnvoll sind in meinen Augen allenfalls folgende arithmetische>> Operationen:>> Auch davon wäre im Sinn dieser Betrachtungen abzuraten, weil es zu sehr> suggeriert, dass man mit 'I'+1 zu 'J' kommt.
Richtig. Deswegen habe ich auch "halbwegs sinnvoll" geschrieben :)
Yalu X. schrieb:> Ich würde sagen, das Problem liegt eher darin, dass man in C und C++ mit> Zeichen rechnen kann.
Das löst aber nicht das gezeigte Problem aus. Wenn man damit nicht
rechnen könnte müsste man halt ständig casten, denn Eingaben aus Dateien
sind immer "char"...
und ich bleibe dabei, dass das Ursache für das gezeigte Problem die
komischen C-Regeln für Operatoren sind; warum muss die Summe zweier
8bit-Zahlen immer 32bit sein, während die Summe zweier 32bit-Zahlen
nur 32bit ist (auf x86 mit char=8bit, int=32bit)? Wäre es nicht viel
sinnvoller wenn das Ergebnis (im 1. Fall) wieder 8bit wäre? Wenn man
einen Overflow erwartet müsste man halt vorher casten - genauso wie man
das bei Datentypen >= int (zB 32bit) muss.
Ja, ändern kann man das jetzt nicht mehr, aber die Regeln sind trotzdem
bescheuert und sorgen für Überraschungen, wenn der Typ eines Ausdrucks
eine Rolle spielt (wie eben bei Überladungen, Template-Type-Inference
wie sehr oft von der Standard Library verwendet, decltype, auto).
Dr. Sommer schrieb:> warum muss die Summe zweier 8bit-Zahlen immer 32bit sein
Muss sie nicht. Sie muss mindestens 16 Bits sein, weil das die
offizielle untere Grenze von "int" ist.
Warum? Vermutlich weil die PDP-11 so arbeitete. Deren 8-Bit Operationen
arbeiteten mit impliziter Vorzeichenerweiterung auf 16 Bits. Und es
waren hauptsächlich PDP-11 Rechner, auf denen C und Unix anfangs
verbreitet wurde. Deshalb sind ja auch chars mit Vorzeichen recht
verbreitet, eine eigentlich recht bizarre Idee.
Ritche hatte sicherlich nicht geplant, dass diese bewusst einfach
konzipierte Sprache über Jahrzehnte zur dominanten Programmiersprache
für viele Anwendungsbereiche wird.
Dr. Sommer schrieb:> Yalu X. schrieb:>> Ich würde sagen, das Problem liegt eher darin, dass man in C und C++ mit>> Zeichen rechnen kann.> Das löst aber nicht das gezeigte Problem aus. Wenn man damit nicht> rechnen könnte müsste man halt ständig casten, denn Eingaben aus Dateien> sind immer "char"...
Deinen Einwand verstehe ich jetzt nicht ganz. Wenn Zeichen und Zahlen
grundsätzlich unterschiedliche Datentypen wären, gäbe es doch auch beim
Aufruf überladener Funktionen keine Verwechslungsmöglichkeit mehr?
> warum muss die Summe zweier 8bit-Zahlen immer 32bit sein, während die> Summe zweier 32bit-Zahlen nur 32bit ist (auf x86 mit char=8bit,> int=32bit)? Wäre es nicht viel sinnvoller wenn das Ergebnis (im 1.> Fall) wieder 8bit wäre?
Ja, da stimme ich dir zu. Man hätte sich dadurch die ganzen – nicht ganz
leicht zu merkenden – Integer-Promotion-Rules sparen können. Aber den
Grund für diese Regel hat A.K. ja schon genannt.
Trotzdem: Wer sollte auf die Idee kommen, +'0' statt '0' zu schreiben?
Oder anders ausgedrückt: Mit dem Voranstellen eines Vorzeichens teilt
der Programmier dem Compiler doch mit, dass er in diesem Fall das
Zeichen als eine Zahl intepretiert haben möchte. Deswegen ist es nur
konsequent, wenn von den beiden alternativen Implementationen von "test"
diejenige genommen wird, die eine Zahl als Argument erwartet.
Yalu X. schrieb:> Deinen Einwand verstehe ich jetzt nicht ganz. Wenn Zeichen und Zahlen> grundsätzlich unterschiedliche Datentypen wären, gäbe es doch auch beim> Aufruf überladener Funktionen keine Verwechslungsmöglichkeit mehr?
Und man müsste sich bei Zeichen nicht mit den 3 verschiedenen Datentypen
char
signed char
unsigned char
rumärgern, die grad bei Überladung stets für grosse Freude sorgen.
A. K. schrieb:> Muss sie nicht. Sie muss mindestens 16 Bits sein, weil das die> offizielle untere Grenze von "int" ist.
Lies doch was ich schreibe; da steht x86 als Beispiel.
Yalu X. schrieb:> Deinen Einwand verstehe ich jetzt nicht ganz. Wenn Zeichen und Zahlen> grundsätzlich unterschiedliche Datentypen wären, gäbe es doch auch beim> Aufruf überladener Funktionen keine Verwechslungsmöglichkeit mehr?
Okay, mein Beispiel war missverständlich, mit "char" meinte ich halt
"einen kleinen integer", nicht "ein zeichen", und mit '0' einen
Integer diesen Typs. Ich wollte verdeutlichen dass der Rückgabetyp von
+x überraschenderweise "int" ist, (u.a.) wenn x ein "char" ist; somit
hat man seltsamerweise eben int-Ergebnisse aus einer char-Rechnung,
wodurch unbeabsichtigt z.B. falsche Overloads aufgerufen werden.
Yalu X. schrieb:> Trotzdem: Wer sollte auf die Idee kommen, +'0' statt '0' zu schreiben?
Ja direkt schreibt das natürlich keiner! Leider gibt es genug Code der
+x enthält als "kurznotation" für "cast nach int", aber man könnte ja
auch aus Versehen dinge schreiben wie;
1
voidtest(shortx){test2(x+short{7});}
- und hier wird test2 mit einem int aufgerufen, nicht mit short
(was eben Probleme gibt wenn test2 Überladungen für int und short hat,
oder ein Template ist); korrekt wäre
Yalu X. schrieb:> Ich würde sagen, das Problem liegt eher darin, dass man in C und C++ mit> Zeichen rechnen kann.
Richtig. Das sehe ich genauso. Wenn char genau gleich definiert wäre,
wie jetzt, außer daß man damit nicht rechnen könnte, dann würde kein
Mensch sich dafür interessieren, ob das jetzt signed oder unsigned ist.
Wenn man mal irgendwo rechnen muß, müßte man halt in einen dafür
sinnvollen Typ konvertieren. Das ist an sich ja heute schon so, nur
wollen das viele nicht einsehen. Sie wollen lieber wider besseren Rates
direkt mit char rechnen und fangen daher an, über irgendwelche
Vorzeichen nachzudenken.
> Bei der Zeigerarithmetik gibt es entsprechende Einschränkungen, warum> also nicht auch bei den Zeichen? Als Integer-Datentyp für gewöhnliche> Berechnungen hätte man zusätzlich zu "long" und "int" noch das "byte"> einführen können.
So sehe ich das auch. Den "kleinen Integer" statt char byte nennen und
genau wie die anderen Integertypen behandeln. Dann noch einen separaten
'char', mit dem keine Rechenoperationen erlaubt sind. Wer die will, muß
dafür halt nach unsigned byte konvertieren. Ist das nicht in manchen von
C abgeleiteten Sprachen auch so?
> Auf der anderen Seite:>> Wer kommt schon auf die Idee,> test (+'0');>> zu schreiben?
Das erinnert mich an bool in C++, mit dem man leider auch rechnen kann.
Das führt dann zu so lustigen Stilblüten wie dem Konvertierungs-Operator
nach void* in den iostreams. Eigentlich soll der einen booleschen Wert
zurückliefern, der zeigt, ob der Stream ok ist oder ob EOF erreicht bzw.
ein Fehler aufgetreten ist. Es wird aber eben kein bool mit true und
false zurückgegeben, sondern ein void*, der entweder NULL ist oder
irgendwas anderes. Und das nur, weil man mit void* im Gegensatz zu bool
nicht rechnen kann und es dann einen Fehler bei sowas wie:
1
if(std::cin+3)
gibt.
Dr. Sommer schrieb:> Yalu X. schrieb:>> Ich würde sagen, das Problem liegt eher darin, dass man in C und C++ mit>> Zeichen rechnen kann.> Das löst aber nicht das gezeigte Problem aus. Wenn man damit nicht> rechnen könnte müsste man halt ständig casten, denn Eingaben aus Dateien> sind immer "char"...
Sie sind es bei Stringoperationen wie fgets(). fread(), das eher für
Binärdaten gedacht ist, nutzt einen void*.
> und ich bleibe dabei, dass das Ursache für das gezeigte Problem die> komischen C-Regeln für Operatoren sind; warum muss die Summe zweier> 8bit-Zahlen immer 32bit sein, während die Summe zweier 32bit-Zahlen> nur 32bit ist (auf x86 mit char=8bit, int=32bit)? Wäre es nicht viel> sinnvoller wenn das Ergebnis (im 1. Fall) wieder 8bit wäre?
Was mich eher stört ist, daß z.B. eine Multiplikation int * int nicht
den nächstgrößeren Typ für das Ergebnis nutzt. Die
Multiplikations-Instruktionen der Prozessoren liefern das in der Regel
aber. Wenn ich einen Überlauf verhindern will, muß ich in C erst die
Eingangs-Werte auf den nächsthöheren Typ casten, damit auch das Ergebnis
diesen Typ hat.
A. K. schrieb:> Muss sie nicht. Sie muss mindestens 16 Bits sein, weil das die> offizielle untere Grenze von "int" ist.>> Warum? Vermutlich weil die PDP-11 so arbeitete. Deren 8-Bit Operationen> arbeiteten mit impliziter Vorzeichenerweiterung auf 16 Bits. Und es> waren hauptsächlich PDP-11 Rechner, auf denen C und Unix anfangs> verbreitet wurde.
Das ist auch heute nicht so unüblich. ARMe haben auch keine
8-Bit-Rechenoperationen, sondern arbeiten immer auf 32 Bit. Sie haben
nur für den Speicherzugriff Instruktionen, die ein Byte in ein
32-Bit-Reigster laden. Das entspricht also exakt der in C definierten
impliziten Konvertierung nach int vor der Berechnung.
> Deshalb sind ja auch chars mit Vorzeichen recht verbreitet, eine> eigentlich recht bizarre Idee.
Bizarr ist nicht das Vorzeichen, sondern daß man überhaupt mit dem Typ
für Text rechnen kann.
Dr. Sommer schrieb:> Okay, mein Beispiel war missverständlich, mit "char" meinte ich halt> "einen kleinen integer", nicht "ein zeichen", und mit '0' einen> Integer diesen Typs. Ich wollte verdeutlichen dass der Rückgabetyp von> +x überraschenderweise "int" ist, (u.a.) wenn x ein "char" ist; somit> hat man seltsamerweise eben int-Ergebnisse aus einer char-Rechnung,> wodurch unbeabsichtigt z.B. falsche Overloads aufgerufen werden.
Ok, jetzt weiß ich, worauf du hinaus wolltest.
Ja, die schon nicht ganz einfachen Integer-Promotion-Regeln in
Verbindung mit den noch komplizierteren Regeln für die Anwendung
überladener Funktionen öffnen der Verwirrung Tür und Tor. Ich versuche
deswegen, Überladungen immer klar (also ohne die Anwendung komplizierter
Regeln) unterscheidbar zu halten, also Funktionen, die sich
ausschließlich in den Datentypen numerischer Argumente unterscheiden, zu
vermeiden.
Die Frage ist doch, warum man nicht einen C und C++ Standard etabliert,
der mal ordentlich ausmistet. Weg mit den Designfehlern und weg mit dem
undefinierten Verhalten. Den alten Quellcode lässt sich dann immer noch
mit den alten Standards kompilieren.
Aber davor bin ich Kaiser von China. Deshalb hoffe ich auf Sprachen wie
Go und Rust. Gerade Rust ist interessant, da GC optional(stdlib) und es
RAII mithilfe von Smartpointer unterstützt. Der unique_ptr ist sogar in
der Syntax der Sprache und shared_ptr in der stdlib. Leider ist Rust
noch nicht fertig, die Entwicklung geht allerdings schnell voran. Man
kann schon einiges damit machen, schwer macht es aber die stdlib die
noch ein bisschen verbuggt ist. Man darf sich nur nicht von der Syntax
abschrecken lassen, sie ist neben C auch ein wenig an Lisp, glaube ich,
angelegt.
Rolf Magnus schrieb:> Ist das nicht in manchen von> C abgeleiteten Sprachen auch so?
Jo, Java.
Rolf Magnus schrieb:> Das erinnert mich an bool in C++, mit dem man leider auch rechnen kann.
Nein, kann man nicht. Er wird nur automatisch 'int' konvertiert,
entweder nach 1 oder 0, was oft sehr praktisch ist und einem Mengen an
expliziten Casts erspart; und sich außerdem so ähnlich verhält wie
"C-Booleans" d.h. ein "int" mit den Werten 1 oder 0.
Rolf Magnus schrieb:> Das führt dann zu so lustigen Stilblüten wie dem Konvertierungs-Operator> nach void* in den iostreams.
Dank 'explicit' braucht man das zum Glück seit C++11 nicht mehr, und
siehe da der void* operator ist weg und es gibt jetzt den bool operator:
http://en.cppreference.com/w/cpp/io/basic_ios/operator_boolYalu X. schrieb:> also Funktionen, die sich> ausschließlich in den Datentypen numerischer Argumente unterscheiden, zu> vermeiden.
Selbst Java macht sowas:
http://docs.oracle.com/javase/7/docs/api/java/io/PrintStream.html#println%28boolean%29TriHexagon schrieb:> Die Frage ist doch, warum man nicht einen C und C++ Standard etabliert,> der mal ordentlich ausmistet.
Bei Java hat man zB etwas zu viel ausgemistet, die Sprache kann gar
nichts mehr.
TriHexagon schrieb:> Weg mit den Designfehlern und weg mit dem> undefinierten Verhalten. Den alten Quellcode lässt sich dann immer noch> mit den alten Standards kompilieren.
Und wie soll dann alt-Standard-kompilierte Software mit der neuen
kompatibel sein, wenn zB die Header sich unterschiedlich verhalten?
Hallo,
Verwirrter schrieb:> Kannst Du denn mal erklären was char* bedeuten soll, oder bin ich der> einzige der das nicht kennt?
Ja, da bist Du der Einzige.
Grüße,
Karl
Achja, und:
TriHexagon schrieb:> und weg mit dem undefinierten Verhalten.
D.h. es soll ein bestimmtes Verhalten vorgeschrieben werden, was dann
aber nur auf einer Architektur so ist, s.d. C++ auf anderen nicht mehr
läuft bzw. das Verhalten kompliziert in Software emuliert werden muss?
Nene, das soll Sprachen wie Java vorbehalten sein, C++ ist portabel.
Dr. Sommer schrieb:>> Weg mit den Designfehlern und weg mit dem>> undefinierten Verhalten. Den alten Quellcode lässt sich dann immer noch>> mit den alten Standards kompilieren.> Und wie soll dann alt-Standard-kompilierte Software mit der neuen> kompatibel sein, wenn zB die Header sich unterschiedlich verhalten?
Mhm ok, das habe ich nicht beachtet, Headerdateien sämtlicher
Bibliotheken wären dann natürlich fehlerhaft.
Dr. Sommer schrieb:>> und weg mit dem undefinierten Verhalten.> D.h. es soll ein bestimmtes Verhalten vorgeschrieben werden, was dann> aber nur auf einer Architektur so ist, s.d. C++ auf anderen nicht mehr> läuft bzw. das Verhalten kompliziert in Software emuliert werden muss?> Nene, das soll Sprachen wie Java vorbehalten sein, C++ ist portabel.
Hab jetzt erst gesehen, dass es da eine Unterscheidung zwischen
undefinierten und unspezifiziertes Verhalten gibt. Ich meine dann
natürlich unspezifiziertes Verhalten wie:
1
a=500;
2
function(a*=12,a+=100);
Aber ist auch egal, C und C++ werden wohl so bleiben. Eine andere
Sprache wirds hoffentlich mal richten.
Mark Brandis schrieb:> Inwiefern?
Java hat vieles solches undefiniertes Verhalten nicht, wie eben z.B.
integer over/under -flows und Integer-Größen; auf Plattformen die sich
aber so wie von Java gewünscht nicht verhalten muss die JVM das
Verhalten dann emulieren ( -> ineffizient). Java verwendet zB für
Array-Größen/Indices und alles mögliche andere immer 32bit-Integer, kann
somit die Vorteile von 64bit-Architekturen nicht (vollständig) nutzen
und generiert zusätzlichen Overhead auf zB 16bit-Architekturen. In C++
kann man Code schreiben (was aber nicht ganz einfach ist) der
unabhängig von solchen Unterschieden immer funktioniert und auch keine
unnötigen Performance-Einbußen hat (wie zB die Verwendung von
16bit-Integern für Indices auf AVR).
TriHexagon schrieb:> a = 500;> function(a*=12, a+=100);>> Aber ist auch egal, C und C++ werden wohl so bleiben. Eine andere> Sprache wirds hoffentlich mal richten.
Grad bei so einem Beispiel frage ich mich schon, welchen großen Vorteil
es hätte, wenn das Verhalten in so einem Fall definiert oder
spezifiziert (was auch immer) wäre.
Wer so etwas schreibt, in welcher Sprache auch immer, sollte sowieso in
der Programmiererhölle schmoren, zumindest für einige Zeit....
TriHexagon schrieb:> Die Frage ist doch, warum man nicht einen C und C++ Standard etabliert,> der mal ordentlich ausmistet. Weg mit den Designfehlern und weg mit dem> undefinierten Verhalten. Den alten Quellcode lässt sich dann immer noch> mit den alten Standards kompilieren.
Neue Versionen der ISO-C und -C++-Norm ersetzen immer die alte Version.
Offiziel gibt es die dann quasi gar nicht mehr. Und wenn man so
"ausmistet", wie du es vorschlägst, wäre das Verhalten doch sehr anders,
und es wäre eine neue Sprache, die dann auch einen neuen Namen verdient
(und sei es nur C+=2 ;-) ).
Dr. Sommer schrieb:> Rolf Magnus schrieb:>> Das erinnert mich an bool in C++, mit dem man leider auch rechnen kann.> Nein, kann man nicht. Er wird nur automatisch 'int' konvertiert,
Ja, ok. Das Resultat ist aber das selbe.
> entweder nach 1 oder 0, was oft sehr praktisch ist und einem Mengen an> expliziten Casts erspart;
Ehrlich gesagt fallen mir so auf die Schnelle nicht wirklich "Mengen"
von Fällen ein, wo ich einen Cast bräuchte. Genau genommen fällt mir
kein einziger ein. Hast du mal ein Beispiel?
Dr. Sommer schrieb:> Rolf Magnus schrieb:>> Das führt dann zu so lustigen Stilblüten wie dem Konvertierungs-Operator>> nach void* in den iostreams.> Dank 'explicit' braucht man das zum Glück seit C++11 nicht mehr,
Ja. Warum das davor nur für Konvertierkonstruktoren, aber nicht für
Konvertieroperatoren verfügbar war, hab ich eh nicht verstanden. Ist
wohl einfach nur vergessen worden.
Rolf Magnus schrieb:> Ehrlich gesagt fallen mir so auf die Schnelle nicht wirklich "Mengen"> von Fällen ein, wo ich einen Cast bräuchte. Genau genommen fällt mir> kein einziger ein. Hast du mal ein Beispiel?
Öhm. Ich hatte so diverse in meinem Projekt. Jetzt weiß ich natürlich
auf die schnelle keinen davon auswendig. z.B. aber sowas:
Rolf Magnus schrieb:> und sei es nur C+=2
"C++" ist sowieso der falsche Name!
Das hieße ja, daß man C weiterentwickelt bzw. die Version erhöht, dann
aber doch die alte Version verwendet.
Schlauer wäre "++C" gewesen, dann hätte man die Weiterentwicklung auch
nutzen können und nicht mit C weiterarbeiten müssen.
Klaus Wachtler schrieb:> Das hieße ja, daß man C weiterentwickelt bzw. die Version erhöht, dann> aber doch die alte Version verwendet.
Und? Wenn ich mir den Trend des Forums vor Augen führe, dann ist genau
das passiert. Da hat jemand schon vor Jahrzehnten die Sprache
weiterentwickelt, aber fast alle verwenden immer noch C. ;-)
Dr. Sommer schrieb:> bool enabled => ...> int powerInput = ...> ...> int powerOutput = powerInput * enabled;> oder> bool counterEnabled = ...> int counter = ...> counter += counterEnabled;>> erspart halt den Cast.
Hmm, solchen Code schreibe ich nicht. Einen booleschen Wert benutze ich
auch tatsächlich als solchen und multipliziere oder addiere ihn nicht
mit irgendwas. Ich finde, daß der Code dadurch nicht unbedingt an
Übersichtlichkeit gewinnt. Und daß ein
1
counter+=counterEnabled;
vom Compiler in was signifikant schnelleres umgesetzt wird als
Dr. Sommer schrieb:> Öhm. Ich hatte so diverse in meinem Projekt. Jetzt weiß ich natürlich> auf die schnelle keinen davon auswendig. z.B. aber sowas:
Sowas ist genau das, wofür C so geliebt wird. Man kann wirklich jeden
Unsinn damit anstellen...
Oliver
Rolf Magnus schrieb:> vom Compiler in was signifikant schnelleres umgesetzt wird als
Das sowieso nicht, aber es ist halt kürzer, und wenn man von den
Konvertierungsregeln weiß versteht man es auch.
Oliver S. schrieb:> Sowas ist genau das, wofür C so geliebt wird. Man kann wirklich jeden> Unsinn damit anstellen...
zB seinen Job sichern indem man verhindert dass ein dahergelaufener
Java-Programmierer den Code versteht :P außerdem gehts um C++.
Bei solchem Code sollte man sich vor dem Einsatz auf lausigen
Mikrocontrollern vergewissern, dass der Compiler tatsächlich die
Multiplikation wegoptimiert:
1
intpowerOutput=powerInput*enabled;
Der AVR-GCC tut dies nämlich nicht und halst einen ATtiny unnötigerweise
eine vollständige 16-Bit-Multiplikation in Software auf.
Folgendes ist auch nicht viel mehr zu tippen und IMHO mindestens genauso
verständlich:
Yalu X. schrieb:> Der AVR-GCC tut dies nämlich nicht und halst einen ATtiny unnötigerweise> eine vollständige 16-Bit-Multiplikation in Software auf.
Ich meine mal, daß dies eine wirlich gerechte Strafe ist für Leute, die
SO programmieren.
> Folgendes ist auch nicht viel mehr zu tippen und IMHO mindestens genauso> verständlich:> int powerOutput = enabled ? powerInput : 0;
Erstens ist es unleserlich, zweitens kann man heutzutage schon drauf
vertrauen, daß ein guter Compiler aus
if (enabled) blablabla; else wasanderes;
einen ordentlichen Code macht und drittens ist sowas die Krätze. Man
sollte die Compiler so umschreiben, daß sie Typangelegenheiten und
Vorbelegungen beim Variablendeklarieren schlichtweg als Fehler
rausschmeißen - um all diesen tollen Programmierern zwangsweise
ordentliches Benehmen beizubringen.
Also eher
int powerOutput;
if (enabled) poweroutput = 1; else poweroutput = 0;
Sowas ist deutlich lesbarer und deutlich besser wartbar.
W.S.
W.S. schrieb:>> int powerOutput = enabled ? powerInput : 0;>> Erstens ist es unleserlich,
Aber höchstens für den, der noch nie ein C-Buch in der Hand hatte und
deswegen nicht weiß was das '?' und der ':' bedeuten. Ich bin auch kein
Freund des ?:-Operators, wenn die drei Operanden komplizierte Ausdrücke
sind, so dass man Anfang und Ende jedes Operators erst mühevoll suchen
muss. Aber hier besteht jeder Operand gerade einmal aus einem einzigen
Symbol, einfacher geht's nun wirklich nicht.
> Man sollte die Compiler so umschreiben, daß sie Typangelegenheiten und> Vorbelegungen beim Variablendeklarieren schlichtweg als Fehler> rausschmeißen - um all diesen tollen Programmierern zwangsweise> ordentliches Benehmen beizubringen.
Wenn man den tollen Programmierern unbedingt Benehmen beibringen möchte,
sollte man eher das Gegenteil von dem tun, was du vorschlägst, nämlich
die Intialisierung bei der Variablendefinition explizit vorschreiben.
Dadurch wird gleich eine ganze Klasse von Fehlern, nämlich die der
uninitialisierten Variablen, vermieden, die oft erst entdeckt werden,
wenn die Software bereits ausgeliefert und im produktiven Einsatz ist.
> int powerOutput;> if (enabled) poweroutput = 1; else poweroutput = 0;
Aber dann bitteschön nicht in einer Zeile, sondern – wie es sich gehört
– in vieren:
1
if(enabled)
2
poweroutput=1;
3
else
4
poweroutput=0;
Die Variante mit '?' und ':' finde ich aber trotzdem besser, da man dort
auf den ersten Blick sieht, dass nur eine einzige Variable (poweroutput)
einen neuen Wert zugewiesen bekommt.
Leicht unleserlich aber garantiert effizient wäre
int powerOutput = -enabled & powerInput;
insbesondere wenn man "enabled" passend negiert speichert. Solange
niemand das Einerkomplementsystem wiedererfindet wäre es wohl auch
sicher.
Yalu X. schrieb:> Wenn man den tollen Programmierern unbedingt Benehmen beibringen möchte,> sollte man eher das Gegenteil von dem tun, was du vorschlägst, nämlich> die Intialisierung bei der Variablendefinition explizit vorschreiben.
Bin ich kein Freund davon. Eine "aus Prinzip" immer vorgenommene
Wertzuweisung kann auch mal einen echten Bug kaschieren, der sonst
als "may be used uninitialized"-Warnung aufgeflogen wäre. Typisches
Beispiel sind geschachtelte Schleifen mit verwechseltem "i" und "j"
oder sowas.
Eine uninitialisierte Verwendung nichtdynamischen Speichers kann wohl im
dritten Jahrtausend hoffentlich jeder Compiler anmosern, genauso wie
das typisch verwechselte "=" mit "==". Die zuweilen propagierte Form
1
if(KONSTANTE==variable)...
empfinde ich jedenfalls als außerordentlich schwer zu lesen, da unser
natürlicher Lesefluss eben eine Variable auf ihren Inhalt vergleichen
mag, nicht einen Inhalt auf eine Variable.
Jörg Wunsch schrieb:> Bin ich kein Freund davon. Eine "aus Prinzip" immer vorgenommene> Wertzuweisung
Man sollte die variable eben erst genau dann definieren, wenn man auch
einen wert für sie hat. So kann man nie auf eine wert-"lose" variable
zugreifen. OOP-Sprachen treiben das sogar noch weiter indem sie durch
konstruktoren eine automatische vorbelegung ermöglichen/erzwingen (wenn
argumente verlangt).
Und wenn man gerade dabei ist definiert man die Variablen in den
kleinsten&innersten Scope wie möglich, denn dann sieht man viel besser
aud was die Variable Einfluss hat und was man mit ihr machen darf.
Die Zeiten wo alle benötigten Variablen an den Anfang einer Funktion
geschrieben werden mussten sind lang vorbei.
> Man sollte die variable eben erst genau dann definieren, wenn man auch> einen wert für sie hat.
Geht nicht immer:
1
booldings(int&a){
2
// Liefer mir einen Wert für a
3
a=42;
4
returnfalse;
5
}
6
7
intmain(intargc,char*[]argv)
8
{
9
inta;
10
if(dings(a)){
11
tu_was_mit(a);
12
}
13
}
Jeder Initialwert bei der Definition von a wäre zwangsläufig ein
Dummywert, der verhindern würde, dass man eine ordentliche
Compilerwarnung bekommt, wenn man im Kontrollfluss den Aufruf von dings
überspringt.
der mechatroniker schrieb:> Geht nicht immer:
Richtig. Aber oft genug.
> bool dings(int &a) {> // Liefer mir einen Wert für a> a = 42;> return false;> }
Wer so codet hat eh was falsch gemacht.
Oliver S. schrieb:> Dr. Sommer schrieb:>> <C++ Vodoo>>> Oliver S. schrieb:>> Sowas ist genau das, wofür C so geliebt wird. Man kann wirklich jeden>> Unsinn damit anstellen...
Das, was du als "C++ Voodoo" bezeichnest, ist kein Unsinn, sondern sehr
saubere Programmierung (klar definierte Funktionen ohne versteckte
Nebeneffekte). Leider sind C++ und erst recht C zu wenig ausdrucksstark,
um solche Dinge elegant umzusetzen.
In anderen Sprachen, bspw. Python, geht das sehr viel übersichtlicher.
Hier ist Dr. Sommers Beispiel mit dem "pair" als Funktionswert:
Yalu X. schrieb:> Das, was du als "C++ Voodoo" bezeichnest, ist kein Unsinn, sondern sehr> saubere Programmierung (klar definierte Funktionen ohne versteckte> Nebeneffekte). Leider sind C++ und erst recht C zu wenig ausdrucksstark,> um solche Dinge elegant umzusetzen.
Das das Unsinn ist, habe ich ja nie behauptet. Mir ging es um was
anderes.
Dieser thread begann mal mit einer Frage zu
> char* str = "abc";
Das, was ich als C++-Vodoo bezeichnet habe, ist davon so weit entfernt
wie der Pluto von der Sonne. Und trotzdem ist das alles C++.
Alles kann, nichts muß...
Oliver
Kindergärtner schrieb:> std::string myString = "Cheese!";> for (char c : myString) {> tuwas(c);> }
Das finde ich prima. Kann mir jemand sagen seit wann (welcher Standard)
das unterstützt wird? Gibt es ein gutes Buch in dem solche Sachen
erklärt sind? (in meinem Stroustrup nix gefunden, vielleicht zu alt)
Dr. Sommer schrieb:> bool enabled = ...> int powerInput = ...> ...> int powerOutput = powerInput * enabled;oderbool counterEnabled = ...> int counter = ...> counter += counterEnabled;erspart halt den Cast.
Wenn mir jemand sowas in den Code hunzen würde, dem würde ich solange
Bücher wie "writing solid code" oder "Clean code" um die Ohren hauen bis
er den Inhalt auswendig weis.
Dr. Sommer schrieb:> B seinen Job sichern indem man verhindert dass ein dahergelaufener> Java-Programmierer den Code versteht :P
Wenn du zu solchem Schweinkram greifen musst damit du deinen Job
behältst tust du mir leid. Bei uns wäre sowas eher ein Kündigungsgrund.
Der hergelaufene Java-Programmierer ist dir dann zumindest im Bezug auf
les- und wartbaren Code schreiben Lichtjahre voraus.
Yalu X. schrieb:> sondern sehr> saubere Programmierung (klar definierte Funktionen ohne versteckte> Nebeneffekte)
Wow, der erste der das hier von meinem Code sagt.
Yalu X. schrieb:> In anderen Sprachen, bspw. Python
Python ist aber dynamisch typisiert... Mit ein "bisschen"
impliziter-konvertierungs-magic könnte man es vielleicht so wie dein 2.
Beispiel in C++ hinbekommen. Beides geht aber interessanterweise in der
ebenfalls streng statisch typisierten Sprache Scala.
lalala schrieb:> Das finde ich prima. Kann mir jemand sagen seit wann (welcher Standard)> das unterstützt wird?
C++11.
> Gibt es ein gutes Buch in dem solche Sachen> erklärt sind? (in meinem Stroustrup nix gefunden, vielleicht zu alt)
Dann nimm den neuesten Stroustrup... http://www.amazon.de/dp/0321563840Udo Schmitt schrieb:> Dr. Sommer schrieb:> Wenn mir jemand sowas in den Code hunzen würde, dem würde ich solange> Bücher wie "writing solid code" oder "Clean code" um die Ohren hauen bis> er den Inhalt auswendig weis.
Wer sich über sowas aufregt hat auch komische Prioritäten.
> Wenn du zu solchem Schweinkram greifen musst damit du deinen Job> behältst tust du mir leid. Bei uns wäre sowas eher ein Kündigungsgrund.
In der Kündigung steht dann "Büße für den Frevel der impliziten
bool-integer-Konvertierung!"
> Der hergelaufene Java-Programmierer ist dir dann zumindest im Bezug auf> les- und wartbaren Code schreiben Lichtjahre voraus.
Jaa. Nur weil einer Java kann muss er noch lange nicht "wartbaren" Code
schreiben. Mein Lieblingsbeispiel (gesehen in einem realen Projekt):
1
class Base {
2
public int getID () {
3
if (this instanceof Derived1) return 1;
4
else if (this instanceof Derived2) return 2;
5
else if (this instanceof Derived3) return 3;
6
}
7
}
8
class Derived1 {};
9
class Derived2 {};
10
class Derived3 {};
Es gibt doch weitaus ekligere Möglichkeiten hässlichen Code zu
schreiben, insbesondere in Bezug auf die Struktur/Modellierung des
gesamten Codes, als an einer stelle "lokal" das ? : zu sparen. Denn -
die Konvertierung zu "reparieren" erfordert das Ändern einer einzigen
Stelle, sowas wie das da oben zu reparieren schon 4 Stellen.
Dr. Sommer schrieb:> Yalu X. schrieb:>> In anderen Sprachen, bspw. Python> Python ist aber dynamisch typisiert... Mit ein "bisschen"> impliziter-konvertierungs-magic könnte man es vielleicht so wie dein 2.> Beispiel in C++ hinbekommen. Beides geht aber interessanterweise in der> ebenfalls streng statisch typisierten Sprache Scala.
Oder in Haskell :)
Trotz der statischen Typisierung und der strengen Typprüfung braucht man
dort dank Typ-Inferenz die Typdeklarationen nicht einmal explizit
hinschreiben.
Das erste Beispiel sieht von der Struktur her ganz ähnlich wie in Python
aus:
1
dings = (False, 42)
2
3
main = let (ok, wert) = dings
4
in when ok (tuWasMit wert)
Im zweiten Beispiel muss der Funktionswert von dings in einen Maybe-Typ
verpackt werden, damit valide und invalide Ergebnisse den gleichen
Datentyp haben.
Der parametrisierte Typ "Maybe" aus der Standardbibliothek entspricht
dabei ziemlich genau deiner Template-Klasse "optional". Das Verpacken
geschieht mit den Konstruktoren Just (valid) und Nothing (invalid):
1
dings = if bedingung
2
then Nothing
3
else Just 42
4
5
main = let r = dings
6
in when (isJust r) (tuWasMit (fromJust r))
Das "main" in diesem Beispiel kann etwas übersichtlicher auch so
geschrieben werden:
1
main = case dings of
2
Just wert -> tuWasMit wert
3
Nothing -> return ()
Ich finde es irgendwie schon beeindruckend, wie man in Haskell Code
ähnlich elegant wie in Python schreiben kann, dabei aber der Compiler
Fehlerprüfungen vornimmt, die weit über diejenigen von C++ hinausgehen.
Yalu X. schrieb:> Ich finde es irgendwie schon beeindruckend, wie man in Haskell Code> ähnlich elegant wie in Python schreiben kann, dabei aber der Compiler> Fehlerprüfungen vornimmt, die weit über diejenigen von C++ hinausgehen.
Das wird hier wohl möglich gemacht durch Extraction und Return Type
Inferring... Beides Dinge die auch für C++ geplant sind ;-)
Dr. Sommer schrieb:> Das wird hier wohl möglich gemacht durch Extraction und Return Type> Inferring... Beides Dinge die auch für C++ geplant sind ;-)
Meinst du die Erweiterung in C++14
http://en.wikipedia.org/wiki/C%2B%2B1y#Function_return_type_deduction
oder gibt es auch schon Pläne für spätere C++-Standards? Ich konnte dazu
leider nichts finden.
In Haskell kann bspw. bei einem Ausdruck nicht nur von den Typen der
Operanden bzw. der Funktionsargumente auf den Ergebnistyp geschlossen
werden, sondern auch umgekehrt. Das erlaubt es sehr oft, die komplette
Signatur einer Funktion (einschließlich der Argumenttypen) zu bestimmen.
Da aus Gründen der Flexibilität nicht nur fixe Typen, sondern auch
Typklassen unterstützt werden, ist der Algorithmus für die Typinferenz
alles andere als trivial:
http://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system
Ich kann mir nicht vorstellen, dass in C++ auch nur annähernd so weit
gegangen werden kann, da es bspw. implizite Typkonvertierungen und
überladene Funktionen beliebig schwer machen, nachträglich einen
weitreichenden Typinferenzmechanismus aufzusetzen. Denn diese beiden
Features setzen ja voraus, dass man die Operandentypen bereits kennt. Um
diese aber zu ermitteln, müsste man vorher schon wissen, welche
Typkonvertierung bzw. welche Variante einer überladenen Funktion
verwendet wird. Da beißt sich die Katze in den Schwanz.
Ein Erfolg wäre es IMHO aber schon, wenn man sich wenigstens einen
Großteil der hässlichen Template-Argumentlisten ersparen könnte. Schon
die in C++11 implementierte Typinferenz – auch wenn sie noch recht
primitiv ist – hat hier schon einiges zum Positiven gewendet.
Da muss aber noch mehr gehen ;-)
Yalu X. schrieb:> Meinst du die Erweiterung in C++14>> http://en.wikipedia.org/wiki/C%2B%2B1y#Function_re...
Jo.
> Ich kann mir nicht vorstellen, dass in C++ auch nur annähernd so weit> gegangen werden kann
Ja das kann sein.
> Da beißt sich die Katze in den Schwanz.
C++ funktioniert halt anders herum, hauptsächlich von innen nach aussen.
> Da muss aber noch mehr gehen ;-)
Naja das Ziel von C++ ist ja nicht Haskell zu imitieren...
Dr. Sommer schrieb:> Naja das Ziel von C++ ist ja nicht Haskell zu imitieren...
Das nicht, aber:
C++ ist eine sehr mächtige Sprache. Dass es kaum etwas gibt, was nicht
geht, erkennt man am besten, wenn man sich die Boost-Bibliothek(en)
anschaut. Dabei stecken in den meisten dieser Bibliotheken nicht einmal
komplexe Algorithmen, es werden einfach nur Sprachkonstrukte, die in
jedem C++-Buch beschrieben sind, zusammengesteckt.
Trotzdem sind die allerwenigsten C++-Programmierer in der Lage zu
verstehen, wie diese Bibliotheken funktionieren, geschweige denn, etwas
ähnliches selber zu schreiben. Selbst erfahrene C++-Programmierer
brauchen einige Zeit, um so ein Geflecht von Template-Konstrukten
vermischt mit Klassenhierarchien, überladenen Funktion usw. zu
durchschauen.
Schaut man sich hingegen Code vergleichbarer Funktionalität in einer
Python-Bibliothek an, hat man oft innerhalb weniger Minuten ein
Aha-Erlebnis und ist anschließend nicht nur befähigt, sondern auch
motiviert, selber ähnlich coole Dinge zu stricken.
C++ liefert prinzipiell alle Voraussetzungen für die generische
Programmierung. Gute Beispiele für deren Nutzung sind die STL und auch
hier wieder die Boost. Generische Programmierung ist die Basis für die
Wiederverwendbarkeit von Softwaremodulen, weswegen eigentlich jeder
professionelle Programmierer, wo immer möglich, generisch programmieren
sollte.
Leider ist die generische Programmierung in C++ mit nicht unerheblicher
zuätzlicher Schreibarbeit verbunden, macht den Code unübersichtlich und
erschwert das Debuggen. Deswegen werden Dinge meist nur dann generisch
programmiert, wenn zu Beginn der Entwicklung bereits feststeht, dass sie
auch in anderen Projekten verwendet werden sollen. Für den ganzen Rest
gilt: Falls etwas davon irgendwann erneut benötigt wird, wird der Code
kopiert und so lange hingebogen, bis er passt. Weil die erforderlichen
Änderungen aber oft über große Teile des Codes verstreut sind, läuft es
oft genug darauf hinaus, dass dieser neu geschrieben wird.
In Python und Haskell hat jede noch so schnell hingerotzte Funktion
schon eine gewisse Grundgenerizität. Schreibt man bspw. in Python
folgende Funktion, die aus einer Liste von Integer-Zahlen die am
häufigsten vorkommende Zahl ermittelt
1
def haeufigst(werte):
2
[(wert, n)] = Counter(werte).most_common(1)
3
return wert
und benötigt irgendwann später eine Funktion, die das häufigste Zeichen
in einem Textstring ermittelt, stellt man fest, dass man die Funktion
überhaupt nicht anpassen muss. In C++ müsste man zu diesem Zweck die
entsprechende Funktion als Template implementieren, was aber kein Mensch
macht, wenn es dafür keinen dringenden Grund gibt.
Ich finde es schade, dass in einer Sprache, die so viele Möglichkeiten
bietet wie C++, so wenige davon im realen Leben tatsächlich genutzt
werden. Und wenn es doch einmal jemand tut, gilt er gleich als C++-Nerd,
dessen Code man sich lieber nicht anschaut, weil man ihn ja sowieso
nicht verstehen wird. Man kann dieses Unverständnis aber auch niemandem
verübeln, denn C++ ist nun einmal eine sehr komplizierte Sprache.
Ein Weg aus diesem Dilemma besteht darin, dass man sich innerhalb einer
Entwicklergruppe auf eine Untermenge von C++ einigt, die jeder leicht
verstehen kann. Man erkauft sich diesen Ausweg aber mit langfristig
höheren Entwicklungskosten wegen schlechterer Widerverwendbarkeit des
entwickelten Codes.
Aber die (IMHO bessere) Alternative besteht darin, durch die gezielte
Weiterentwicklung der Sprache deren nützliche Features auch den weniger
gewieften Durchschnittsprogrammierern (zu denen ich auch gehöre) näher
zu bringen.
Und ich finde, C++ ist derzeit auf einem ganz guten Weg in diese
Richtung. Folgendes Beispiel zeigt eine generisch programmierte
Schleife, die für einen beliebigen Container-Typ dessen Elemente eines
beliebigen numerischen Typs aufsummiert:
Früher sah das schon etwas gruselig aus:
Und mit etwas syntaktischem Zucker, der ebenfalls von C++11 beigesteuert
wird, sieht das Ganze endlich wie ordentlicher Code aus:
1
for(autox:container)
2
s+=x;
Ich bin davon überzeugt, dass man mit einer erweiterten Typinferenz und
noch etwas mehr Zucker auch viele andere häufig vorkommende Konstrukte
übersichtlicher gestalten kann, so bspw. (wie in den Beispielen von Dr.
Sommer) auch mehrfache Funktionsrückgabewerte und Rückgabewerte, die
optional invalid sein können.
Das meinte ich, als ich oben schrieb:
> Da muss aber noch mehr gehen ;-)
Es wird mehr gehen! C++14 ist ja eher als so eine Art Zwischenversion
gedacht. Dewegen bin ich sehr gespannt, was dessen Nachfolgerversion
(C++2x?) bringen wird :)
Yalu X. schrieb:> Und mit etwas syntaktischem Zucker,...
Hör mal, Yalu, du hast dich da ziemlich verrannt. Glaube bitte NICHT,
daß auch nur eines deiner Beispiele wirklich LESBAR ist. Die schiere
Anzahl von hingetippten Zeichen zu verringern ist kein Beitrag zur
besseren Lesbarkeit, sondern nur das Ansinnen, immer mehr
Hintergrundwissen über implizite Dinge beim Leser vorauszusetzen. Aber
genau DARAN hakt es, wie du ja selbst schriebest: Die meisten C++
Programmierer verstehen nur noch Teilmengen dessen, was in dieser
Sprache formell möglich ist. Sag jetzt nicht, daß diese Programmierer ja
bloß zu blöd seien. Ich hab da einen ganz anderen Standpunkt: Eine
Zwecksprache, also eine Programmiersprache, sollte gefälligst ein
verständliches und möglichst leicht zu gebrauchendes Mittel sein, das
auszudrücken, was man bezwecken will. C und in gesteigertem Maße C++
sind das nicht, siehe diese Diskussion hier.
Der Knackpunkt der Diskussion scheint mir der durchaus abwegige Glaube
zu sein, daß die universelle Wiederverwertbarkeit geschriebenen Codes
das höchste Ziel sei. Das ist es nicht. Was willst du da eigentlich
wiederverwenden? Einen algorithmus, der für einen bestimmten Zweck mit
bestimmten Daten erdacht wurde für einen ganz anderen Zweck mit ganz
anderen Daten? Das läuft darauf hinaus, eine eierlegende Wollmilchsau zu
schreiben und sie später durch aufwendige Parametrisierung an die
verschiedenen Stellen der realen Welt anzupassen. Was du bei der
wiederverwendbaren Sau eingespart hast, geht bei der Parametrisierung
doppelt und dreifach wieder drauf - und du hast den erzeugten Code so
unübersichtlich gemacht, daß er schlecht verständlich und damit schlecht
wartbar und bugträchtig ist.
Kurzum, ich verstehe ja das Bestreben, sich die eigene Arbeit zu
optimieren, aber das stupide Wiederverwendenwollen eines
Quellcodefetzens ist dies nicht, da wäre schon viel eher ein Fundus
wohlkommentierter und gut verständlicher Algorithmen angesagt, die man
in seinem persönlichen Portfolio hat und auch noch nach ein paar Jahren
noch ohne Mühen nachvollziehen kann.
W.S.
Yalu X. schrieb:> Das nicht, aber:
Dem ist eigentlich nichts mehr hinzuzufügen, außer: C++ ist für
"super-effiziente" und dabei abstrakte Programme gedacht, insbesondere
auch Embedded, mit expliziter Speicherverwaltung etc. Das schränkt die
Möglichkeiten der Sprache halt gegenüber z.B. Haskell drastisch ein und
nutzt Tastaturen ab, ist aber für Low-Level Anwendungen mit
deterministischer Speicherverwaltung noch (lange) unersetzbar...
W.S. schrieb:> Yalu X. schrieb:>> Und mit etwas syntaktischem Zucker,...>> Hör mal, Yalu, du hast dich da ziemlich verrannt. Glaube bitte NICHT,> daß auch nur eines deiner Beispiele wirklich LESBAR ist.
Chinesisch ist für mich auch nicht lesbar. Man muss die Sprache
natürlich können. Ich kann zwar kein Haskell aber andere funktionale
Sprachen und konnte Yalu's Beispiele durchaus (halbwegs) lesen.
> Die schiere> Anzahl von hingetippten Zeichen zu verringern ist kein Beitrag zur> besseren Lesbarkeit, sondern nur das Ansinnen, immer mehr> Hintergrundwissen über implizite Dinge beim Leser vorauszusetzen.
Oder den Blick auf die wesentliche Funktion des Codes nicht durch
unnötig viel Syntax zu verstellen?!
> Aber> genau DARAN hakt es, wie du ja selbst schriebest: Die meisten C++> Programmierer verstehen nur noch Teilmengen dessen, was in dieser> Sprache formell möglich ist.
Insbesondere du nicht, wie wir ja schon länger wissen.
> Sag jetzt nicht, daß diese Programmierer ja> bloß zu blöd seien. Ich hab da einen ganz anderen Standpunkt: Eine> Zwecksprache, also eine Programmiersprache, sollte gefälligst ein> verständliches und möglichst leicht zu gebrauchendes Mittel sein, das> auszudrücken, was man bezwecken will. C und in gesteigertem Maße C++> sind das nicht, siehe diese Diskussion hier.
Stimmt prinzipiell, aber gewisse Umgebungen wie Embedded haben gewisse
Anforderungen, und um die alle auszudrücken braucht man C oder C++.
Andererseits kann man in C++ schon eine Menge mehr ausdrücken als zB in
Pascal oder Java...
>> Der Knackpunkt der Diskussion scheint mir der durchaus abwegige Glaube> zu sein, daß die universelle Wiederverwertbarkeit geschriebenen Codes> das höchste Ziel sei. Das ist es nicht.
Das ist deine Meinung.
> Was willst du da eigentlich> wiederverwenden? Einen algorithmus, der für einen bestimmten Zweck mit> bestimmten Daten erdacht wurde für einen ganz anderen Zweck mit ganz> anderen Daten?
Siehe z.B. std::max, std::sort, std::transform, std::copy,
std::partition etc. - Algorithmen die so universal sind, dass sie sogar
in die Sprachdefinition aufgenommen wurden, und dank generischer
Programmierung auf sehr viele Fälle angewendet werden können. Wenn man
die Algorithmen also kennt kann man Anwendungs-Code, der sie verwendet,
viel leichter lesen.
> Das läuft darauf hinaus, eine eierlegende Wollmilchsau zu> schreiben und sie später durch aufwendige Parametrisierung an die> verschiedenen Stellen der realen Welt anzupassen.
Hilfe, std::sort braucht EINEN GANZEN Typ-Parameter! Da benutzen wir
lieber 100 einzelne Algorithmen, gell?
> Was du bei der> wiederverwendbaren Sau eingespart hast, geht bei der Parametrisierung> doppelt und dreifach wieder drauf - und du hast den erzeugten Code so> unübersichtlich gemacht, daß er schlecht verständlich und damit schlecht> wartbar und bugträchtig ist.
Wenn alle deine Versuche, Meta-Programmierung zu betreiben, so enden,
ist das natürlich bedauerlich. Aber das heißt natürlich nicht, dass
andere das nicht richtig könnten, und für die ist C++ (und andere
Meta-Programmierung-fähige Sprachen) ein mächtiges Werkzeug.
> Kurzum, ich verstehe ja das Bestreben, sich die eigene Arbeit zu> optimieren, aber das stupide Wiederverwendenwollen eines> Quellcodefetzens ist dies nicht, da wäre schon viel eher ein Fundus> wohlkommentierter und gut verständlicher Algorithmen angesagt, die man> in seinem persönlichen Portfolio hat und auch noch nach ein paar Jahren> noch ohne Mühen nachvollziehen kann.
Genau, den std::max Algorithmus sollte man sich viel lieber textuell auf
eine Karteikarte schreiben, um ihn dann jedes Mal neu zu verstehen und
in seinen Code für die einzelnen Typen neu zu implementieren.
Machen wir doch das Beispiel. Was findest du schöner:
1x zu programmieren:
1
template<typenameT>
2
Tmax(constT&a,constT&b){
3
returna>b?a:b;
4
}
Und diesen Code immer wieder zu verwenden, oder in jedem einzelnen
Projekt für jeden einzelnen Typ zu schreiben:
floatmax(floata,floatb){returna>b?a:b;}//etc. etc.
Aber Code-Wiederverwendbarkeit ist ja nicht so wichtig, mehr die
Vermeidung ach so schwerer Dinge wie des "template" -Keywords. Und nein,
die C-Schummler-Version
1
#define max(a,b) ((a)>(b)?(a):(b))
ist natürlich nicht gleichwertig, da sie zB bei Ausdrücken mit
Seiteneffekten nicht funktioniert, keinen Scope hat etc.
Das ist halt wie mit Besteck:
Wer zu grobmotorisch ist, sollte lieber stumpfe Messer nehmen zum Essen.
Wer ordentliches Werkzeug will, bevorzugt scharfe Messer.
TriHexagon schrieb:> Die Frage ist doch, warum man nicht einen C und C++ Standard> etabliert,> der mal ordentlich ausmistet. Weg mit den Designfehlern und weg mit dem> undefinierten Verhalten. Den alten Quellcode lässt sich dann immer noch> mit den alten Standards kompilieren.
Es ist doch an Dir, Dir einen eigenen Satz zu definieren und es gibt ja
innerhalb von Firmen auch Standards und Bibs.
Das Problem wie immer: Bringe mal alle Interessen unter einen Hut.