Die Sprache C ist berühmt/berüchtigt für ihre Gemeinheiten.
Eigentlich kann einem ja in C nix passieren, solange man sich streng
an die Logik hält. Aber uneigentlich hat es vermutlich fast jeden C
Programmierer schon mal massiv erwischt. Es geht nicht so wie es soll,
man grübelt tagelang, bis man sich dann irgendwann massiv vor den Kopf
haut...
Wär mal interessant, was andere aus dem Nähkästchen zu plaudern haben.
Hier ist mein Beitrag:
Eine Variable als uint8_t vereinbart. Eigentlich mach ich das fast
immer, hab bisher noch nie eine Variable mit negativem Inhalt benötigt.
Über eines hatte ich allerdings nicht nachgedacht. Wenn der Inhalt der
Variablen 0 ist und es wird 1 subtrahiert... wird eine uint Variable
trotzdem nicht negativ 8o
Micha schrieb
> wird eine uint Variable> trotzdem nicht negativ 8o
dich wundert das eine variable ohne vorzeichen nicht negativ wird? Ich
glaube das ist hier die größte merkwürdigkeit.
Der compieler wird vermutlich sogar das komplette IF entfernen weil es
eh nie war werden kann.
Micha schrieb
> Eine Variable als uint8_t vereinbart. Eigentlich mach ich das fast> immer, hab bisher noch nie eine Variable mit negativem Inhalt benötigt.
Naja... das ist aber an sich kein C-Fallstrick, sondern ein "Nicht
nachgedacht"-Fallstrick und der kann dir in jeder Sprache begegnen :D
Wenn man immer alles als unsigned deklariert ohne darüber nachzudenken
was später überhaupt mit der Variable geschehen soll braucht man sich
nicht wundern :D
Ein Klassiker in C wäre z.B. Zuweisung statt Vergleich (= statt ==).
Micha schrieb
> if (x < 0) blafasel();
Was hat -Wall -Wextra denn dazu gesagt? Sicher sowas wie:
1
foo.c:9:1: warning: comparison is always false due to limited range of data type
Der mit den geschachtelten Schleifen und dem Verwechseln der Indizes
ist da schon gemeiner, aber hat natürlich nicht viel mit "Fallstricke
in C" zu tun.
Ein 'gemeiner' Fallstrick in C ist für mich zb
i = 018;
Die führende 0-en hätte ich vor vielen Jahren gerne gehabt, um
tabellenartige Ausrichtungen eben mit führenden 0-en zu kriegen.
Gemeinerweise ist das dann aber eine Oktalzahl und keine Dezimalzahl.
Karl Heinz Buchegger schrieb:> Ein 'gemeiner' Fallstrick in C ist für mich zb>> i = 018;
Das ist ein Fehler zur Compilezeit, aber kein Fallstrick. Fallstrick
wird dann
Joerg Wolfram schrieb:> funktionsaufruf1(paramter_array[index++],parameter_array[index++]);> Wenn zum Beispiel index=1 ist, entspricht das:> funktionsaufruf(paramter_array[2],parameter_array[1]);
d.h. er setzt zuerst als 2. argument die variable index ein,
inkrementiert sie, setzt diese dann ins erste argument ein,
inkrementiert sie, und am ende ist index=3?
> funktionsaufruf1(paramter_array[index++],parameter_array[index]);> das "gefühlt richtige" Ergebnis liefert, bei index=1 wäre das dann:> funktionsaufruf(paramter_array[1],parameter_array[2]);
und hier macht er es umgekehrt oder wie? zuerst ins 1. argument index
einsetzen, inkrementieren, dann ins 2. einsetzen und das wars? und dann
ist index=2?
was wie wo? warum? hä?
Joerg Wolfram schrieb:> Wenn zum Beispiel index=1 ist, entspricht das:>> funktionsaufruf(paramter_array[2],parameter_array[1]);
Es kann dem entsprechen, es kann aber genausogut anders herum sein.
Auch hier: Compilerwarnungen einfach ansehen:
1
foo.c:6:27: warning: operation on 'index' may be undefined
Ein ähnlicher Fall zu dem schon von Joerg Wolfram genannten:
pc ist ein Zeiger auf einen String in dem Daten stehen die zeichenweise
ausgewertet werden sollen.
char * pc;
if ((pc++ == '0') && (pc++ == '2') {
strcpy(..., pc);
...
}
Funktionierte mit Microsoft C5.x einwandfrei, mit MS-C 6.0 und gleichen
build optionen nicht mehr.
Fehler:
1. Man weiss nicht wann die beiden post-increments passieren, bei MSC
6.0 wurden sie erst nach beiden Vergleichen ausgeführt, so daß beide
Vergleiche mit dem ersten Zeichen gemacht wurden.
2. Wenn der erste Teilvergleich nicht wahr ist, ist der ganze Ausdruck
unwahr und die weitere Auswertung wird abgebrochen, damit fehlt u.U das
2. increment und die weitere Auswertung des Strings ist um ein Zeichen
versetzt.
Der Fehler war nicht von mir, aber ich musste ihn finden. Leider war das
damals die Auswertung der Antwort einer Autorisierungszentrale an einen
Geldautomaten, das ganze hat damals einen Schaden von mehreren 10000 DM
verursacht.
Udo Schmitt schrieb:> Fehler:> 1. Man weiss nicht wann die beiden post-increments passieren,
Doch, weiß man. Der &&-Operator ist ein Sequence-Point, daher ist das
Verhalten sehr wohl definiert.
Udo Schmitt schrieb:> bei MSC> 6.0 wurden sie erst nach beiden Vergleichen ausgeführt, so daß beide> Vergleiche mit dem ersten Zeichen gemacht wurden.
Das ist dann ein Compiler-Fehler.
Udo Schmitt schrieb:> 2. Wenn der erste Teilvergleich nicht wahr ist, ist der ganze Ausdruck> unwahr und die weitere Auswertung wird abgebrochen, damit fehlt u.U das> 2. increment und die weitere Auswertung des Strings ist um ein Zeichen> versetzt.
Nicht "u.U.", sondern "ganz sicher". Mich würde aber nicht wundern, wenn
das durchaus so gewollt war, dass wenn das erste Zeichen nicht passt,
der Pointer auch nur einmal inkrementiert wird. Sonst könnte z.B. das
"02" nicht in "x02" gefunden werden.
Jörg Wunsch schrieb:> Aua. :-O
Jepp, "02" hiess glaube ich "Karte gesperrt, einziehen". Leider wurde 2
mal die erste Ziffer '0' genommen und "00" hies "Alles ok".
Die Software war nur auf Pilotmaschinen aber das hat eine Bande
anscheinend ganz schnell rausgekriegt und innerhalb von einem Wochenende
plus 1-2 Arbeitstage den Betrag mit eigentlich schon gesperrten
geklauten Karten abgezockt.
Udo Schmitt schrieb:> char * pc;>> if ((pc++ == '0') && (pc++ == '2') {> strcpy(..., pc);> ...> }
trotzdem ... so was führt doch zu "unpredictable behaviour". Vor allem
bei der Entwicklung von Geldautomatensoftware würde ich mich hüten,
potentiell gefährliche Konstrukte zu verwenden. Da schreib ich lieber ne
Zeile mehr...
Abgesehen davon ... sowas macht man generell nicht, weil man nie sicher
sein kann, was rauskommt...
Stefan Ernst schrieb:> Doch, weiß man. Der &&-Operator ist ein Sequence-Point, daher ist das> Verhalten sehr wohl definiert.
Jetzt habe ich doch tatsächlich was gelernt.
"Sequence point" war mir nicht geläufig, seit wann ist das im Standard?
(Habe seit etwa 7 Jahren nur noch wenig mit c und c++ zu tun)
Das war Microsoft C 6.0 in den späten 80er Jahren.
Stefan Ernst schrieb:> Mich würde aber nicht wundern, wenn> das durchaus so gewollt war,
Nein war an der Stelle definitiv nicht gewollt.
Random ... schrieb:> trotzdem ... so was führt doch zu "unpredictable behaviour".
Der Code ist prinzipiell völlig in Ordnung so.
Random ... schrieb:> Da schreib ich lieber ne> Zeile mehr...
Man ist nie sicher vor Compiler-Fehler, egal wie du den Code
formulierst.
Udo Schmitt schrieb:> Stefan Ernst schrieb:>> Doch, weiß man. Der &&-Operator ist ein Sequence-Point, daher ist das>> Verhalten sehr wohl definiert.>> Jetzt habe ich doch tatsächlich was gelernt.> "Sequence point" war mir nicht geläufig, seit wann ist das im Standard?> (Habe seit etwa 7 Jahren nur noch wenig mit c und c++ zu tun)> Das war Microsoft C 6.0 in den späten 80er Jahren.
Soweit ich mich erinnere: Seit dem ersten ANSI C Standard. Und ich
glaub, die haben das Konzept des Sequence Points damals auch von K&R
übernommen.
Stefan Ernst schrieb:> Das ist dann ein Compiler-Fehler.
Wofür die älteren Versionen von Microsoft-C aber durchaus berüchtigt
waren.
Stefan Ernst schrieb:> Der Code ist prinzipiell völlig in Ordnung so.
Nö, denn das fehlende 2. Inkrement (wenn der erste Teil falsch
ist) war ja offenbar unbeabsichtigt.
Udo Schmitt schrieb:> "Sequence point" war mir nicht geläufig, seit wann ist das im Standard?> (Habe seit etwa 7 Jahren nur noch wenig mit c und c++ zu tun)
Seit wann genau kann ich nicht sagen, aber in ANSI-C ist jedenfalls
schon drin.
Udo Schmitt schrieb:> Das war Microsoft C 6.0 in den späten 80er Jahren.
War mir nicht bewusst, dass von so ollen Kamellen die Rede ist (die
Versions-Nummern der MS-Compiler sind mir nicht geläufig).
Jörg Wunsch schrieb:> Stefan Ernst schrieb:>> Das ist dann ein Compiler-Fehler.>> Wofür die älteren Versionen von Microsoft-C aber durchaus berüchtigt> waren.
:-)
War damals nicht die Doktrin: Verwende nur MS-Compiler mit einer
ungeradzahligen Versionsnummer?
Karl Heinz Buchegger schrieb:> War damals nicht die Doktrin: Verwende nur MS-Compiler mit einer> ungeradzahligen Versionsnummer?
Ich kannte es so: Turbo-C für zuverlässige Codegenerierung, MSC für
einen schnellen Compiler und aggressiv (eben manchmal überaggressiv)
optimierten Code.
Karl Heinz Buchegger schrieb:> War damals nicht die Doktrin: Verwende nur MS-Compiler mit einer> ungeradzahligen Versionsnummer?
Rofl.
Ich muss heute abened mal nachschauen ob in meinem K&R "sequence point"
ein Thema ist. Das schafft mich jetzt schon etwas das ich das komplett
vergessen oder sogar nie gehört haben sollte.
Also Danke an Stefan Ernst, habe wieder was gelernt :-)
Jörg Wunsch schrieb:> Ich kannte es so: Turbo-C für zuverlässige Codegenerierung, MSC für> einen schnellen Compiler und aggressiv (eben manchmal überaggressiv)> optimierten Code.
Jepp so hatten wir damals entwickelt. Wobei, war MSC 6.0 nicht der erste
MS Compiler mit einer IDE?
p.s. Damals wurde Dokumentation noch in cm oder Meter gemessen
Jörg Wunsch schrieb:> Stefan Ernst schrieb:>> Der Code ist prinzipiell völlig in Ordnung so.>> Nö, denn das fehlende 2. Inkrement (wenn der erste Teil falsch> ist) war ja offenbar unbeabsichtigt.
Ich habe mit dem Satz dies
> trotzdem ... so was führt doch zu "unpredictable behaviour".
kommentiert. Und in Bezug auf das "unpredictable behaviour" ist der Code
"prinzipiell völlig in Ordnung so".
Außerdem sollte das "prinzipiell" eigentlich eben genau ausdrücken, dass
ich keine logische oder allgemeine Richtigkeit im weiteren Kontext
meine. ;-)
Stefan Ernst schrieb:> Random ... schrieb:>> trotzdem ... so was führt doch zu "unpredictable behaviour".>> Der Code ist prinzipiell völlig in Ordnung so.>> Random ... schrieb:>> Da schreib ich lieber ne>> Zeile mehr...>> Man ist nie sicher vor Compiler-Fehler, egal wie du den Code> formulierst.
Klar, aber man kann sich sicher sein, dass der Code mit nem anderen C
Compiler noch genau so funktioniert (von Compilerfehlern mal
abgesehen...).
1
char*pc;
2
3
if((pc++=='0')&&(pc++=='2'){
4
strcpy(...,pc);
5
}
6
7
if(pc[0]=='0'&&pc[1]=='2'){
8
strcpy(...,&pc[2]);
9
}
Hab das mal zerlegt. Ist der Code überhaupt korrekt? Wie sieht der
string pc aus? "00CardName" oder so?
Ein schönes Beispiel für den Abbruch der Verarbeitung ist mMn die
Sicherung von strings:
Udo Schmitt schrieb:> Jepp so hatten wir damals entwickelt. Wobei, war MSC 6.0 nicht der erste> MS Compiler mit einer IDE?
Ende der 80-er. IDE?
Die hätten wir nie angerührt!
Nacktes make und PLINK zum Linken von Overlays waren angesagt. (Dazu
eine höllisch komplizierte und fehlerträchtige Speicherverwaltung damit
man EMS und XMS Speicher benutzen konnte). Und als das Projekt in den
letzten Zügen kurz vor der Fertigstellung war, brachte Watcom den ersten
Compiler raus, mit dem die ganzen Klimmzüge wegen Flat Memory Model
obsolet waren :-)
> Hab das mal zerlegt. Ist der Code überhaupt korrekt?
Wenn du die fehlenden Dereferenzier-* in der ersten Version noch
ergänzt.
Aber ganz gleich sind die Teile auch nicht. Wenn hinter dieser Sequenz
pc noch anderweitig benutzt wird, unterscheiden sie sich.
Meist deswegen fies, weil das meist nicht immer gleich zu einen Problem
führt sondern erst viel später (weil man sich u.U. dahinter stehende
Variablen "zerschossen" hat).
Udo Schmitt schrieb:> Stefan Ernst schrieb:>> Mich würde aber nicht wundern, wenn>> das durchaus so gewollt war,> Nein war an der Stelle definitiv nicht gewollt.
Mal so nebenbei, dann hat es ja mit Microsoft C5.x auch nur auf Grund
eines Compiler-Fehlers funktioniert.
Udo Schmitt schrieb:> Wobei, war MSC 6.0 nicht der erste> MS Compiler mit einer IDE?
Möglicherweise von MS, das weiß ich nicht.
Borland hatte die IDE schon mit Turbo-Pascal auf CP/M, das dürfte
deutlich davor gewesen sein (Wikipedia nennt das Jahr 1983 für die
Version 1.0).
Jörg Wunsch schrieb:> Udo Schmitt schrieb:>> Wobei, war MSC 6.0 nicht der erste>> MS Compiler mit einer IDE?>> Möglicherweise von MS, das weiß ich nicht.>> Borland hatte die IDE schon mit Turbo-Pascal auf CP/M, das dürfte> deutlich davor gewesen sein (Wikipedia nennt das Jahr 1983 für die> Version 1.0).
Na ja. IDE ist deutlich übertrieben :-) und war letztendes auch nur ein
Abklatsch der 'IDE' von UCSD Pascal.
Beides war mehr so eine Menüzeile aus der man Editor und Compiler
aufrufen konnte. Plus ein wenig 'Zucker' um Projekte und damit Files zu
verwalten.
Jörg Wunsch schrieb:> Wikipedia nennt das Jahr 1983 für die> Version 1.0).
Ich habe mit Turbo Pascal 3.0 begonnen das muss 1985 oder 1986 gewesen
sein, etwa ein Jahr später kam Turbo Pascal 4.0 heraus, mit einer
richtigen IDE mit integriertem Debugger und keinem 64K Limit mehr (TP
3.0 hat .com Dateien erzeugt).
Etwa in dem Zeitraum kamen auch die Turbo C Versionen heraus.
Die Letzte mit der ich gearbeitet hatte war Borland C++ 2.0 mit einem
halben Meter Doku.
Der Entwickler schrieb:> Ein "beliebter" Fallstrick ist auch> int a[5];>> for(int i=0; i <=5;i++) a[i]=0;
Jap. Wenn man seinen src aber generell so auslegt, dass man (fast)
ausschliesslich mit 'i < MAX' Vergleichen arbeitet, vereinfacht man sich
vieles.
Karl Heinz Buchegger schrieb:> Na ja. IDE ist deutlich übertrieben :-) und war letztendes auch nur ein> Abklatsch der 'IDE' von UCSD Pascal.
UCSD Pascal war doch ein reiner Kommandozeilencompiler, oder? Habe
ich aber nie selbst in der Hand gehabt. Aber ich hatte mal einen
anderen Pascal-Compiler für CP/M und auch C-Compiler, die halt alle
deutlich umständlicher zu benutzen waren. Die brauchten ja
teilweise eine eigene Diskette nur für das Compilersystem, die
Nutzerdaten mussten also zwangsläufig auf einer zweiten stehen.
Keine 2 Minuten später hatte man dann sein "hello world" compiliert ...
dagegen ware Turbo-Pascal in der Tat ein Rennauto.
> Beides war mehr so eine Menüzeile aus der man Editor und Compiler> aufrufen konnte.
Von der Erinnerung her würde ich sagen, dass bei einem Compile-Fehler
der Editor auch mit der passenden Stelle aufgerufen wurde, oder?
Abgesehen vom Debugger (wirkliches symbolisches Debuggen im heutigen
Sinne gab's unter CP/M noch nicht) also erstmal alle wesentlichen
Grundfunktionen, die auch heute noch eine IDE bietet.
Ich habe damit mal die Ansteuerung meines EPROMMers geschrieben.
Anfangs komplett in Pascal, nach der Inbetriebnahme dann die innere
Schleife als inline assembly. Wenn ich alle Kontakte mal putze, geht
er vielleicht heute noch. :-)
Random ... schrieb:> Klar, aber man kann sich sicher sein, dass der Code mit nem anderen C> Compiler noch genau so funktioniert (von Compilerfehlern mal> abgesehen...).
Aber das kann man doch auch mit der ursprünglichen Variante (außer eben
bei einem Compiler-Fehler).
Nochmal ganz deutlich:
Der Compiler selbst hatte keine IDE. Dafür gabs dann Multiedit und Co.
und der MS make nammte sich Nmake ;)
Aber wenn ich mich richtig erinnere hatte der dazugehörige Codeview
Debugger so etwas ähnliches wie IDE.
Jörg Wunsch schrieb:> Karl Heinz Buchegger schrieb:>> Na ja. IDE ist deutlich übertrieben :-) und war letztendes auch nur ein>> Abklatsch der 'IDE' von UCSD Pascal.>> UCSD Pascal war doch ein reiner Kommandozeilencompiler, oder?
Ich habs nur in Form eine 'IDE' kennen gelernt (auf einem Phillips P2000
- Z80 System). Das UCSD Pascal hatte damals den Charm, dass man kein
Betriebssystem in dem Sinne, wie es mit CP/M kam, brauchte.
> Von der Erinnerung her würde ich sagen, dass bei einem Compile-Fehler> der Editor auch mit der passenden Stelle aufgerufen wurde, oder?
Weiß ich nicht mehr.
Hans-georg Lehnard schrieb:> Aber wenn ich mich richtig erinnere hatte der dazugehörige Codeview> Debugger so etwas ähnliches wie IDE.
Das kann auch sein, schon ziemlich lange her :-)
Ich hab das mal durch den AVR-GCC gejagt.
Der Code wird vollkommen korrekt übersetzt:
1
char*test(char*pc,char*dest)
2
{
3
b6:cf93pushr28
4
b8:df93pushr29
5
ba:fc01movwr30,r24
6
if((*pc++=='0')&&(*pc++=='2')){
7
bc:ec01movwr28,r24
8
be:2196adiwr28,0x01;1
9
c0:8081ldr24,Z
10
c2:8033cpir24,0x30;48
11
c4:39f4brne.+14;0xd4<test+0x1e>
12
c6:8181lddr24,Z+1;0x01
13
c8:2196adiwr28,0x01;1
14
ca:8233cpir24,0x32;50
15
cc:19f4brne.+6;0xd4<test+0x1e>
16
strcpy(dest,pc);
17
ce:cb01movwr24,r22
18
d0:be01movwr22,r28
19
d2:04d0rcall.+8;0xdc<strcpy>
20
}
21
returnpc;
22
}
23
d4:ce01movwr24,r28
24
d6:df91popr29
25
d8:cf91popr28
26
da:0895ret
Man sieht auch sehr schön die geradezu panische Angst des AVR-GCC vor
Postincrement.
Er legt sich lieber eine Kopie an und macht zweimal ein extra ADIW.
Peter
Stefan Ernst schrieb:> Random ... schrieb:>> Klar, aber man kann sich sicher sein, dass der Code mit nem anderen C>> Compiler noch genau so funktioniert (von Compilerfehlern mal>> abgesehen...).>> Aber das kann man doch auch mit der ursprünglichen Variante (außer eben> bei einem Compiler-Fehler).> Nochmal ganz deutlich>
1
if((pc++=='0')&&(pc++=='2')){
hat kein "unpredictable
> behaviour".
Erhöht aber die Wahrscheinlichkeit von unpredictable behavior vor der
Tastatur deutlich. Ich hätte bei der Zeile auch erstmal nachschauen
müssen ob das definiertes Verhalten ist. Und ich mach schon ein paar
Jahre in C. Mag gültig sein aber ob das guter Stil ist?
Matthias
Μαtthias W. schrieb:> müssen ob das definiertes Verhalten ist. Und ich mach schon ein paar> Jahre in C. Mag gültig sein aber ob das guter Stil ist?
Jedesmal wenn du einen Pointerzugriff absicherst
if( Pointer && Pointer->function() )
machst du nichts anderes als auszunutzen dass
* in C && eine Short Circuit Evaluation machen muss
* letzten Endes der && ein Sequence Point darstellt, denn natürlich
muss auch
if( foo() && foo()->function() )
funktionieren, wenn der Pointer durch die Funktion foo geliefert wird.
while( ( c = getchar() ) && c != '\n' )
...
ist eine Variation davon, die in vielen C Büchern ziemlich bald am
Anfang vorkommt. Genau das gleiche. Das ist definiert weil && ein
Sequence Point ist
gcc machts seit ein paar Jahren "richtig", was andere C-Compiler daraus
machen, kann man erahnen, wenn man sich den Preprocessor-Output via "gcc
--traditional -E foo.c" anschaut.
Gruß,
Frank
Was ist für dich richtig ?
#define NEG(a) -a
#define DCR(a) --a
defines sind reine Textersetzung.
Wenn du ein definiertes Verhalten haben willst setze Klammern
#define NEG(a) (-a)
#define DCR(a) (--a)
Frank M. schrieb:> gcc machts seit ein paar Jahren "richtig",
richtig ist in deinem Code ein Ergebnis von 2
Und eigentlich sollte das so ziemlich jeder Compiler der letzten 30
Jahre hinkriegen.
Aber in einem Punkt hast du schon recht.
Präprozessor-Dinge sind auch beliebte Fallstricke für Programmierer, die
allzu sorglos Makros einsetzen wo sie eigentlich Funktionen nehmen
sollten.
Hans-georg Lehnard schrieb:> Was ist für dich richtig ?
Wenn Du genau nachliest, hatte ich das Wort "richtig" bewusst in
Anführungszeichen gesetzt.
> Wenn du ein definiertes Verhalten haben willst setze Klammern
Das brauchst Du mir nicht zu sagen. Ich habe dieses Beispiel aus dem "C
Puzzle Book". Lesenswert, da sind viele kleine C-Rätsel drinne. Naja,
ist schon 25 Jahre her, wo ich das gelesen habe.
Karl Heinz Buchegger schrieb:> Frank M. schrieb:>>> gcc machts seit ein paar Jahren "richtig",>> richtig ist in deinem Code ein Ergebnis von 2
Neuere Versionen von gcc sehen das anders:
$ cc foo.c && ./a.out
Ausgabe:
y = 3
Das liegt an:
$ cc -E foo.c:
1
...
2
main()
3
{
4
intx=3;
5
inty;
6
7
y=--x;
8
printf("y = %d\n",y);
9
}
Der Preprocessor baut da ein Blank zwischen die beiden Minuszeichen.
Jetzt:
$ cc --traditional -E foo.c:
1
...
2
main()
3
{
4
intx=3;
5
inty;
6
7
y=--x;
8
printf("y = %d\n",y);
9
}
Jetzt fehlt das Blank und Dein "richtiges" Ergebnis mit dem Wert 2 würde
da auch rauskommen, wenn gcc sich nicht weigern würde, traditional-Code
zu compilieren.
Ich weiß nicht, wann das im gcc geändert wurde, muss irgendwann in den
Neunzigern gewesen sein.
Gruß,
Frank
Frank M. schrieb:>> richtig ist in deinem Code ein Ergebnis von 2>> Neuere Versionen von gcc sehen das anders:
Ach shit.
Ja, da hat der gcp recht.
In ISO C basiert die Ersetzung auf einer Tokenisierung. Wir sagen zwar
oft, dass der Preprocessor 'nur Textersetzung' macht, aber eigentlich
stimmt das schon lange nicht mehr. Das ist so ein Fall, bei dem es einen
Unterschied macht.
ein anderer
#define foo() baz
foo()bar
wird zu
baz bar
und nicht zu
bazbar
Bei der Ersetzung bleiben die Tokens erhalten. Vor der Ersetzung gab es
2 Tokens "foo()" und "bar" und danach sind es immer noch 2 Token.
Da hab ich wohl zu schnell gebrüllt :-)
Karl Heinz Buchegger schrieb:> Ja, da hat der gcp recht.> In ISO C basiert die Ersetzung auf einer Tokenisierung. [...]
Dann baut Microsoft Visual C++ 2008 immer noch Mist. Ich habe es eben
mal getestet: Hier wird 2 ausgegeben. Und auch MS Visual C++ 2010 gibt 2
aus. Auch andere UNIX-Compiler beharren auf der 2. gcc ist der einzige,
der mich eines anderen belehrt hat.
> #define foo() baz>> foo()bar>> wird zu>> baz bar>> und nicht zu>> bazbar
Gerade mit -traditionl getestet. Hier setzt der Preprocessor immer ein
Blank ein. Das hat wohl damit zu tun, dass es nun ein Makro und keine
Konstante ist.
> Da hab ich wohl zu schnell gebrüllt :-)
Wie gesagt: Die anderen Compiler, die ich kenne, geben auch heute noch 2
als Ergebnis aus. "richtig" kann man so oder so werten ;-)
Hier noch ein schönes Problem, das ich Ende der 80er hatte, als ich den
Gosling-Emacs (ist ein Text-Editor, Source stammte ursprünglich von BSD)
auf Unix-System-V portieren wollte:
Der portierte Emacs zeigte keine Zeilenumbrüche an. Selbst beim Debuggen
konnte man sehen, dass einfach C-Code komplett übersprungen wurde... bis
ich endlich nach langem Suchen herausfand, dass eine vermeintliche
Funktion tatsächlich ein Preprocessor-Makro war, der in einem der
dutzenden Include-Dateien versteckt war...
Ich kann das hier nicht mehr wortwörtlich wiedergeben. Daher schreibe
ich hier stellvertretend mal einen Ersatz-Code, welcher den Fallstrick
verdeutlichen soll:
1
main()
2
{
3
intcolumns=80;
4
intx=4711;
5
6
if(columns>80)
7
check_line(x);
8
else
9
printf("columns <= 80\n");// Diese Zeile wird NICHT ausgegeben!
10
}
Rätselhaft? Nicht wenn man das Makro dazu kennt:
1
#define check_line(a) if (a > 0) a = 0
Was lernt man daraus? Man sollte in Makros nicht nur die Argumente
klammern, sondern am besten auch das ganze Makro in geschweifte Klammern
setzen, also:
1
#define check_line(a) { if (a > 0) a = 0; }
Da ich in der Vergangenheit viel im Team programmiert habe und mich
nicht immer auf den Code von "Kollegen" verlassen konnte, habe ich mir
seitdem angewöhnt, if-Statements immer als Blöcke zu schreiben, auch
wenn nur eine Zeile nach dem if folgt.
Also:
1
if(columns>80)
2
{
3
check_line(x);
4
}
5
else
6
{
7
printf("columns <= 80\n");// Diese Zeile wird NICHT ausgegeben!
8
}
Und so halte ich das heute noch. Ich nenne das "defensives
Programmieren".
Gruß,
Frank
P.S.
Die Lösung, warum der Gosling-Emacs unter BSD die Zeilen korrekt umbrach
und unter Unix-System-V nicht, war schnell gefunden:
Jemand, meinte, über den Code einen automatischen "C-Beautifier" laufen
lassen zu müssen, damit der Code lesbarer und damit portabler wird.
Dieser Automatismus hatte die geschweiften Klammern nach dem
if (columns > 80)
entfernt.
Karl Heinz Buchegger schrieb:> i = 018;>> Gemeinerweise ist das dann aber eine Oktalzahl und keine Dezimalzahl.
Bist du dir ganz sicher, dass ausgerechnet dies eine Oktalzahl ist?
Frank M. schrieb:> Was lernt man daraus? Man sollte in Makros nicht nur die Argumente> klammern, sondern am besten auch das ganze Makro in geschweifte Klammern> setzen, also:>> #define check_line(a) { if (a > 0) a = 0; }
Auch damit setzt du dich ggf. noch in die Tinte:
if (...)
check_line(a);
else
...sonstwas...
Sieht perfekt aus, aber der Compiler hat dazu seine eigene Ansicht.
Wer es mit sowas wirklich ernst meint, der verwendet
#define check_line(a) do{ if (a > 0) a = 0; }while(1)
Frank M. schrieb:> Da ich in der Vergangenheit viel im Team programmiert habe und mich> nicht immer auf den Code von "Kollegen" verlassen konnte, habe ich mir> seitdem angewöhnt, if-Statements immer als Blöcke zu schreiben, auch> wenn nur eine Zeile nach dem if folgt.
Bei uns in der Firma hat das zu den normalen coding style guide gehört.
Unter anderem verhindert das auch dumme Fehler, die man macht wenn man
spät abends noch was fixen will:
1
if(a>10)
2
do_something();
3
do_something_else();
wird beim debuggen zu:
1
if(a>10)
2
do_something();
3
printf("Doing something...");
4
do_something_else();
oder
1
if(a>10)
2
first_do_another_thing();
3
do_something();
4
do_something_else();
Wenn man wach ist fällt einem sowas natürlich schnell auf, aber wenn
abends die Server nicht laufen und alles was man ausprobiert nur Unsinn
macht, dann ist es sinnvoll den Code so narrensicher wie möglich zu
haben...
A. K. schrieb:> Karl Heinz Buchegger schrieb:>>> i = 018;>>>> Gemeinerweise ist das dann aber eine Oktalzahl und keine Dezimalzahl.>> Bist du dir ganz sicher, dass ausgerechnet dies eine Oktalzahl ist?
Hehe, der war gut :-)
foo.c:7:13: error: invalid digit "8" in octal constant
Wer mal versucht hat, den Fähigkeiten des C Präprozessors mit
Tokenisierung, # und ## Operatoren etc. komplett auszuloten, der wird
beim nächsten Compiler feststellen, dass bei diesem Aspekt der Compiler
die genaue Einhaltung des Standards schwächer ist als in den meisten
anderen Stellen.
Verwirrter Anfänger schrieb:> Bei uns in der Firma hat das zu den normalen coding style guide gehört.
Sehe ich auch so. Geschweifte Klammern sind ein absolutes Muss. Schade,
dass Karl Heinz dies bei seinen C-Beispiel-Hilfen (die ich für
bewundernswert halte) das nicht konsequent durchhält. Gerade Anfänger
sollte man direkt von Anfang an "erziehen".
> Wenn man wach ist fällt einem sowas natürlich schnell auf, aber wenn> abends die Server nicht laufen und alles was man ausprobiert nur Unsinn> macht, dann ist es sinnvoll den Code so narrensicher wie möglich zu> haben...
Absolutes ACK.
A. K. schrieb:> Natürlich so:> #define check_line(a) do{ if (a > 0) a = 0; }while(0)
Ich weiß, worauf Du hinauswillst, aber das zu lesen ist einfach Sch...
;-)
Wenn man konsequent im eigentlichen Quellcode geschweifte Klammern
verwendet, kann es einem egal sein, wie das Makro aussieht. Und das war
eigentlich die Intention meines Beitrags.
Frank M. schrieb:>> #define check_line(a) do{ if (a > 0) a = 0; }while(0)>> Ich weiß, worauf Du hinauswillst, aber das zu lesen ist einfach Sch...
Kannst ja den alten Trick verwenden:
#define MACRO_BEGIN do{
#define MACRO_END }while(0)
#define check_line(a) MAKRO_BEGIN if (a > 0) a = 0; MAKRO_END
und schon sieht es wieder wunderschön aus. ;-)
Frank M. schrieb:> Verwirrter Anfänger schrieb:>>> Bei uns in der Firma hat das zu den normalen coding style guide gehört.>> Sehe ich auch so. Geschweifte Klammern sind ein absolutes Muss. Schade,> dass Karl Heinz dies bei seinen C-Beispiel-Hilfen das nicht> konsequent durchhält.
Das liegt daran, dass ich strikt gegen solche Makro-Schweinereien bin,
wie du sie hier aufgeführt hast. :-)
Ne, im Ernst. Ich bin noch nie in dieses Problem gelaufen, dass ich mir
mit einem Makro das if-Nesting verbogen habe. Wahrscheinlich muss ich
mir da erst mal die Finger drann verbrennen. Aber wie gesagt:
solche Schweiniereien mach ich grundsätzlich nicht mit
und wenn, dann nur mit entsprechender Absicherung durch die do-while {}
Geschichte.
Ich bin auch ganz starker Verfechter der Konvention: Makros werden
grundsätzlich und immer in Grossbuchstaben geschrieben. Und umgekehrt -
ein Name in dem nur Grossbuchstaben vorkommen ist immer ein Makro. Bei
sowas
#define check_line(a) .....
würde ich eine Revolte anzetteln :-)
Ein anderer Grund ist, dass ich jetzt seit vielen Jahren beruflich
praktisch nur noch C++ mache und da gibt es für derartige Dinge immer
bessere Möglichkeiten als ein Makro. D.h. die Verwendung von Makros ist
(bis auf ab und zu mal eine #define Konstante und ein paar #if)
praktisch bei 0 angekommen.
A. K. schrieb:> Kannst ja den alten Trick verwenden:> #define MACRO_BEGIN do{> #define MACRO_END }while(0)
Gefällt mir schon besser! :-)
Das weckt die Erinnerungen an den uralten Source der Bourne-Shell (ja,
vom berühmten Stephen R. Bourne). Er mochte C wohl auch nicht so recht,
denn seine "Lieblingssprache" war Algol, die er damals selbst
mitentwickelt hat.
Aus diesem Grund finden man in seinen C-Sourcen der Shell so etwas:
#define IF if(
#define THEN ){
#define ELSE } else {
#define ELIF } else if (
#define FI ;}
Das galt auch für while, switch usw. Sämtliche Macros kann man hier
nachlesen:
http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/mac.h
Da sieht "C" plötzlich ganz anders aus ;-)
Karl Heinz Buchegger schrieb:> #define check_line(a) .....>> würde ich eine Revolte anzetteln :-)
Die AVR Include-Files strotzen nur so vor kleingeschriebenen Macros.
Peter
Peter Dannegger schrieb:>> würde ich eine Revolte anzetteln :-)>> Die AVR Include-Files strotzen nur so vor kleingeschriebenen Macros.
:-)
Ich meinte hier bei mir in der Firma.
Karl Heinz Buchegger schrieb:> Das liegt daran, dass ich strikt gegen solche Makro-Schweinereien bin,> wie du sie hier aufgeführt hast. :-)
Das sehe ich auch so. Ich mag auch keine Makro-Schweinereien.
> Ich bin auch ganz starker Verfechter der Konvention: Makros werden> grundsätzlich und immer in Grossbuchstaben geschrieben. Und umgekehrt -> ein Name in dem nur Grossbuchstaben vorkommen ist immer ein Makro. Bei> sowas
Konventionen sind immer gut, gerade in C. Auch ich halte mich an die
Großbuchstaben-Konvention bei Konstanten und Makros. Aber leider kann
man sich nicht immer darauf verlassen, wenn man Sources von anderen
Leuten weiterbearbeiten muss - leider.
> #define check_line(a) .....>> würde ich eine Revolte anzetteln :-)
Wie Peter schon schrieb: kleingeschriebene Makros findet man zuhauf...
leider.
Naja, die Macros in den Compiler-Includes sollten schon einigermaßen
störfest sein. Ich wüßte da jetzt keine ernsthaften Probleme.
Der größte Fallstrick beim AVR-GCC war für mich die volatile Falle.
Da hab ich lange Zeit gebraucht, um mich daran zu gewöhnen.
Und die char-Falle, wenn man in Strings numerische Werte eingebettet hat
und diese dann unerwünscht signed expandiert werden.
Leider meckert der AVR-GCC rum, wenn man zur Vermeidung dieser Falle die
Strings (UART-Puffer) unsigned definiert.
Ich tus trotzdem (lieber meckern als falschen Code) und trickse ihn aus
(void*).
Ansonsten sagt mir die sparsame Syntax schon sehr zu.
Ich mag es auch, wenn sich Sprachen sehr logisch verhalten.
Peter
Ich musste vor langer Zeit den Unterschied zwischen Pointer- und
Arraydeklarationen lernen, und zwar bei mehreren Sourcedateien (war ein
AVR-Projekt):
In einer Sourcedatei hatte ich einen globalen Puffer:
1
charbuf[BUFSIZE];
In anderen Sourcedateien hatte ich folgende Deklaration, um auf den
Puffer zuzugreifen:
1
externchar*buf;
Nur ist das halt nicht dasselbe wie:
1
externcharbuf[];
Mein Programm lief nicht, der gcc spuckte keinen Fehler aus.
Nach Betrachten des Assemblercodes kam dann der große Aha-Moment.
Xenu schrieb:> In anderen Sourcedateien hatte ich folgende Deklaration, um auf den> Puffer zuzugreifen:> extern char* buf;> Nur ist das halt nicht dasselbe wie:> extern char buf[];
Warum nicht?
Ich verwende es ohne Probleme, z.B.:
1
float*fp;
2
fp=...
3
fp[cal_no]=fval;
Ein Fehler kann nur dann entstehen, wenn man den Zeiger auf das
Ursprungs-Array zu ändern versucht. Aber das ist dann ein Fehler des
Programmierers.
Peter
Peter Dannegger schrieb:> Xenu schrieb:>> In anderen Sourcedateien hatte ich folgende Deklaration, um auf den>> Puffer zuzugreifen:>> extern char* buf;>> Nur ist das halt nicht dasselbe wie:>> extern char buf[];>> Warum nicht?
An dieser Stelle bedeutet es tatsächlich was anderes.
buf ist nun mal keine Variable in der eine Adresse drinnen steht.
Dieser Fehler ist die Manifestation des Ausspruchs "Ein Array ist kein
Pointer".
(Du hast vielleicht übersehen, dass von globalen Variablen die Rede ist)
Danke für die Erklärung.
Bei der Deklaration ist es ein Unterschied.
Nur bei der Verwendung oder als Parameter ist es egal.
Zum Fehlervermeidung sollte man daher auch immer das eigene Include mit
in das jeweilige C-File einbinden.
Dann kriegt man bei Unterschieden einen Error:
Frank M. schrieb:> A. K. schrieb:>> Mit>> #define TRUE (-1)>> aber zur Laufzeit auch. ;-)>> Ich habe mich auch schon gefragt, was er damit bezweckt hat. Hast Du> eine Idee?
Ein Byte mit lauter 1-Bit als Gegensatz zu einem mit lauter 0-Bits.
Aber wenn du mich fragst, was das bringt: keinen blassen Schimmer.
Vielleicht hat es ihm einfach nur besser gefallen, wenn er sich
'boolsche' Variablen im Debugger angesehen hat, denen er TRUE explizit
zugewiesen hat.
Vielleicht hat ihm auch ~ als Negierung besser gefallen, als ein
schnödes !
if( ~Wert == TRUE )
Peter Dannegger schrieb:> Karl Heinz Buchegger schrieb:>> #define check_line(a) .....>>>> würde ich eine Revolte anzetteln :-)>> Die AVR Include-Files strotzen nur so vor kleingeschriebenen Macros.>>> Peter
assert() ist auch eins - und das ist nicht nur bei AVR vorhanden :)
Karl Heinz Buchegger schrieb:> Ein Byte mit lauter 1-Bit als Gegensatz zu einem mit lauter 0-Bits.
Ich würde sogar sagen: Ein Word mit lauter 1-Bit, die Shell hat er wohl
auf einer PDP11 geschrieben ;-)
> Aber wenn du mich fragst, was das bringt: keinen blassen Schimmer.> Vielleicht hat es ihm einfach nur besser gefallen, wenn er sich> 'boolsche' Variablen im Debugger angesehen hat, denen er TRUE explizit> zugewiesen hat.
Kann gut sein, 16 Einsen (bzw. 0177777 oktal) stechen einem da schon
mehr ins Auge. Damals gab es ja den Typ "bool" in C noch nicht.
Vermutlich wollte er damit den Wert von TRUE von den "allgemeinen"
Integer-Zahlen abheben.
> Vielleicht hat ihm auch ~ als Negierung besser gefallen, als ein> schnödes !>> if( ~Wert == TRUE )
Wenn ich ein
#define NOT ~
in seinen Macros gefunden hätte, würde ich Dir das glatt abnehmen ;-)
Kann es sein, dass ein Laden von -1 in ein PDP11-CPU-Register direkt ein
Carry-Bit o.ä. gesetzt hat?
Dann wäre ein
if (x == TRUE)
vom C-Compiler sehr effizient umzusetzen...
A. K. schrieb:> Frank M. schrieb:>>> Was lernt man daraus? Man sollte in Makros nicht nur die Argumente>> klammern, sondern am besten auch das ganze Makro in geschweifte Klammern>> setzen, also:>>>> #define check_line(a) { if (a > 0) a = 0; }>> Auch damit setzt du dich ggf. noch in die Tinte:> if (...)> check_line(a);> else> ...sonstwas...> Sieht perfekt aus, aber der Compiler hat dazu seine eigene Ansicht.>> Wer es mit sowas wirklich ernst meint, der verwendet> #define check_line(a) do{ if (a > 0) a = 0; }while(1)
Meiner Meinung nach einer der wenigen Fälle, wo man mal den ?-Operator
benutzen kann.
#define check_line(a) ( (a) > 0 ? (a) = 0 )
aber check_line würde ich nichts desto trotz komplett groß schreiben ;-)
Simon K. schrieb:> Meiner Meinung nach einer der wenigen Fälle, wo man mal den ?-Operator> benutzen kann.>> #define check_line(a) ( (a) > 0 ? (a) = 0 )
Geht nicht. Was sollte denn von diesem Konstrukt der Wert sein, wenn a
nicht größer als 0 ist? Der dritte Operand ist nicht optional.
Rolf Magnus schrieb:> Simon K. schrieb:>> Meiner Meinung nach einer der wenigen Fälle, wo man mal den ?-Operator>> benutzen kann.>>>> #define check_line(a) ( (a) > 0 ? (a) = 0 )>> Geht nicht. Was sollte denn von diesem Konstrukt der Wert sein, wenn a> nicht größer als 0 ist? Der dritte Operand ist nicht optional.
Oh, übersehen
dann halt:
#define check_line(a) ( (a) > 0 ? (a) = 0 : (a) = (a) )
Simon K. schrieb:> dann halt:>> #define check_line(a) ( (a) > 0 ? (a) = 0 : (a) = (a) )
Wenn a volatile ist, wird der letzte Ausdruck unnütz ausgeführt.
Ein Ausdruck muß aber keine Variable sein, es geht auch eine Zahl:
Stefan W. schrieb:> assert() ist auch eins - und das ist nicht nur bei AVR vorhanden :)
Die gängige Konvention ist doch, dass "function-like macros" klein
geschrieben werden, "constant value macros" dagegen groß.
Der ganze C-Standard enthält einige dieser funktionsähnlichen Makros,
die allesamt klein geschrieben sind. Es ist natürlich Aufgabe des
Implementierers, dass sich der Makro auch wirklich wie eine Funktion
benimmt. Geht natürlich nicht vollständig, wenn beispielsweise
jemand ein Argument wie "*cp++" übergibt, dann kann es passieren, dass
die Inkrementierung zweimal erfolgt. Irgendwo gab's da was in den
Untiefen von X11, bei dem ausdrücklich davor gewarnt wird, das ++ mit
in den Aufruf zu schreiben, da die vermeintliche Funktion ein Makro
ist.
Kein Fallstrick, aber trotzdem hübsch anzusehen. Ich wette, dass der
eine oder andere die eine oder andere Schreibweise von Arrays und deren
Elementen noch nie gesehen hat ;-)
1
#include<stdio.h>
2
3
int
4
main()
5
{
6
inti;
7
inta[3];
8
9
for(i=0;i<3;i++)
10
{
11
a[i]=2*i;
12
}
13
14
i=1;
15
printf("%d %d\n",i[a],a[i]);
16
printf("%d %d\n",1[a],a[1]);
17
printf("%d %d\n",2[a],a[2]);
18
printf("%c %c\n",i["fun"],"fun"[i]);
19
printf("%s %s\n",&i["fun"],&"fun"[i]);
20
return0;
21
}
Ausgabe:
2 2
2 2
4 4
u u
un un
Zum Beispiel war mir die Schreibweise Index[Array] statt Array[Index]
lange Zeit unbekannt.
Viel Spaß beim ... Verstehen :-)
Frank M. schrieb:> Zum Beispiel war mir die Schreibweise Index[Array] statt Array[Index]> lange Zeit unbekannt.
C ist eben sehr Assembler nah:
Zieladresse = Startadresse + Offset
Und die Addition ist ja bekanntermaßen kommutativ.
Der AVR-GCC nutzt das sogar aus, lädt den Index in den Z-Pointer und
addiert dann die konstante Startadresse.
Peter
Frank M. schrieb:> Zum Beispiel war mir die Schreibweise Index[Array] statt Array[Index]> lange Zeit unbekannt.
Dann hast du noch nie in den IOCCC reingeschaut. ;-)
Das C99 Rationale meint dazu übrigens:
6.5.2.1 Array subscripting
The C89 Committee found no reason to disallow the symmetry that
permits a[i] to be written as i[a].
Jörg Wunsch schrieb:> Dann hast du noch nie in den IOCCC reingeschaut. ;-)
Stimmt. Für mich war immer die Bibel K&R das Maß der Dinge. Dort muss
ich es entweder überlesen haben oder es steht nicht drin ;-)
Also die häufigsten Probleme sind sicherlich Stringüberläufe. Das
Problem ist, das C so implementiert ist, dass das a) nicht abgefangen
werden kann (in den üblichen String Implementierungen) und b) häufig der
Stack und somit der Rücksprungzeiger überschrieben wird.
Dann gibts natürlich auch noch die üblichen Portabilitätsprobleme:
unsigned int i=0;
i--;
gibt für i unterschiedliche Ergebnisse, abhängig von Compiler und
Architektur.
Die Kombination aus "nicht Hochsprache" und "nicht maschinennah" war für
mich der Grund die weitere Beschäftigung mit C einzustellen. Es lohnt
den Mehraufwand für mich nicht. Wenn ich eine höhere Sprache verwenden
will, nehme ich Pascal. Da kümmert sich auch der Compiler selbst um
Units und Libraries. Pascal gibt mir im obigen Anwendungsfall einen
Fehler aus. (eventuell erst zur Laufzeit und nur wenn ich die
Laufzeitprüfungen aktiviere)
Kan asta schrieb:> Peter Dannegger schrieb:>> C ist eben sehr Assembler nah:>> pfff...verglichen womit? HTML?
Weil HTML eine prozedurale Programmiersprache ist, so wie C?
Naja, HTML ist keine Programmiersprache. Eher etwas, um Seiten zu
beschreiben. Der Vergleich hat einen Klumpfuß ;)
Vermutlich ist der Vergleich auch nicht so ernst gemeint ...
Klar hat C so seine Macken. Aber es taugt für ganz kleine wie für
ganz große Programme. Das kann eigentlich kaum eine andere Sprache.
Ich habe mal mit Pascal angefangen, das war mir aber zu geschwätzig
(1 Seite Pascal ~ 2 Zeilen C).
Fehler ? Klar. So ziemlich alles was hier aufgeführt wurde. Kann man
das C anlasten ? Welche Programmiersprache hat denn so etwas nicht ?
Udo Schmitt schrieb:> Der Fehler war nicht von mir, aber ich musste ihn finden. Leider war das> damals die Auswertung der Antwort einer Autorisierungszentrale an einen> Geldautomaten, das ganze hat damals einen Schaden von mehreren 10000 DM> verursacht.
Der Fehler waren fehlende Test Cases.
Joachim Drechsel schrieb:> Vermutlich ist der Vergleich auch nicht so ernst gemeint ...
Zwischen Assembler und C liegen Welten. Daher der Vergleich mit HTML,
was von Stümpern auch gern als Programmiersprache bezeichnet wird.
Kan asta schrieb:> Zwischen Assembler und C liegen Welten. Daher der Vergleich mit HTML,> was von Stümpern auch gern als Programmiersprache bezeichnet wird.
Ich komme aus der Assembler-Ecke, der Umstieg auf C war nicht so
schwierig. Anfangs C wie eine Art Makro-Assembler verwendet, dann
immer abstrakter. Einen Unterschied von "Welten" sehe ich da jetzt
nicht. Es kommt, wie immer, drauf an was man daraus macht.
Ob man mit HTML/CSS programmiert ? Es ist egal wie man das nennt ;)
Die meisten sagen "Nein".
Ich halte das auch für unwichtig. Ich kenne einige Leute die mit
HTML/CSS sehr fit sind und da tolle Sachen erstellen. "Stümper" sind
das nicht (die schlagen immer 3 Kreuze wenn ich serverseitig mit C
ankomme und staunen dann über so Sachen wie "Performance" da sie das
von PHP her nicht kennen ;)).
HTML/CSS, C etc sind alles Dinge, die einen Computer dazu bewegen
sollen, das zu machen was ich will. iA nent man das "Programmieren".
Kan asta schrieb:> Zwischen Assembler und C liegen Welten.
Ich glaube, dass Du Peters Formulierung
"C ist eben sehr Assembler nah:"
einfach falsch verstanden hast. Natürlich sind C und Assembler
grundverschiedene Programmiersprachen. Aber vieles, was in C formuliert
wird, kann vom Compiler sehr transparent (und für den Programmierer auch
nachvollziehbar) in Assembler übersetzt werden.
im obfuscated c contest fand ich mal eine nette zeile:
#define _ define
oder es war
#define _ #define
ein paar solcher scherze hatte ich mal ausprobiert, zu oft macht dann
der gcc preprozessor n strich durch die richtung.
kennt jemand ne sammlung von techniken aus eben diesem obfuscated c
contest?
http://www.ioccc.org/
n.n. schrieb:> #define _ #define
Hat mit einem heutigen Präprozessor keinen Sinn mehr. Aus dem Grund
hat man ja in C99 _Pragma() als Alternative zu #pragma erfunden, weil
man jenes auch innerhalb eines Makros benutzen kann.
p.s.: Bitte nur sinnvolle email-Adressen angeben. Die Angabe dieser
Adresse dient ausschließlich dazu, dass der Absender über bspw. eine
Löschung oder Verschiebung informiert werden kann. Wer auf diese
Möglichkeit verzichten möchte, gibt einfach nichts an, aber eine
unsinnige Adresse bringt im besten Fall gar nichts, im schlimmsten
Fall Andreas' Server mehr Arbeit (weil er ja ggf. doch versuchen wird,
eine Mail zuzustellen).
weiterer kleiner fallstrick in c: eine schleife genau 256 mal
durchlaufen lassen, mit nem uint8_t als zähler.
for(;;) geht da nicht in trivialer art.
lösung:
uint8_t count=0;
do {
//hier aktion ausführen..
count++;
} while(count);
ps: oh danke für den hinweis mit der email, werd brav das feld nun
freilassen. :)
>Das ist aber die Variante, wie man's nicht macht.
Ja eben. Das war der Original-Beispielcode aus der Borland-Builder-Hilfe
zur Funktion fgetc(). Das ist ja gerade das Lustige, dass es falsch ist.
> Maybe I need to change my story.
Er sagt "vielleicht". In meinen einfachen Augen braucht eine
"richtige" Programmiersprache Kontrollstrukturen wie "if",
Schleifen etc. Egal, Turing war Mathematiker, programmiert hat
er wohl eher nicht.
Nebenbei sind HTML5/CSS3 im Moment zwar in, aber nicht zu 100%
anwedbar (vielen Dank an M$).
Ich: "Tja Kunde, ihr habt echt noch IE6 ? Sicherheitsprobleme
und die Darstellung stimmt nicht ?"
Kunde: "die Umstellung des Terminalservers kostet 12.000. Das bleibt".
Blattschuß. Nix mit HTML5/CSS3. Vielleicht in 10 Jahren wieder :-(
Joachim Drechsel schrieb:> Kontrollstrukturen
Steuerstrukturen bitte. Die Dinger "kontrollieren" nichts.
"to control" kann im Deutschen sowohl steuern, regeln als auch
kontrollieren bedeuten.
Jörg Wunsch schrieb:> "to control" kann im Deutschen sowohl steuern, regeln als auch> kontrollieren bedeuten.
Verstanden hast Du's aber ;)
hepp ein Päckchen Korinthen :-)))
Der Entwickler schrieb:> Sorry musste aus gegebenen Anlass> (Beitrag "goto-Label an Funktion übergeben?") sein ;-)
Nein, ist Quark, weil natürlich dort geklärt worden ist, dass es
sowas erst einmal nicht gibt. setjmp/longjmp ist eine andere
Baustelle, und die haben durchaus ihre Berechtigung (werden aber
eher selten verwendet).
Christian Berger schrieb:> Das ist inzwischen Falsch. Die Kombination aus HTML und CSS ist Turing> vollständig.
Na ja - das Beispiel, das man online finden kann, erfordert zwingend die
Interaktion des Benutzers. Also nicht mal eine einfache Endlosschleife
kriegt man damit hin, da ist jeder Uralt-Assembler mächtiger ;-)
Johann L. schrieb:>> Nein, ist Quark, weil natürlich dort geklärt worden ist, dass es>> sowas erst einmal nicht gibt.>> computed goto in GNU-C?
Erstens hat das nichts mit der Sprache C zu tun ;,), zweitens geht
es meiner Meinung nach auch nur innerhalb der Funktion (hab's aber
nicht probiert, da ich das Fietscher ohnehin für recht zweifelhaft
halte).
Aus aktuellem Anlass, etwas aus der Kategorie unerwartete implizite
Typenkonverierung:
1
voidmain(){
2
foo(10,-60,50);
3
}
4
5
voidfoo(uint32_tnOp1,int32_tnOp2,uint32_tnOp3)
6
{
7
if(nOp1+nOp2<nOp3)
8
{
9
// das wird nicht gemacht, hätte ich aber erwartet
10
}else{
11
// das wird gemacht
12
}
13
}
C konvertiert wenn der unsigned Wert gleiche oder größer Bitbreite als
der signed Wert hat immer zum unsigend Wert.
Hier was der C99 Standard dazu sagt:
1
6.3.1.8
2
If both operands have the same type, then no further conversion is needed.
3
Otherwise, if both operands have signed integer types or both have unsigned
4
integer types, the operand with the type of lesser integer conversion rank is
5
converted to the type of the operand with greater rank.
6
Otherwise, if the operand that has unsigned integer type has rank greater or
7
equal to the rank of the type of the other operand, then the operand with
8
signed integer type is converted to the type of the operand with unsigned
9
integer type.
10
Otherwise, if the type of the operand with signed integer type can represent
11
all of the values of the type of the operand with unsigned integer type, then
12
the operand with unsigned integer type is converted to the type of the
13
operand with signed integer type.
14
Otherwise, both operands are converted to the unsigned integer type
15
corresponding to the type of the operand with signed integer type.
Xenu schrieb:> char ch;>> do> {> ch = fgetc(stream);> putch(ch);> } while (ch != EOF);
Vielleicht steh ich aufn Schlauch, aber ich komm nicht drauf wo hier das
Problem liegt. :/
Ok, wahrscheinlich würd ich ne while- anstatt der do-Schleife verwenden
mit einem vorangestelltem "ch = fgetc(stream); " damit unter umständen
kein char geputtet wird wenn Mist aus dem stream gelesen wird.
Aber wo liegt hier der 8-Bit Fallstrick?
Duck & Weg :-)
lex schrieb:> Aber wo liegt hier der 8-Bit Fallstrick?
Zweierlei: Wenn "char" vorzeichenlos ist, und EOF wie üblich -1, dann
ist (ch != -1) nicht sonderlich erfolgversprechend. Und selbst wenn EOF
dank vorzeichenbehaftetem "char" funktioniert, dann ist bei 0xFF im
Input unerwartet Schluss.
Ausserdem findet sich anschliessend ein ebenso unerwartetes 0xFF am Ende
vom Output-Stream.
lex schrieb:> Aber wo liegt hier der 8-Bit Fallstrick?
Dass EOF den Wert -1 hat, der von getchar() als int zurückgegeben
wird. Der unterscheidet sich von allen möglichen Werten für char,
die da nämlich 0 ... 255 (bzw. -128 ... +127) sein können. Die
gültigen Zeichen decken damit die Bitmuster 0x0000 bis 0x00ff ab,
während EOF 0xffff (oder 0xffffffff auf einer 32-bit-Maschine) ist.
Wenn man das verfrüht auf 8 bit verkürzt, kann man das Zeichen 255
nicht mehr von EOF unterscheiden. Ist natürlich vor allem dann eine
Fratzenfalle, wenn der Eingabestrom beliebige Binärzeichen enthalten
darf.
Das kann man übrigens noch eine Stufe weiterspinnen. Auch wenn's selten
vorkommt, so darf bei einem Compiler durchaus char auch genauso groß wie
int sein. Dann gibt es gar keinen int-Wert, der außerhalb des
char-Bereichs liegt. Deshalb gibt's auch zusätzlich noch die Funktion
feof(), um das File-Ende zu erkennen.
Zugegeben kein sonderlich häufig auftretender "Fallstrick"...
Rolf Magnus schrieb:> Zugegeben kein sonderlich häufig auftretender "Fallstrick"...
Vor allem keiner, mit dem irgendwelche real existierenden Programme
zurecht kämen. Denn dann würde die recht verbreitete und m.W. als
zulässig angesehene Schleife
int c; while ((c = getchar()) != EOF) ...
ebenfalls in die Fritten gehen.
Nope. Wenn auf irgendeiner Kiste mangels Hardwaresupport eine "char"
Variable den gleichen Wertebereich wie "int" haben sollte, dann wird
getchar ziemlich sicher dennoch sowas Ähnliches wie 8-Bit Zeichen
retournieren. Und damit ist das Problem ebenfalls vom Tisch.
Rolf Magnus schrieb:> Deshalb gibt's auch zusätzlich noch die Funktion> feof(), um das File-Ende zu erkennen.
Nein. feof() und ferror() gibt es, damit man sich erkundigen kann,
welche der beiden Bedingungen (end-of-file oder Fehler) beim dem
Erhalt eines EOF eingetreten waren.
Allerdings dürfte die korrekte Implementierung eines getchar() und
zugehörigen Werts von EOF auf einer solchen Maschine, ähm, interessant
werden, wenn man die Möglichkeit vorsehen will, dass getchar()
tatsächlich alle möglichen Werte von `char' lesen könnte. ;-) Naja,
der Standard wird sich da mit sowas wie `host character set' oder
dergleichen rausreden.
Jörg Wunsch schrieb:> Wie man sieht, genügt es für all die "fiesen Fallen", sich mal seine> Compilerwarnungen durchzulesen (und sie natürlich auch einzuschalten).
Besser wäre es m.E. sogar, alle Warnungen als Fehler auszugeben. Dann
passiert so etwas erst gar nicht, weil man dann nämlich quasi gezwungen
wird, die "Warnungen" durchzulesen.
@Jörg:
Gibt der von dir verwendete Compiler die Meldung auch dann aus, wenn in
meinen Beispiel statt char[100] dort std::string steht, also:
Der Entwickler schrieb:> Besser wäre es m.E. sogar, alle Warnungen als Fehler auszugeben.
-Werror
Muss man natürlich auch einschalten. ;-) Mittlerweile kann ma ja
sogar mittels Pragma einzelnen Warnungen für eine Funktion
ausschalten, wenn es mal sinnvoll sein sollte, den Code lieber
nicht zu modifizieren.
> Gibt der von dir verwendete Compiler die Meldung auch dann aus, wenn in> meinen Beispiel statt char[100] dort std::string steht, also:
1
test.cc: In function `char* generateConfigString()':
2
test.cc:9: error: invalid conversion from `const char*' to `char*'
OK, das wolltest du wohl nicht wissen :), also mal den Typ der
Funktion geändert auf const char *. Nein, dann warnt er leider
nicht. Ist aber zugegebener Maßen schon eine etwas ältliche
GCC-Version hier.