Guten Nachmittag miteinander,
normalerweise programmiere ich meine AVRs in ASM. Irgendwo habe ich dann
mal gelesen in C sei das alles viel bequemer. Bevor ich einen
Programmiersprachenkrieg anzettel komme ich nun zu meiner Frage.
Es geht um folgenden Ausschnitt im GCC Tutorial hier auf der Seite:
1
i=PINA;
2
i=i&0x01;
3
if(i!=0){test1();}
4
5
if((PINA&0x01)!=0){test1();}// verkürzt
6
7
if(PINA&0x01){test1();}// nochmals verkürzt
Also bei der ersten Variante der Verkürzung verzichtet man einfach auf
die Variable i und checkt einfach ob das Byte <0 ist. Richtig?
Die zweite Variante verstehe ich jetzt nicht ganz.
PINA wird mit 0x01 geundet (Also logisch und genommen? Gemacht? Wie auch
immer)
Woher weiß der uC jetzt in welchem Fall die Funktion ausgeführt werden
soll? Immerhin steht nirgends, dass das Byte komplett 0 sein soll.
Eine If-Anweisung wird in C dann ausgeführt, wenn die Bedingung nicht
null ist. Die zweite Verkürzung wird also nur dann ausgeführt, wenn das
maskierte Bit den Wert 1 hat.
Das Problem dabei ist der völlig unmathematische Entwurf von C. C kennt
keine boolean Variablen und somit muß eine Verfügung im Stile eines
Ordre de Mufti her: Null ist false und alles ungleich Null ist true,
Basta.
Deine beiden Ausdrücke sind in ihrer Wirkung gleich. Der erste,
ausdrückliche ist eigentlich der mathematisch korrekte, aber du wirst in
deinem Leben massenweise auch den zweiten finden, weil der ein paar
Zeichen weniger hat. Eingeschworene C-Leute mögen sowas bevorzugt,
genauso wie sie auch den Fragezeichenoperator einem If..Else Konstrukt
vorziehen. Nachdem der Compiler das alles durch seine Mangel geleiert
hat, kommt in beiden Fällen der gleiche Code heraus. Die Version mit dem
? ist bloß kryptischer und unleserlicher, weswegen sie bei vielen
C-Programmierern den Vorzug hat.
Klaro?
W.S.
W.S. schrieb:> Das Problem dabei ist der völlig unmathematische Entwurf von C.
Er ist aber sehr maschinennah:
1
if((PINA&0x01)!=0){test1();}// verkürzt
ich muss nicht ein Register (PINA) mit 1 ver-unden und dann hinterher
nochmal mit 0 vergleichen. Der Schon nach der Berechnung des Ausdrucks
PINA&1 stehen die Flags im Prozessor richtig und können ausgewertet
werden. Ein zusätzlicher Vergleich auf "ungleich Null" ist unnötig.
W.S. schrieb:> Die Version mit dem ? ist bloß kryptischer und unleserlicherVerilog kann das auch. Ich hätte da den Beitrag
Beitrag "Re: Entscheidungsoperation IF/else oder ?" mit dem Link auf den
Fragezeichenoperator... ;-)
Was ich hier einmal betonen möchte ist, dass in der Compiler hier in
allen drei Fällen vermutlich den selben Code generieren würde (und
Ausnahmen bestätigen die Regel).
Das bedeutet dass man in C den Code ruhig so schreiben kann, wie es am
besten lesbar ist.
So würde z.B. wenn es die Architektur unterstützt z.B. ein
Bittest-Befehl erzeugt werden (weiss nun nicht, ob ein AVR so was hat),
wenn dieser Vorteile gegenüber einem "AND" Befehl hat.
(Randbedingungen:
1. der Optimizer muss aktiv sein (z.B. -O2)
2. Die Inhalt der Variable "i" wird später nicht für andere Dinge
verwendet)
ZigZeg
Lothar Miller schrieb:> Er ist aber sehr maschinennah:if ( (PINA & 0x01) != 0 ) { test1(); } //> verkürzt> ich muss nicht ein Register (PINA) mit 1 ver-unden und dann hinterher> nochmal mit 0 vergleichen. Der Schon nach der Berechnung des Ausdrucks> PINA&1 stehen die Flags im Prozessor richtig und können ausgewertet> werden. Ein zusätzlicher Vergleich auf "ungleich Null" ist unnötig.
Nee, mein Lieber.
Du schreibst eben wie ein C-Programmierer, was einem mathematisch
denkenden Zeitgenossen als schlichter Unsinn erscheint, weil man dazu
eben die vielen "Ordres de Mufti" beachten muß, um den Hintersinn zu
erfassen.
Ob auf einer konkreten Maschine das verunden einer Variablen mit einer
Konstanten tatsächlich irgendwelche Flags setzt oder nicht, kann man
allein im Manual zu der betreffenden Architektur nachlesen. In ganz
vielen Fällen ist das so, aber aus mathematischer Sicht kommt als
Ergebnis NUR eine Zahl heraus, die der Operation entspricht - und die
muß man, wenn man ein logisches Ergebnis haben will, eben anschließend
einem Vergleich unterziehen.
Und nochwas: C ist nicht "aber sehr maschinennah". Vergleiche doch mal
die Ausdrucksmöglichkeiten von C mit der Hardware in einem kleinen
PIC16. Dort findest du ausdrückliche Befehle zum Arbeiten mit einzelnen
Bits. Man könnte dort schreiben
if (PortA.4) DoSomething();
( in Assembler
BTFSC PortA,4
CALL DoSomeThing
)
wenn das in C so vorgesehen wäre. Ist es aber nicht. Stattdessen braucht
man ellenlange Headerdateien, um mittels Präprozessor etwas
vergleichbares hinzukriegen. Aber Präprozessor-Akrobatik ist was anderes
als die Syntax von C.
W.S.
W.S. schrieb:> wenn das in C so vorgesehen wäre. Ist es aber nicht. Stattdessen braucht> man ellenlange Headerdateien, um mittels Präprozessor etwas> vergleichbares hinzukriegen. Aber Präprozessor-Akrobatik ist was anderes> als die Syntax von C.
naja, die Header braucht es in erster Linie, damit ich statt "Bit 4"
einen symbolischen Namen nehmen kann. Wenn ich "Bit 4" meine sage ich in
C einfach
Nob schrieb:
>Irgendwo habe ich dann mal gelesen in C sei das alles viel bequemer.
Du beherrscht ASM? Dann bleibe dabei -warum willst Du Dir so eine
Gaukelsprache wie C beibringen?
Dieselwolf schrieb:> Nob schrieb:>>Irgendwo habe ich dann mal gelesen in C sei das alles viel bequemer.>> Du beherrscht ASM? Dann bleibe dabei -warum willst Du Dir so eine> Gaukelsprache wie C beibringen?
Stimmt, über den Tellerrand zu schauen ist auch wirklich völliger
Unsinn. Schuster bleib bei Deinen Leisten!
ZigZeg
W.S. schrieb:> Ob auf einer konkreten Maschine das verunden einer Variablen mit einer> Konstanten tatsächlich irgendwelche Flags setzt oder nicht, kann man> allein im Manual zu der betreffenden Architektur nachlesen.
Kennst du eine, bei der das nicht der Fall wäre?
C ist in erster Linie so entworfen worden, dass es die Fähigkeiten
der PDP-11-CPU bestmöglich in eine Sprache abbilden konnte, die mehr
abstrahiert als Assembler, ohne dass man dabei zwangsweise die
Effektivität einbüßen muss. Das sieht man an den Bitoperationen
hier (zum Vergleich: Pascal als Lehrsprache hat sowas nicht¹), aber
auch an Konstrukten wie *cp++. Hätte man derartige Konstrukte nicht
irgendwie in eine höhere Programmiersprache rübergerettet, wären die
Betriebssysteme weiter aufwändig (und fehleranfällig) in Assembler
gehackt worden.
Andererseits dürfte C natürlich heutzutage den umgekehrten Effekt auf
viele CPUs haben: man entwirft sie so, dass sich C möglichst effektiv
drauf implementieren lässt. Das ist für den AVR dokumentiert, und
die Cortexe haben ja ebenfalls als Entwurfsziel, dass man sie komplett
ohne (expliziten) Assemblercode programmieren kann.
Sich darüber zu streiten, ob das nun gut ist oder nicht, ist ziemlich
müßig.
¹) Bevor die Programmiersprachenglaubenskrieger jetzt alle auffahren:
ja, aktuelle Pascal-Dialekte haben sowas. Das Wirthsche Pascal, etwa
zur gleichen Zeit wie C entstanden, nur mit einem völlig anderen
Fokus (Lehrsprache vs. Betriebssystemimplementierung) hatte es jedoch
nicht.
Also ich verwende ASM. Ich denke das kann man nicht mit beherrschen
gleichsetzen wobei ich halbwegs gut klar komme.
C wirkt wesentlich kompakter wobei ich ab Beispiel der If Abfrage soeben
festgestellt habe, dass dies auf kosten der Verständlichkeit passiert.
Lothar Miller schrieb:> Schon nach der Berechnung des Ausdrucks> PINA&1 stehen die Flags im Prozessor richtig und können ausgewertet> werden.
Wenn es Flags gibt. Bei MIPS (wie PIC32) gibt es sie nicht...
> Ein zusätzlicher Vergleich auf "ungleich Null" ist unnötig.
...weshalb ein bedingter Sprungbefehl zwei Register vergleicht und
abhängig davon springt.
Rufus Τ. Firefly schrieb:> Kai S. schrieb:>> Schuster bleib bei Deinen Leisten!>> Deine_m_. Der Leisten.
Natürlich "der" Leisten. Aber ein Schuster mit nur einem Leisten wird
wohl kein erfolgreicher Schuster sein.
Als Sprichwort wird aber wohl beides verwendet - interessant!
Jörg Wunsch schrieb:> W.S. schrieb:>> Ob auf einer konkreten Maschine das verunden einer Variablen mit einer>> Konstanten tatsächlich irgendwelche Flags setzt oder nicht, kann man>> allein im Manual zu der betreffenden Architektur nachlesen.>> Kennst du eine, bei der das nicht der Fall wäre?
Mal abgesehen davon, dass es ziemlich schnurz ist, die eine Maschine
genau verundet: Ja, es gibt auch Machinen, bei der ein AND keine
Flags setzt; etwa auf Maschinen, die (effektiv) kein PSW haben. Ein
Beispiel ist Infineon TriCore.
A. K. schrieb:> ...weshalb ein bedingter Sprungbefehl zwei Register vergleicht und> abhängig davon springt.
Das weiß der Compiler und fügt diesen Vergleich dann eigenhändig ein.
W.S. schrieb:> Man könnte dort schreiben> if (PortA.4) DoSomething();
Solche prozessorabängigen Erweiterungen gabs schon für den 8051. Und der
Lohn? Bei einem Prozessor- oder nur Compilerwechsel viel Handarbeit.
> ( in Assembler> BTFSC PortA,4> CALL DoSomeThing> )> wenn das in C so vorgesehen wäre. Ist es aber nicht.
Ein brauchbarer C-Compiler macht aber genau das aus dem "Maskencode".
Welche Sprache ist deiner Meinung nach geeigneter und/oder orthogonaler?
Jörg Wunsch schrieb:> Kennst du eine, bei der das nicht der Fall wäre?
Klassische Statusregister mit Flags sind mitnichten selbstverständlich.
Weil Flags singuläre Ressourcen sind, die im Highend-Bereich erheblichen
Aufwand mit sich bringen.
Neben MIPS war da beispielsweise DEC Alpha eine Architektur ohne Flags.
PowerPC wiederum hat statt dessen einen separaten Satz von 8 Registern
für Status-Resultate, bei Intel IA64 sind es noch ein paar mehr. HPs PA
übersprang den Folgebefehl anhängig vom Resultat eines Vergleichs.
Die x86 Implementierungen treiben grossen Aufwand, um trotz klassischem
Statusregister nicht dauernd über die eigenen Füsse zu stolpern, dennoch
entstehen nicht selten unerwünschte und bremsende Abhängigkeiten.
In der Anfangsphase der 8-Bit Mikroprozessoren gab es welche, die
allenfalls ein Carry-Flag hatten, aber bei Null- oder Vorzeichentests im
Sprung direkt den Akku überprüften (RCA 1802, NS SC/MP).
W.S. schrieb:> Die Version mit dem> ? ist bloß kryptischer und unleserlicher, weswegen sie bei vielen> C-Programmierern den Vorzug hat.
Du wirst keinen Programmierer finden, der völlig auf "if" verzichtet.
Ein guter Programmierer setzt immer jeweils den Operator ein, der für
die konkrete Aufgabe am besten ist.
Der "?" Operator liefert im Gegensatz zum "if" einen Wert zurück. Das
kann mit "if" dann wirklich sehr umständlich und kompliziert werden und
auch der erzeugte Code wird größer und langsamer.
Sind dagegen nur verschiedene Anweisungen auszuführen, wird man dafür
kaum den "?" Operator benutzen.
Peter Dannegger schrieb:>> ? ist bloß kryptischer und unleserlicher, weswegen sie bei vielen>> C-Programmierern den Vorzug hat.>> Du wirst keinen Programmierer finden, der völlig auf "if" verzichtet.
Dafür sehr viele, die den ternären Operator meiden wie der Teufel das
Weihwasser und nur benutzen, wenn es gar nicht anders geht. Aber das
passt halt nicht in W. S.' Weltbild.
W.S. schrieb:> Die Version mit dem> ? ist bloß kryptischer und unleserlicher, weswegen sie bei vielen> C-Programmierern den Vorzug hat.
Im Gegenteil: Die (richtige) Anwendung des ?-Operators fördert die
Lesbarkeit von C-Programmen. Man kann in C manches kurz und knackig
formulieren, wofür man in anderen Sprachen eine ganze Litanei
runtersabbeln muss.
Natürlich kann man in C sehr kryptisch programmieren. Das ist aber
nicht unbedingt das Ziel des gemeinen C-Programmierers.
Du ratterst hier daher lediglich Vorurteile runter, die man schon in den
70er Jahren gehört hat, als in Deutschland noch kaum jemand C überhaupt
kannte. Wie Du am Erfolg von C erkennen kannst (immerhin werden
mittlerweile damit ganze Betriebssysteme geschrieben), bist Du mit
Deinen Argumenten irgendwo hinter irgendeinem Planeten auf diesem
Sonnensystem stehengeblieben.
Was wäre denn für Dich die ideale Programmiersprache?
Hier hast Du eine klitzekleine Auswahl (Vorsicht, Humor nützlich!):
http://web.archive.org/web/20130930062739/http://helloworldsite.he.funpic.de/hello.htm
Kai S. schrieb:> Das bedeutet dass man in C den Code ruhig so schreiben kann, wie es am> besten lesbar ist.
Dein Wort in Gottes Ohr. So mancher C-Programmierer ist der Meinung, er
müsste kryptischen Scheiß zusammenschreiben, obwohl er gerade gar nicht
am IOCCC teilnimmt.
Frank M. schrieb:
>Wie Du am Erfolg von C erkennen kannst (immerhin werden>mittlerweile damit ganze Betriebssysteme geschrieben), bist Du mit>Deinen Argumenten irgendwo hinter irgendeinem Planeten auf diesem>Sonnensystem stehengeblieben.
Woher kommt denn der "Erfolg"?
Dadurch, daß an den Lehreinrichtungen nichts Anderes unterrichtet
wird?
Es ist nicht immer das Bessere, das sich durchsetzt.
Boris schrieb:
>Ich schreibe (und lese) jedenfalls lieber....
Bis Du den Quelltext nach 3-4 Jahren mal wieder in die Hand bekommst
und Änderungen durchführen sollst.
Dann kommt der Moment, wor Du denkst: "Ach hätte ich das doch
übersichtlich geschrieben!"
Die Zeit, die Du da eingespart hattest, gibst Du dann doppelt drauf.
Dieselwolf schrieb:> Boris schrieb:>>Ich schreibe (und lese) jedenfalls lieber....>> Bis Du den Quelltext nach 3-4 Jahren mal wieder in die Hand bekommst
Gerade dann ist das schneller, einfacher und übersichtlicher.
Der ternäre Operator ist ein Operator wie jeder andere auch. Kein Grund
ihn nicht einzusetzen wo er Sinn macht und einem das Leben leichter
machen kann.
Karl-Heinz behauptete:
>Gerade dann ist das schneller, einfacher und übersichtlicher.
Siehst Du: Für Dich ist die eine Variante übersichtlicher, für einen
Anderen die Andere.
Ich bin aus dem Holz: Quelltexte so ausführlich und eindeutig, wie es
die Syntax der Sprache, die man benutzen muß zulässt.
Wenn man die Wahl hat: Eine Sprache benutzen, die man selbst am
Vernünftigsten hält.
Dieselwolf schrieb:> Karl-Heinz behauptete:>>Gerade dann ist das schneller, einfacher und übersichtlicher.>> Siehst Du: Für Dich ist die eine Variante übersichtlicher, für einen> Anderen die Andere.
Was findest du übersichtlicher
1
if(a){
2
if(b)
3
func(1,2);
4
else
5
func(1,4);
6
}
7
else{
8
if(b)
9
func(3,2);
10
else
11
func(3,4);
12
}
Die Absicht ist klar:
Wenn a gegeben ist, dann soll die Funktion mit einem Wert von 1 als
erstem Paramater aufgerufen werden, ansonsten mit einem Wert von 3
Wenn b wahr ist, dann soll die Funktion mit einem Wert von 2 als erstem
Paramater aufgerufen werden, ansonsten mit einem Wert von 4.
Obiges lässt sich mit dem ternären Operator schreiben als
1
func(a?1:3,
2
b?2:4
3
);
und drückt kurz und präzise genau das gewünschte aus. Und zwar ohne das
man alle möglichen Fallunterscheidungen ausformulieren muss.
Nimm noch eine dritte Bedingung mit dazu und du ertrinkst in if-s und
else.
Man kann das natürlich schreiben als
1
if(a)
2
arg1=1;
3
else
4
arg1=3;
5
6
if(b)
7
arg2=2;
8
else
9
arg2=4;
10
11
func(arg1,arg2);
und hat damit 2 temporäre Variablen, die eigentlich keiner braucht. An
die Einfachheit und Übersichtlichkeit, die durch die Verwendung des
ternären Operators entsteht, kommt aber auch diese Variante nicht ran.
Mir kommt das eher vor, wie auf gut österreichisch:
Wozu braucht man das? Das habn wir noch nie so gemacht. Da könnt ja
jeder kommen!
Der ternäre Operator ist ein Operator wie jeder andere auch. Er hat
seine Bedeutung. In manchen Fällen erleichtert er das Leben. In manchen
Fällen ist er nicht angebracht. Ja, man kann sich damit auch in den Fuss
schiessen. Aber nur weil man sich damit einen Finger abtrennen kann, ist
eine Kreissäge noch lange kein verteufelungswürdiges Werkzeug. Auf jeden
Fall ist allerdings ein Taschenmesser kein Ersatz dafür, nur weil es
damit schwerer ist, sich die Hand abzutrennen.
Frank M. schrieb:> Das else ist überflüssig:> if(isRunning)> {> return GetNextValue();> }> return sDefaultValue;>> ;-)
Ob das dann besser lesbar ist...
...aber ich muß zugeben, mir ist auch gerade langweilig genug, daß ich
darüber diskutieren würde.
Walter Tarpan schrieb:> Frank M. schrieb:>> Das else ist überflüssig:>> if(isRunning)>> {>> return GetNextValue();>> }>> return sDefaultValue;>>>> ;-)>> Ob das dann besser lesbar ist...
Finde ich schon. Am Ende einer non-Void-Funktion sollte immer ein return
stehen. Dem wird meine Variante eher gerecht ;-)
Aber man könnte herrlich darüber streiten, ob mehrere Ausgänge innerhalb
einer Funktion überhaupt sinnvoll sind.
> ...aber ich muß zugeben, mir ist auch gerade langweilig genug, daß ich> darüber diskutieren würde.
Geht mir genauso ;-)
Es ist meist ein Vorteil und selten ein Nachteil, wenn man doppelt
vorkommende Code-Passagen meidet. Für lange Passagen gilt das sowieso,
oft aber auch für kurze. Mit dem ternären Operator lässt sich doppelter
Code in vielen Fällen vermeiden.
Beispiel 1 (ohne ternären Operator, nur zur Motivation):
Schlecht:
1
array[n+i]=array[n+i]*2;
Gut:
1
array[n+i]*=2;
Denn hier sieht man sofort, dass die Quelle und das Ziel dasselbe
Array-Element ist.
Beispiel 2:
Schlecht:
1
if(bedingung)
2
{
3
array[n+i]=x;
4
}
5
else
6
{
7
array[n+i]=y;
8
}
Gut:
1
array[n+i]=bedingung?x:y;
Denn hier sieht man sofort, dass dem Array-Element (und nur diesem) auf
jeden Fall ein Wert zugeweisen wird. Nur die Quelle ist von der
Bedingung abhängig, nicht aber das Ziel. Deswegen sollte dieses
außerhalb des bedingt ausgeführten Codes stehen.
Bspiel 3:
Schlecht:
1
if(bedingung)
2
{
3
returnx;
4
}
5
else
6
{
7
returny;
8
}
Gut:
1
returnbedingung?x:y;
Denn hier sieht man sofort, dass die Funktion an dieser Stelle endet,
ohne Wenn und Aber. Lediglich der Rückgabewert ist an eine Bedingung
geknüpft, nicht aber die Tatsache, dass die Funktion an diese Stelle zum
Aufrufer zurückkehrt. Deswegen sollte das return außerhalb des bedingt
ausgeführten Codes stehen.
Neben der Verbesserung der Lesbarkeit und des logischen Aufbaus eines
Programms gibt es aber auch Fälle, wo der ternäre Operator sogar
zwingend erforderlich ist oder nur sehr umständlich durch andere
Konstrukte ersetzbar ist. Dazu gehört bspw. die bedingte Intialisierung
von Variablen.
Weil er so nützlich ist, gibt es ihn (teilweise mit anderer Syntax) in
den meisten gängigen Programmierprachen:
http://en.wikipedia.org/wiki/%3F:
Einfach zu sagen "Will ich nicht, brauch ich nicht, deswegen sofort in
den Müll damit" ist deswegen ein wenig zu kurz gedacht :)
Lothar Miller schrieb:> Ein brauchbarer C-Compiler macht aber genau das aus dem "Maskencode".
Ein guter XYZ-Compiler ist mittlerweile so schlau (genauer: seine
Verfasser waren so schlau), daß er die hinter einer Formulierung
steckende Absicht des Schreibers erkennt und nach Abwägung der
Möglichkeiten der Zielhardware sich den dafür optimalen Maschinencode
zusammenbastelt. Man sehe sich mal an, was z.B. der Keil aus einem
C-Quellcode macht, wenn man ihn nur ordentlich optimieren läßt. Es ist
wirklich erstaunlich.
Dieselwolf schrieb:> Wenn man die Wahl hat: Eine Sprache benutzen, die man selbst am> Vernünftigsten hält.
Nachtrag meinerseits: ..sofern diese Sprache für die Zielplattform
verfügbar ist. Sonst muß man notgedrungen in irgendeinen sauren Apfel
beißen.
Karl Heinz schrieb:> Was findest du übersichtlicher> if( a ) {> if( b )> func( 1, 2 );> else> func( 1, 4 );> }> else {> if( b )> func( 3, 2 );> else> func( 3, 4 );> }
Zu allererst fände ich es übersichtlicher, die zugrundeliegenden Blöcke
erkennen zu können, ohne erst nach den öffnenden Klammern suchen zu
müssen. Schreibst du etwa immer so?
Als nächstes fände ich es übersichtlicher, sich echte Gedanken über den
geschriebenen Code zu machen, bevor man ihn in die Tasten haut.
Üblicherweise bauschen temporäre Variablen weniger auf als eine
Breitseite von Funktionsaufrufen und überflüssige Klammern.
etwa so:
if (a)
{ if (b) func(1, 2); else func(1, 4);
}
else
{ if (b) func(3, 2); else func(3, 4);
}
oder etwa so:
int tmpa, tmpb;
tmpa = 3; if (a) tmpa = 1;
tmpb = 4; if (b) tmpb = 2;
func(tmpa, tmpb);
Nochmal dasselbe, diesmal von unserem Programmier-Begeisterten:
Yalu X. schrieb:> Schlecht:> if(bedingung)> {> array[n+i] = x;> }> else> {> array[n+i] = y;> }>> Gut:> array[n+i] = bedingung ? x : y;
und noch besser:
int tmp;
tmp = y;
if (bedingung) tmp = y; //Kommentar, warum
array[beliebig komplizierte Berechnung] = tmp;
Im Grunde geht es nur um wirklich überflüssige Dinge in C. Der ?
Operator ist de facto überflüssig und trägt nur zur Unleserlichkeit bei,
genauso wie der Komma-Operator. Sowas wie 'typedef' übrigens auch - oder
hat jemand dazu eine wirklich stichhaltige Begründung für 'typedef'?
W.S.
W.S. schrieb:> Sowas wie 'typedef' übrigens auch - oder> hat jemand dazu eine wirklich stichhaltige Begründung für 'typedef'?
Man merkt, daß Du mit C nicht arbeitest, sondern es aus recht
praxisferner Sicht kritisierst. Offensichtlich hast Du noch nie etwas
mit Funktionspointern zu tun gehabt.
Deine Definition von "lesbarkeit" weicht auch stark von meiner ab.
Den C-Stil von Karl Heinz finde ich nun auch nicht besonders schön (für
mich gehören zusammengehörige geschweifte Klammern in die gleiche
Spalte, und nach öffnenden und vor schließenden runden Klammern gehört
kein Whitespace, vor öffnende dann, wenn es sich nicht um einen
Funktionsaufruf handelt) -- aber das sind rein kosmetische Differenzen,
die sich bei C-Anwendern mit Erfahrung individuell einspielen und
anpassen. Was die grundliegenden Ansichten zu C betrifft, sehen wir die
Dinge recht ähnlich.
Ich verdiene mir meinen Lebensunterhalt seit bald 25 Jahren mit C, und
ich habe schon ziemlich viel fremden C-Code gesehen, insofern bilde ich
mir ein, ein gewisses Gefühl für Lesbarkeit von C-Code zu haben.
Unnötige Zwischenvariablen, mehrere Anweisungen in einer Zeile etc. sind
das genaue Gegenteil von lesbarem Code. Code, der so formatiert ist, wie
Deiner, landet bei mir automatisch in einem "Beautifier", um eine
lesbare und halbwegs verbreiteten Konventionen übliche Formatierung zu
erhalten.
W.S. schrieb:> Zu allererst fände ich es übersichtlicher, die zugrundeliegenden Blöcke> erkennen zu können, ohne erst nach den öffnenden Klammern suchen zu> müssen. Schreibst du etwa immer so?
Ist eine reine Konventionssache.
Wenn mein Arbeitgeber die Klammern in einer neuen Zeile will, kriegt er
sich auch dort. Wenn ich für mich schreibe, ist das je nach Projekt
unterschiedlich. Lange Jahre hab ich die öffnende Klammer ans Zeilenende
geschrieben, dann wieder ein paar Jahre in eine neue Zeile. Letzten
Endes macht es für mich wenig Unterschied. Die schliessende Klammer
steht in der Spalte auf jeden Fall unter der 'Anweisung' in der die
öffnende steht. Ob da jetzt die öffnende Klammer alleine steht, oder ob
die in dieser Zeile am Zeilenende steht, ist mit persönlich mitlerweile
egal. Ich finde in beiden Formatierungen optisch die jeweils öffnende
Klammer recht zuverlässig - das hat sich längst automatisiert ohne dass
ich groß darüber nachdenken muss.
Viel wichtiger ist die konsistente Einrückung.
> etwa so:> if (a)> { if (b) func(1, 2); else func(1, 4);> }> else> { if (b) func(3, 2); else func(3, 4);> }
Wer mir allerdings so etwas als 'guten Stil' unterjubeln will, den nehm
ich nicht mehr ernst. Sorry.
Dein einziges Argument gegen den ?: Operator ist: Ein ? in einem Code
kommt mir seltsam vor, daher verteufle ich das erst mal.
W.S. schrieb:> und noch besser:>> int tmp;> tmp = y;> if (bedingung) tmp = y; //Kommentar, warum> array[beliebig komplizierte Berechnung] = tmp;
Finde ich nicht. Wie man sieht schleichen sich da auch leicht Fehler
ein. (In der dritten Zeile meintest Du vermutlich 'x' statt 'y')
Habe ich aber erst beim 2ten lesen gesehen, es ist halt unübersichtlich.
W.S. schrieb:> Sowas wie 'typedef' übrigens auch - oder> hat jemand dazu eine wirklich stichhaltige Begründung für 'typedef'?
Wie willst du sowas wie int16_t definieren, wenn du keine Typedefs hast?
#define ist zwar im Prinzip möglich, aber ich stimme mit Stroustrup
dahingehend überein, dass man den Präprozessor so sparsam wie möglich
verwenden sollte. Dieser Mischmasch aus zwei sehr unterschiedlichen
Sprachen (Präprozessor / C[++]) ist nicht sauber und hinsichtlich
Namescopes grad in grossen Projekten recht problematisch.
Unglücklich bin ich über typedef nur, was die syntaktische Seite angeht.
In die Grammatik passen sie nicht sauber hinein. Aber das wurde
letztlich schon mit der Erfindung von C verbockt, wo typedefs erst
nachträglich hinzugefügt wurden.
foo schrieb:> Tja, jeder wie er will.> Bei meinen Funktionen gibt es genau einen Returnpfad.> uint8 foo(void)> {> uint8 r;>> if(bedingung)> {> r = 1u;> }> else> {> r = 2u;> }>> return(r);> }
Ja, kann man zur Religion erheben. Man kann sich auch auf den Standpunkt
stellen, dass man erst Predicates teste (ganz modernes Zeug ...), bevor
es im eigentliche Code ohne Beiwerk zur Sache geht.
1
uint8_tbla(inti){
2
// Check predecates - see function documentation for contract
foo schrieb:> Bei meinen Funktionen gibt es genau einen Returnpfad.
Besonders bei größeren Funktionen mit vielen Bedingungen kann das
schnell ein ausgesprochen unübersichtliches Geschachtel werden.
foo schrieb:> Tja, jeder wie er will.> Bei meinen Funktionen gibt es genau einen Returnpfad.>>
1
>uint8foo(void)
2
>{
3
>uint8r;
4
>
5
>if(bedingung)
6
>{
7
>r=1u;
8
>}
9
>else
10
>{
11
>r=2u;
12
>}
13
>
14
>return(r);
15
>}
Noch besser, weil kürzer:
1
uint8foo(void)
2
{
3
uint8r=2u;
4
5
if(bedingung)
6
{
7
r=1u;
8
}
9
10
return(r);
11
}
Jörg Wunsch schrieb:> Besonders bei größeren Funktionen mit vielen Bedingungen kann das> schnell ein ausgesprochen unübersichtliches Geschachtel werden.
Korrekt. Bei kleineren Funktionen à la get_value() kann man es aber
schon sehr gut so machen.
Mark Brandis schrieb:> Korrekt. Bei kleineren Funktionen à la get_value() kann man es aber> schon sehr gut so machen.
Also genau da, wo es sich nicht lohnt. ;-)
A. K. schrieb:> Also genau da, wo es sich nicht lohnt. ;-)
Was soll sich da nicht lohnen? Obiges Beispiel ist extrem gut lesbar.
Und wartbar. Und der MISRA Checker freut sich. Und überhaupt. :-)
Suboptimal. Wenn bedingung gegeben ist, werden überflüssigerweise 2
Zuweisungen gemacht.
(Vielleicht ist der gcc so schlau, dass er in dem obigen
Trivial-Beispiel die 2u-Zuweisung intern in ein "else" packt. Aber davon
würde ich nicht unbedingt ausgehen)
MISRA C 2004:
14.7 (req): A function shall have a single point of exit at the end of
the function.
Wenn man für die Industrie nach gewissen Prozessen und zur Erreichung
einer festgelegten SW-Qualität fertigt bleibt oft nicht viel
'Wahlmöglichkeit' ;-)
Mark Brandis schrieb:> Was soll sich da nicht lohnen? Obiges Beispiel ist extrem gut lesbar.> Und wartbar. Und der MISRA Checker freut sich. Und überhaupt. :-)
Bei einer solchen Zwergfunktion wie oben ist es für Verständnis und
Überblick ziemlich irrelevant. Das lohnt erst wirklich bei grösseren
Funktionen, bei denen man sich sonst stunden/tagelang wundert, weshalb
der Aufräumcode am Schluss der Funktion manchmal ignoriert wird.
Robert K. schrieb:> MISRA C 2004:>> 14.7 (req): A function shall have a single point of exit at the end of> the function.
Ich muss da Jörg schon Recht geben. Wenn man das bei größeren Funktionen
ohne wenn und aber durchhalten will, dann muss man manchmal ziemliche
Verrenkungen machen. Man schleppt dann den Return-Code durch alle
möglichen Fallunterscheidungen (evtl. über mehrere Ebenen) mit.
Ein vorzeitiges bedingtes return kann da schon mal Klarheit im Code
schaffen. Danach kanns nämlich meist unbedingt (d.h. auf höchster Ebene
in der Funktion) weitergehen.
Frank M. schrieb:> Robert K. schrieb:>> MISRA C 2004:>>>> 14.7 (req): A function shall have a single point of exit at the end of>> the function.>> Ich muss da Jörg schon Recht geben. Wenn man das bei größeren Funktionen> ohne wenn und aber durchhalten will, dann muss man manchmal ziemliche> Verrenkungen machen. Man schleppt dann den Return-Code durch alle> möglichen Fallunterscheidungen (evtl. über mehrere Ebenen) mit.
Dann sind vielleicht die Funktionen auch einfach zu groß. Wer eine
riesige Funktion hat, macht meist schon was falsch.
Frank M. schrieb:> Suboptimal. Wenn bedingung gegeben ist, werden überflüssigerweise 2> Zuweisungen gemacht.
Tragisch. Das sind schon wieder Nanosekunden, die verloren gehen.
Und der gcc ist natürlich - wie so ziemlich jeder Compiler der letzten
zwanzig Jahre - clever genug, das zu optimieren.
Frank M. schrieb:> Ein vorzeitiges bedingtes return kann da schon mal Klarheit im Code> schaffen. Danach kanns nämlich meist unbedingt (d.h. auf höchster Ebene> in der Funktion) weitergehen.
Wobei man dann mit C++ besser dran ist. Weil man Aufräumcode in
Destruktoren lokaler Daten verfrachten kann, statt sie an jenes Ende der
Funktion zu packen, das beim vorzeitigen C-return links liegen gelassen
wird.
Michael schrieb:> Und der gcc ist natürlich - wie so ziemlich jeder Compiler der letzten> zwanzig Jahre - clever genug, das zu optimieren.
Die Welt besteht nicht nur aus gcc.
Du hast leider keine Ahnung, wie (teilweise absichtlich!) dumm manche
Compiler wirklich sind. Schau Dir den XC8 PIC-Compiler an. Der streut
nicht nur überflüssige Befehle in den erzeugten Code, sondern übersetzt
Divisionen und Modula-Operationen mit Zweierpotenzen nicht als Schiebe-
bzw. Maskierungsbefehle, sondern fängt tatsächlich an, echte
int16-Divisionen draus zu machen. Da wird man ja regelrecht gezwungen,
dass man den Code cryptischer formuliert, damit dem Compiler auf die
Sprünge geholfen werden kann.
(Aufgefallen bei der Portierung von IRSND (dem Pendant von IRMP) auf
PIC XC8)
A. K. schrieb:> Wobei man dann mit C++ besser dran ist. Weil man Aufräumcode in> Destruktoren lokaler Daten verfrachten kann, statt sie an jenes Ende der> Funktion zu packen, das beim vorzeitigen C-return links liegen gelassen> wird.
Da hast Du allerdings recht. Bei einem vorzeitigen return kann man schon
mal vergessen, irgendwas wieder wegzuräumen. Und wenn man es
tatsächlichn machen muss, dann stehts doppelt in der Funktion. Ist
unschön und erhöht den Pflegeaufwand, stimmt.
Ausschliesslich für kleine Mikrocontroller bestimmte Compiler sind bei
Optimierung nicht annähernd auf dem Standard von GCC. Einerseits weil
hinter GCC 3 Jahrzehnte Optimierung für Highend-Maschinen stecken.
Andererseits weil zu viel unerwartete Optimierung im Kundenkreis
unerwünscht ist.
Auch unter den Kennern der Freiheitsgrade von C sieht es beispielsweise
nicht jeder gerne, wenn die teure Division, die er eigens vor der
Interrupt-Sperre in den Code einstreute, vom GCC seelenruhig dorthin
verschoben wird, wo der Quotient benötigt wird. Genau mitten in die
gesperrte Zeit hinein.
Frank M. schrieb:> Du hast leider keine Ahnung, wie (teilweise absichtlich!) dumm manche> Compiler wirklich sind.
Und die eine Zuweisung bringt dich jetzt um?
Vor allem immer und überall. So daß du die Nanosekunde nicht nur an der
einen so hochkritischen Stelle einsparen willst, sondern auch in dem
Programmteil, der sich gerade langweilt, bis der Benutzer mal wieder was
eingegeben hat?
Frank M. schrieb:> Da hast Du allerdings recht. Bei einem vorzeitigen return kann man schon> mal vergessen, irgendwas wieder wegzuräumen. Und wenn man es> tatsächlichn machen muss, dann stehts doppelt in der Funktion. Ist> unschön und erhöht den Pflegeaufwand, stimmt.
Da ich mich nicht mit MISRA rumschlagen muss bin ich in solchen Fällen
auch so frei, ab und an mal ein goto unterzubringen. Nämlich von
Mittendrin zum Aufräumschwanz der Funktion. Und da dort ein Label steht
sollte dann auch der dümmste merken, dass irgendwo ein goto sein könnte.
Michael schrieb:> Frank M. schrieb:>> Du hast leider keine Ahnung, wie (teilweise absichtlich!) dumm manche>> Compiler wirklich sind.>> Und die eine Zuweisung bringt dich jetzt um?
Nein, das hat konkret mit der einen Zuweisung nichts zu tun. Aber mit
der Einstellung des Programmierers zum Compiler.
Viele PC-Programme werden von Version zu Version immer langsamer. Warum?
Weil die Programmierer immer undisziplinierter werden: "Das macht schon
der Compiler". Oder: "Wer heute keine 4GB im PC hat, ist halt draußen!".
> Vor allem immer und überall. So daß du die Nanosekunde nicht nur an der> einen so hochkritischen Stelle einsparen willst, sondern auch in dem> Programmteil, der sich gerade langweilt, bis der Benutzer mal wieder was> eingegeben hat?
Es geht nicht um eine Nanosekunde, sondern um zigtausend mal 1
Nanosekunde.
Frank M. schrieb:> Viele PC-Programme werden von Version zu Version immer langsamer. Warum?> Weil die Programmierer immer undisziplinierter werden:
Nö. Nicht auf der Ebene. Nicht solcher Kleinscheiss mördert die
Performance, sondern hinzu kommende Funktionalität, die ihren Preis hat.
Die Performance geht dann im Vorfeld verloren, bei Leuten, die die
Kosten nicht rechtzeitig abschätzen können.
Wenn du im Profiling den Hotspot deines Programms gefunden hast, oder
ihn sowieso schon kennst, dann kannst du dich sehr konkret diesem
Bisschen Code widmen. Deshalb das ganze Programm auf die Nanosekunde zu
tunen, zu Lasten der Wartbarkeit, ist nicht ratsam.
Frank M. schrieb:> Es geht nicht um eine Nanosekunde, sondern um zigtausend mal 1> Nanosekunde.
Also einige zig Mikrosekunden. Grosse Sache! ;-)
A. K. schrieb:> Frank M. schrieb:>> Viele PC-Programme werden von Version zu Version immer langsamer. Warum?>> Weil die Programmierer immer undisziplinierter werden:>> Nö. Nicht auf der Ebene. Nicht solcher Kleinscheiss mördert die> Performance, sondern hinzu kommende Funktionalität, die ihren Preis hat.
Okay, ich will mich ja auch nicht an dem "Kleinscheiss" aufhängen. Auch
wenn Kleinvieh u.U. auch Mist macht ;-)
> Die Performance geht dann im Vorfeld verloren, bei Leuten, die die> Kosten nicht rechtzeitig abschätzen können.
Ja, wahrscheinlich hast Du mit dieser Einschätzung recht.
Frank M. schrieb:> Viele PC-Programme werden von Version zu Version immer langsamer. Warum?> Weil die Programmierer immer undisziplinierter werden: "Das macht schon> der Compiler". Oder: "Wer heute keine 4GB im PC hat, ist halt draußen!".
Nein, das liegt vor allem an den verwendeten Algorithmen und
Softwarearchitekturen, an dem verwenden von Libs wo keiner weiß was die
tun usw..
Michael schrieb:> Und das ändert was?
Nichts.
Warum soll ich mich mit Dir über Nanosekunden unterhalten, wenn schon
ein NOP auf einem mit 8MHz getakteten ATMega ein Vielfaches davon
braucht?
Daher für mich hier EOD - Höflich ausgedrückt.
Frank M. schrieb:> Daher für mich hier EOD
"End of days" mit dem Gouvernator in der Hauptrolle? Handwerklich gut
gemachter Actionfilm, lebt hauptsächlich vom Hauptdarsteller mit etwas
eingestreutem Mystery. Einige Lücken in der Handlung aber das ist normal
in dem Genre. Guter Durchschnitt.
Aber hey wir sind hier doch nicht im Cineasten Forum.
Jörg Wunsch schrieb:> Mark Brandis schrieb:>> Fällt durch jedes Code Review sofort durch ;-)>> Ja, wegen der fehlenden Leerzeichen vor und nach dem "<"-Operator. :-)
Und vor allem, weil "running light" die völlig falsche englische
Übersetzung von "Lauflicht" (was hier wohl gemeint war) ist ;-)