Discussion:
Mehr switching
(zu alt für eine Antwort)
Rainer Weikusat
2016-03-25 16:01:26 UTC
Permalink
switch (iph2->status) {
case PHASE2ST_ADDSA:
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
case PHASE2ST_ESTABLISHED:
trg = &to_list;
break;
}

default:

trg = &from_list;
}

Wer anhand der case-Labels und anderen Namen erraten kann, wo das
herkommt, bekommt einen virtuellen Harzer Handkaese.
Rainer Weikusat
2016-03-25 19:28:36 UTC
Permalink
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
break;
}
trg = &from_list;
}
Dasselbe ohne switch (uncompiliert/ -getestet)

if (iph2->status == PHASE2ST_ESTABLISHED
|| (iph2->status == PHASE2ST_ADDSA
&& !(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))))
trg = &to_list;
else
trg = &from_list;

Falls jemand eine Meinung darueber hat, welche der beiden Varianten aus
was fuer Gruenden zu bevorzugen waere, waere ich sehr an ihr
interessiert (wuerde aber eventuell dagegen argumentieren).
Stefan Reuther
2016-03-26 10:09:30 UTC
Permalink
Post by Rainer Weikusat
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
break;
}
trg = &from_list;
}
Dasselbe ohne switch (uncompiliert/ -getestet)
if (iph2->status == PHASE2ST_ESTABLISHED
|| (iph2->status == PHASE2ST_ADDSA
&& !(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))))
trg = &to_list;
else
trg = &from_list;
Falls jemand eine Meinung darueber hat, welche der beiden Varianten aus
was fuer Gruenden zu bevorzugen waere, waere ich sehr an ihr
interessiert (wuerde aber eventuell dagegen argumentieren).
Ganz klar die zweite, weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.

Alternativ kann es sinnvoll sein, das komplett auszuschreiben, wenn man
in den Genuss einer "du hast da nen enum-Wert vergessen"-Warnung kommen
will. Natürlich nur dann, wenn iph2->status auch ein enum ist.

trg = &from_list;
switch (iph2->status) {
case PHASE2ST_ADDSA:
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
} else {
trg = &from_list;
}
break;
case PHASE2ST_ESTABLISHED:
trg = &to_list;
break;
case PHASE2ST_FOO:
case PHASE2ST_BAR:
case PHASE2ST_BAZ:
trg = &from_list;
break;
}

Wenn man dann Dinge tut wie "den Code als Zustandsautomat aufschreiben"
fällt das mit so einer Lösung jedenfalls deutlich leichter als der
verschachtelte switch/if. Der Compiler dürfte am Ende für alles den
gleichen Code generieren.


Stefan
Rainer Weikusat
2016-03-27 12:20:09 UTC
Permalink
Post by Stefan Reuther
Post by Rainer Weikusat
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
break;
}
trg = &from_list;
}
Dasselbe ohne switch (uncompiliert/ -getestet)
if (iph2->status == PHASE2ST_ESTABLISHED
|| (iph2->status == PHASE2ST_ADDSA
&& !(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))))
trg = &to_list;
else
trg = &from_list;
Falls jemand eine Meinung darueber hat, welche der beiden Varianten aus
was fuer Gruenden zu bevorzugen waere, waere ich sehr an ihr
interessiert (wuerde aber eventuell dagegen argumentieren).
Ganz klar die zweite, weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.
Welche Regeln waeren das denn zum Beispiel?
Post by Stefan Reuther
Alternativ kann es sinnvoll sein, das komplett auszuschreiben, wenn man
in den Genuss einer "du hast da nen enum-Wert vergessen"-Warnung kommen
will. Natürlich nur dann, wenn iph2->status auch ein enum ist.
Die Konstanten sind als Makros definiert. Davon ab benutze ich enum auch
ausschliesslich um mehr oder minder zusammengehoerige Integer-Konstanten
zu definieren, zB

enum {
IPH1_MAGIC = 0x876b58a6b8d7,
IPH1_VER_SHIFT = 48,
IPH1_MAGIC_MASK = (1LLU << IPH1_VER_SHIFT) - 1,

IPH1_VER_SIZE = 8
};

und das macht diese Warnung ziemlich sinnlos.
Post by Stefan Reuther
trg = &from_list;
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
} else {
trg = &from_list;
}
break;
trg = &to_list;
break;
trg = &from_list;
break;
}
Wenn man dann Dinge tut wie "den Code als Zustandsautomat aufschreiben"
fällt das mit so einer Lösung jedenfalls deutlich leichter als der
verschachtelte switch/if.
Insofern solche Zustandsautomaten nicht trivial sind, ist man besser
beraten, den Code, der die jeweiligen Zustaende behandelt, als Menge von
Funktionen zu implementieren und einen Funktionzeiger als
Zustandsvariable zu benutzen. Damit entfaellt die ganze switching-Logik
und das Ergebnis laesst sich meiner Ansicht nach auch einfacher
bearbeiten, weil man es mit relativ kleinen, inhaltlich in sich
abgeschlossenen Textbloecken zu tun hat und nicht mit einer seitenlangen
"In diesem Zustande aber sage ich euch ..."-Litanei.
Stefan Reuther
2016-03-28 09:04:53 UTC
Permalink
[...]
Post by Rainer Weikusat
Post by Stefan Reuther
Post by Rainer Weikusat
Dasselbe ohne switch (uncompiliert/ -getestet)
if (iph2->status == PHASE2ST_ESTABLISHED
|| (iph2->status == PHASE2ST_ADDSA
&& !(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))))
trg = &to_list;
else
trg = &from_list;
Falls jemand eine Meinung darueber hat, welche der beiden Varianten aus
was fuer Gruenden zu bevorzugen waere, waere ich sehr an ihr
interessiert (wuerde aber eventuell dagegen argumentieren).
Ganz klar die zweite, weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.
Welche Regeln waeren das denn zum Beispiel?
Z.B. MISRA-C 2004 Regel 15.1 (required) "A switch label shall only be
used when the most closely-enclosing compound statement is the body of a
switch statement."

Oder die allfällige "nutze kein fallthrough"-Regel, seit Jahrhunderten
von lint geprüft, von MISRA formuliert als Regel 15.2 (required) "An
unconditional break statement shall terminate every non-empty switch
clause", alternativ in HIS-Subset 1.0.3 als Regel 61 oder in JSF-AV-C++
als Regel 193.

Um eine Code Zeile zu sparen mach ich mir jedenfalls nicht den Aufwand,
dafür eine deviation zu verargumentieren.
Post by Rainer Weikusat
Post by Stefan Reuther
Alternativ kann es sinnvoll sein, das komplett auszuschreiben, wenn man
in den Genuss einer "du hast da nen enum-Wert vergessen"-Warnung kommen
will. Natürlich nur dann, wenn iph2->status auch ein enum ist.
Die Konstanten sind als Makros definiert. Davon ab benutze ich enum auch
ausschliesslich um mehr oder minder zusammengehoerige Integer-Konstanten
zu definieren, zB
enum {
IPH1_MAGIC = 0x876b58a6b8d7,
IPH1_VER_SHIFT = 48,
IPH1_MAGIC_MASK = (1LLU << IPH1_VER_SHIFT) - 1,
IPH1_VER_SIZE = 8
};
und das macht diese Warnung ziemlich sinnlos.
In dem Fall, ja. Das ist auch pures C, da ist das nicht zu beanstanden.
In C++ haben enums einen etwas höheren Stellenwert, da vermeide ich das
inzwischen, und das färbt dann eben auf mein C ab...
Post by Rainer Weikusat
Post by Stefan Reuther
trg = &from_list;
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
} else {
trg = &from_list;
}
break;
[...]
Post by Rainer Weikusat
Post by Stefan Reuther
Wenn man dann Dinge tut wie "den Code als Zustandsautomat aufschreiben"
fällt das mit so einer Lösung jedenfalls deutlich leichter als der
verschachtelte switch/if.
Insofern solche Zustandsautomaten nicht trivial sind, ist man besser
beraten, den Code, der die jeweiligen Zustaende behandelt, als Menge von
Funktionen zu implementieren und einen Funktionzeiger als
Zustandsvariable zu benutzen. Damit entfaellt die ganze switching-Logik
und das Ergebnis laesst sich meiner Ansicht nach auch einfacher
bearbeiten, weil man es mit relativ kleinen, inhaltlich in sich
abgeschlossenen Textbloecken zu tun hat und nicht mit einer seitenlangen
"In diesem Zustande aber sage ich euch ..."-Litanei.
Das kommt wie immer drauf an. Wenn man einen Zustand als eine Funktion
darstellt, muss man jegliche Eingabe in einen Funktionsparameter
klatschen. Das kann funktionieren, funktioniert aber oft auch nicht. In
meinen Zustandsautomaten ist es bisher öfter (nicht ausschließlich!)
praktisch gewesen, jegliche Eingabe als Funktion zu formulieren und
darin dann zu switchen:
void handleDataReception(const void* p, size_t n);
void handleDataSent();
void handleConnectionLoss();
void handleConnectionReady(sockaddr_t* a, socklen_t n);
Der Compiler hilft dann halt dabei, zu beurteilen, ob die switch in den
Funktionen auch alle vollständig sind.


Stefan
Rainer Weikusat
2016-03-28 11:51:26 UTC
Permalink
[switch/ if]
Post by Stefan Reuther
Post by Rainer Weikusat
Post by Stefan Reuther
Ganz klar die zweite, weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.
Welche Regeln waeren das denn zum Beispiel?
Z.B. MISRA-C 2004 Regel 15.1 (required) "A switch label shall only be
used when the most closely-enclosing compound statement is the body of a
switch statement."
Oder die allfällige "nutze kein fallthrough"-Regel, seit Jahrhunderten
von lint geprüft, von MISRA formuliert als Regel 15.2 (required) "An
unconditional break statement shall terminate every non-empty switch
clause", alternativ in HIS-Subset 1.0.3 als Regel 61 oder in JSF-AV-C++
als Regel 193.
Das heisst im Grunde genommen nichts als "switch soll so benutzt werden,
als ob es ein Pascal-case waere obwohl das mit potentiell
fehlertraechtigem Mehraufwand verbunden ist". Begruendung entfaellt dank
Autoritaet. Wie ich bereits vor einiger Zeit gepostet hatte,
unterstuetzt C tatsaechlich auch Mehrwege-Verzweigungen mit der
gewuenschten Semantik:

-----
#include <stdio.h>

#define given(x) switch (x)
#define when(x) if (0) case (x):
#define or_when(x) else if (0) case (x):
#define or else default:

int main(int argc, char **argv)
{
char *n, *p;

p = "e";

given (argc - 1) {
when (0)
n = "Keine";

or_when (1)
{
n = "Ein";
p = "";
}

or_when (2)
n = "Zwei";

or_when (3)
n = "Drei";

or_when (4)
n = "Vier";

or
n = "Ganz viele";
}

printf("%s Argument%s\n", n, p);
return 0;
}
------
Post by Stefan Reuther
Um eine Code Zeile zu sparen mach ich mir jedenfalls nicht den Aufwand,
dafür eine deviation zu verargumentieren.
Das wuerde ich wohl auch nicht tun, zumal den Leuten, die diese lustigen
Regelwerke zusammenschreiben und vorschreiben, durchaus zu goennen ist,
sich mit dem Resultat herumaergern zu muessen.
G.B.
2016-03-29 09:30:21 UTC
Permalink
Post by Rainer Weikusat
Begruendung entfaellt dank
Autoritaet.
Die Begründung von Regeln entfällt in der Regel in jedem
Einzelfall, definitionsgemäß, sonst wären alle Regeln sehr
unregelmäßig Regeln: jede nur in ihrem Einzelfall anwendbar...

Setzt man also C-Arbeitskräfte voraus, deren Kapazität
in der Regel für die Arbeit mit von ihnen in der Regel
nicht erwarteten Konstrukten regelmäßig erweitert werden müsste
(für den fachkundigen Gebrauch von
"case X: if ... { case Y: ... }"),
dann fallen künstlerisch wertvolle oder in Einzelfällen
legitime Regelabweichungen mit der allgemeinen Begründung
unter den Tisch, auch wenn diese regelmäßige Begründung schade
ist, oder in Einzelfällen schädlich. Dafür gibt es allerdings:

Die begründete Ausnahme

Wie auch die Regel "C" in Einzelfällen den Assembler-Kenner
ausbremsen wird, sichtbar z.B. wenn sich einer dieser Kenner
darüber beschwert, wie eine Implementierung von C die Regeln
der Sprache nutzt, um einen switch regelgemäß in eine Art Tabelle
zu übersetzen. Dumme, schädliche Regel in "C"? Abschaffen?
Zusätzliche Implementierungsregeln in C aufnehmen? Begründung
einfordern?

Möglicherweise wird der Handwerkerstolz vor einer Ergänzung
wie "when/or_when/or" sein. Der über die Jahrhunderte erfolgreich
praktizierten Fachgeheimniskult, durchaus erkennbar im C-Präprozessor,
wäre ja dann in eine Regel transformiert...

Aber wenn eine verbesserte Kontrollstruktur wirklich so möglich
und nützlich ist, und die Existenz von MISRA-C auf ein Interesse
an zweckgemäßen Ausdrucksmitteln schließen lässt, dann dürfte es
auch möglich sein, die Syntax für normale Gesellen des C-Handwerks
zu ergänzen und das neue Werkzeug C zu verkaufen.
Rainer Weikusat
2016-03-29 10:11:37 UTC
Permalink
Post by G.B.
Post by Rainer Weikusat
Begruendung entfaellt dank
Autoritaet.
Die Begründung von Regeln entfällt in der Regel in jedem
Einzelfall, definitionsgemäß, sonst wären alle Regeln sehr
unregelmäßig Regeln: jede nur in ihrem Einzelfall anwendbar...
Sie entfaellt genau dann, wenn sie die Bestandteil eines
Dienstverhaeltnisses zwischen einem Arbeitgeber und einem Arbeitnehmer
ist: Die Volkswagen-AG hat fraglos die Berechtigung, ihren angestellten
vorzuschreiben, welche Programmiersprachen diese wie verwenden sollen.

[...]
Post by G.B.
Möglicherweise wird der Handwerkerstolz vor einer Ergänzung
wie "when/or_when/or" sein. Der über die Jahrhunderte erfolgreich
praktizierten Fachgeheimniskult, durchaus erkennbar im C-Präprozessor,
wäre ja dann in eine Regel transformiert...
Es waere meiner Ansicht nach in der Tat sinnvoll, wenigstens die
marginalen Moeglichkeiten, die C in dieser Hinsicht bietet, zu nutzen,
um die Befolgung moeglichst vieler dieser Regeln durch
Computerunterstuetzung zu vereinfachen wenn nicht sogar zu
erzwingen. Vor allem sollte man das tun, wenn man sie fuer sachlich
geboten haelt. Noch sinnvoller waere es allerdings, eine Sprache zu
benutzen, die so definiert ist, wie das fuer erforderlich gehalten
wird, dh zb eine, die Rekursionen gar nicht unterstuetzt (sowas verwirrt
den Assembler-Kenner ohnehin bloss). Dann braucht man sie nicht zu
verbieten und die Einhaltung dieses Verbots zu ueberwachen.
Peter J. Holzer
2016-03-29 20:55:14 UTC
Permalink
Post by Rainer Weikusat
Post by G.B.
Post by Rainer Weikusat
Begruendung entfaellt dank
Autoritaet.
Die Begründung von Regeln entfällt in der Regel in jedem
Einzelfall, definitionsgemäß, sonst wären alle Regeln sehr
unregelmäßig Regeln: jede nur in ihrem Einzelfall anwendbar...
Sie entfaellt genau dann, wenn sie die Bestandteil eines
Dienstverhaeltnisses zwischen einem Arbeitgeber und einem Arbeitnehmer
Das mag Deiner Erfahrung nach so sein, ist aber sicher nicht generell
so.
Post by Rainer Weikusat
Die Volkswagen-AG hat fraglos die Berechtigung, ihren angestellten
vorzuschreiben, welche Programmiersprachen diese wie verwenden sollen.
Wenn ein externer Dienstleister für uns Code schreibt, haben wir
zweifellos genau die gleiche Berechtigung, ihm vorzuschreiben, welchen
formalen Kriterien dieser Code zu entsprechen hat.

Tatsächlich sehe ich bei externen die Notwendigkeit sogar noch mehr:
Innerhalb eines Entwicklerteams entwickelt sich ein gemeinsamer Stil oft
von selbst und muss nicht explizit niedergeschrieben werden. Ein
Externer hingegen kann den Hausbrauch nicht kennen und braucht
Richtlinien, wenn sein Code sich einigermaßen harmonisch einfügen soll.

Eine Begründung halte ich in beiden Fällen für notwendig. Einerseits
hält man (ich zumindest) sich leichter an Regeln, deren Sinn man
versteht, andererseits ist eine Begründung eine notwendige Voraussetzung
für einen Review - und man sollte jede Regel regelmäßig auf ihre
Sinnhaftigkeit überprüfen.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2016-03-30 14:39:04 UTC
Permalink
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Die Volkswagen-AG hat fraglos die Berechtigung, ihren angestellten
vorzuschreiben, welche Programmiersprachen diese wie verwenden sollen.
Wenn ein externer Dienstleister für uns Code schreibt, haben wir
zweifellos genau die gleiche Berechtigung, ihm vorzuschreiben, welchen
formalen Kriterien dieser Code zu entsprechen hat.
Begrenzt. Eine Vorgabe "Rekursion darf nicht benutzt werden" kann man
zwar machen, aber es ist jedem freigestellt, dass als sachfremden Unfug[*] zu
anzusehen und solche Auftraege nicht anzunehmen[**].

[*] Der Gedanke, das Leute, denen man nicht die Fachkompetenz zutraut,
Rekursionen sinnvoll zu benutzen, Automobile-Steuersoftware
entwickeln, ist recht erschreckend [***].

[**] "Ich lehne Zusammenarbeit mit anderen ab" habe ich schon (als
anderer, nicht als Auftraggeber) als Ablehnungsgrund von jemandem,
der mir ebenso inkompetent wie "nicht eben notleidend" erschien,
erlebt.

Moeglicherweise provokanter Witz folgt

[***] Andererseits erklaert er gewisse Ereignisse in den Vereinigten
Staaten: Nauterlich hat niemand davon gewusst, dass Software in
VWs Abgastests negativ beeinflusst, denn sonst hatte man
wenigstens versucht, diesen Fehler zu beheben.
Stefan Reuther
2016-03-30 16:40:13 UTC
Permalink
Post by Rainer Weikusat
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Die Volkswagen-AG hat fraglos die Berechtigung, ihren angestellten
vorzuschreiben, welche Programmiersprachen diese wie verwenden sollen.
Wenn ein externer Dienstleister für uns Code schreibt, haben wir
zweifellos genau die gleiche Berechtigung, ihm vorzuschreiben, welchen
formalen Kriterien dieser Code zu entsprechen hat.
Begrenzt. Eine Vorgabe "Rekursion darf nicht benutzt werden" kann man
zwar machen, aber es ist jedem freigestellt, dass als sachfremden Unfug[*] zu
anzusehen und solche Auftraege nicht anzunehmen[**].
[*] Der Gedanke, das Leute, denen man nicht die Fachkompetenz zutraut,
Rekursionen sinnvoll zu benutzen, Automobile-Steuersoftware
entwickeln, ist recht erschreckend [***].
Jemand, der annimmt, eine solche Regel stünde unverrückbar in MISRA, hat
das MISRA-Dokument offenbar nicht gelesen.

Ja, da gibt es eine Regel, die Rekursion untersagt. Das heißt noch lange
nicht, dass man keine machen darf. Sondern das heißt nur, dass man, wenn
man der Meinung ist, welche zu brauchen, sich ein paar gesonderte
Gedanken machen und zu Papier bringen muss. Das gibt dem unerfahrenen
Uni-Abgänger, der aus der Vorlesung "Grundlagen der Programmierung
(Lehrsprachen: Haskell, Scheme)" mit einem "Rekursion ist cool" kommt,
vielleicht auch einen Hinweis.

Der handfeste Vorteil beim Verzicht auf Rekursion ist, dass man mit
relativ einfachen, robusten Tools Dinge wie den maximalen Stack-
verbrauch ermitteln kann. Das ist bei der Auswahl des Mikrocontrollers
genauso nützlich wie bei der Allokation von Thread-Stacks. Wenn man
diese Tools nicht mehr nutzen kann, muss man sich diese Gedanken eben
selbst machen und dies auch dokumentieren.

Man kann natürlich auch die Finger in die Ohren stecken, "*lalala* die
sind ja alle doof *lalala*" rufen, und hoffen, dass im Hintergrund ein
Kernel nebst virtuellem Speicher läuft, der die notwendigen Bytes für
den Stack schon irgendwo herzaubert.


Stefan
G.B.
2016-03-31 09:49:48 UTC
Permalink
Post by Stefan Reuther
Der handfeste Vorteil beim Verzicht auf Rekursion ist, dass man mit
relativ einfachen, robusten Tools Dinge wie den maximalen Stack-
verbrauch ermitteln kann.
Etwas sonderbar beim viel beredeten Einsatz von Sprachen wie C in
der Industrie einerseits und ihrer akademischen (Nicht-)Bahandlung
andererseits erscheint mir manchmal, dass trotz massenhafter
Verwendung von C Fragen, die Verwendung von C betreffend,
ein akademisches Nischendasein fristen. Zum Beispiel:

Welche formalen Einschränkungen von C können kompetente,
normalsterbliche Programmierer in die Lage versetzen, C-Funktionen
zu schreiben, die mit vertretbarem Aufwand – gemäß den fiktiven
technisch-industriellen Erfordernissen – verifiziert werden können?
Beispielsweise wäre klar, dass eine Implementierung von C, lokal so
eingeschränkt, entweder gezwungen ist, endständige Rekursion durch
Sprünge zu ersetzen, und/oder formale Aussagen zum Speicherverbrauch
zu errechnen. Etwa:

#pragma no_malloc, no_sys, no_extern

Da Rekursion in der akademischen Informatik ziemlich zentral ist,
andererseits die vielen eingebetteten Systeme ohne C kaum auskommen,
wenn aber trotzdem die Kombination von Rekursion und C i.S. seiner
Norm trotzdem nicht von Schwärmen begabter Informatiker bearbeitet
zu werden scheint, drängt sich mir ein Verdacht auf:

Sofern nur alle am Markt teilnehmenden Firmen gleich von dem selben
Manko betroffen sind, können alle sich einfacheren Themen widmen und
alle kommen mit weniger verifizierbaren Lösungen davon. Das hat doch
auch etwas Positives, denn in dieser Marktsituation kann man sich
mehr den wichtigen Dingen widmen, wie der Familie, und dem Umstand,
dass besser niemand die überlegene Technik für sich entwickelt.

QoI: BER
Rainer Weikusat
2016-03-31 13:05:35 UTC
Permalink
Post by Stefan Reuther
Post by Rainer Weikusat
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Die Volkswagen-AG hat fraglos die Berechtigung, ihren angestellten
vorzuschreiben, welche Programmiersprachen diese wie verwenden sollen.
Wenn ein externer Dienstleister für uns Code schreibt, haben wir
zweifellos genau die gleiche Berechtigung, ihm vorzuschreiben, welchen
formalen Kriterien dieser Code zu entsprechen hat.
Begrenzt. Eine Vorgabe "Rekursion darf nicht benutzt werden" kann man
zwar machen, aber es ist jedem freigestellt, dass als sachfremden Unfug[*] zu
anzusehen und solche Auftraege nicht anzunehmen[**].
[*] Der Gedanke, das Leute, denen man nicht die Fachkompetenz zutraut,
Rekursionen sinnvoll zu benutzen, Automobile-Steuersoftware
entwickeln, ist recht erschreckend [***].
Jemand, der annimmt, eine solche Regel stünde unverrückbar in MISRA, hat
das MISRA-Dokument offenbar nicht gelesen.
Ja, da gibt es eine Regel, die Rekursion untersagt. Das heißt noch lange
nicht, dass man keine machen darf. Sondern das heißt nur, dass man, wenn
man der Meinung ist, welche zu brauchen, sich ein paar gesonderte
Gedanken machen und zu Papier bringen muss.
Hmm ... naja ... der Nutzen von Rekursion in C ist hauptsaechlich, dass
man unter geeigneten Umstaenden und falls der Overhead keine Rolle
spielt, den Computer/ Compiler dazu kriegen kann, Speicher fuer eine
Menge von Zustandsinformation dynamischen Umfangs automatisch zu
verwalten. Dh es wird weniger und vor allem einfacherer Code benoetigt,
was Arbeitszeit spart und Fehlermoeglichkeiten reduziert.

Wenn man allerdings genoetigt ist, einen mehrseitigen Aufsatz "Warum ich
das so und nicht anders fuer richtig halte" zu verfassen, um sich fuer
die Verwendung zu rechtfertigen, anstatt etwas fuer produktiv gehaltenes
zu tun, zusaetzlich seinen Vorgesetzen damit auf die Nerven faellt,
"schon wieder" aus der Reihe zu tanzen, und man ausserdem fuer die
Mehrarbeit genausogut bezahlt wuerde, duerfte die Entscheidung "Soll ich
das wirklich benutzen?" im gewuenschten Sinne ausfallen, naemlich "Das
sei ferne!".
Post by Stefan Reuther
Der handfeste Vorteil beim Verzicht auf Rekursion ist, dass man mit
relativ einfachen, robusten Tools Dinge wie den maximalen Stack-
verbrauch ermitteln kann.
Das scheint mir keinen Sinn zu ergeben: Der 'maximale Stackverbrauch'
haengt davon ab, wie ein Compiler jede einzelne Funktion uebersetzt, und
welches die Folge von Funktionsaufrufen ist, die den meisten
Stackspeicher belegen wird.

Hier scheinen mir noch ein paar mehr "robuste Einschraenkungen" noetig
wie zB "ausschliesslich globale Variablen benutzen" und "keinerlei
Unterroutinen benutzen" (erst recht keine, die andere aufrufen).

[...]
Post by Stefan Reuther
Man kann natürlich auch die Finger in die Ohren stecken, "*lalala* die
sind ja alle doof *lalala*" rufen, und hoffen, dass im Hintergrund ein
Kernel nebst virtuellem Speicher läuft, der die notwendigen Bytes für
den Stack schon irgendwo herzaubert.
Oder man kann eine Programmiersprache, die Rekursion unterstuetzt, fuer
eine PDP-11 ohne virtuellen Speicher und mit sehr wenig Hauptspeicher
entwerfen. Zeitgleich hat sich wohl auch jemand die Finger in die Ohren
gesteckt und angefangen, "lalala die sind alle doof!" zu singen
(eventuell ca 10 Jahre frueher) und wenn er nicht gestoreben ist, tut er
das heute noch.
Stefan Reuther
2016-03-31 16:14:01 UTC
Permalink
Post by Rainer Weikusat
Post by Stefan Reuther
Post by Rainer Weikusat
Begrenzt. Eine Vorgabe "Rekursion darf nicht benutzt werden" kann man
zwar machen, aber es ist jedem freigestellt, dass als sachfremden Unfug[*] zu
anzusehen und solche Auftraege nicht anzunehmen[**].
[*] Der Gedanke, das Leute, denen man nicht die Fachkompetenz zutraut,
Rekursionen sinnvoll zu benutzen, Automobile-Steuersoftware
entwickeln, ist recht erschreckend [***].
Jemand, der annimmt, eine solche Regel stünde unverrückbar in MISRA, hat
das MISRA-Dokument offenbar nicht gelesen.
Ja, da gibt es eine Regel, die Rekursion untersagt. Das heißt noch lange
nicht, dass man keine machen darf. Sondern das heißt nur, dass man, wenn
man der Meinung ist, welche zu brauchen, sich ein paar gesonderte
Gedanken machen und zu Papier bringen muss.
Hmm ... naja ... der Nutzen von Rekursion in C ist hauptsaechlich, dass
man unter geeigneten Umstaenden und falls der Overhead keine Rolle
spielt, den Computer/ Compiler dazu kriegen kann, Speicher fuer eine
Menge von Zustandsinformation dynamischen Umfangs automatisch zu
verwalten. Dh es wird weniger und vor allem einfacherer Code benoetigt,
was Arbeitszeit spart und Fehlermoeglichkeiten reduziert.
Wenn man allerdings genoetigt ist, einen mehrseitigen Aufsatz "Warum ich
das so und nicht anders fuer richtig halte" zu verfassen, um sich fuer
die Verwendung zu rechtfertigen, [...]
Die Dimensionierung obliegt dem, der die Regeln verantwortet, und da
gibt's leider genug Leute, die Unfug treiben. Ich versuche dann, die in
sinnvolle Richtungen zu treten.

Ein kleiner Aufwand muss sein, dass man bei Trivialitäten wie "Variablen
müssen initialisiert werden" halt einfach das "=0" hinschreibt anstatt
zu diskutieren. Aber ansonsten muss der Aufwand kein großer sein: Ich
schreib ein Ticket, die QS nickt das ab, und gut.
Post by Rainer Weikusat
Post by Stefan Reuther
Der handfeste Vorteil beim Verzicht auf Rekursion ist, dass man mit
relativ einfachen, robusten Tools Dinge wie den maximalen Stack-
verbrauch ermitteln kann.
Das scheint mir keinen Sinn zu ergeben: Der 'maximale Stackverbrauch'
haengt davon ab, wie ein Compiler jede einzelne Funktion uebersetzt, und
welches die Folge von Funktionsaufrufen ist, die den meisten
Stackspeicher belegen wird.
Hier scheinen mir noch ein paar mehr "robuste Einschraenkungen" noetig
wie zB "ausschliesslich globale Variablen benutzen" und "keinerlei
Unterroutinen benutzen" (erst recht keine, die andere aufrufen).
Deswegen lässt man ein solches Tool ja auf ein fertig gebautes Programm
los, es analysiert daraus den Callgraphen und sagt mir für von mir
gewählte Funktionen (sinnfreierweise wähle ich da die Thread-Eintritts-
Funktionen) den kumulativen Stackverbrauch.

Zuletzt benutzt habe ich das Tool 'gstack' von Green Hills.


Stefan
Thomas Jahns
2016-03-31 13:59:44 UTC
Permalink
Post by Stefan Reuther
Der handfeste Vorteil beim Verzicht auf Rekursion ist, dass man mit
relativ einfachen, robusten Tools Dinge wie den maximalen Stack-
verbrauch ermitteln kann. Das ist bei der Auswahl des Mikrocontrollers
genauso nützlich wie bei der Allokation von Thread-Stacks. Wenn man
diese Tools nicht mehr nutzen kann, muss man sich diese Gedanken eben
selbst machen und dies auch dokumentieren.
Man kann natürlich auch die Finger in die Ohren stecken, "*lalala* die
sind ja alle doof *lalala*" rufen, und hoffen, dass im Hintergrund ein
Kernel nebst virtuellem Speicher läuft, der die notwendigen Bytes für
den Stack schon irgendwo herzaubert.
Die Entscheidung hängt aber (nicht im Prinzip sondern in der Menge des
tatsächlich benötigten Speicher auf dem Stack) auch von der Art der Rekursion
ab. Wenn die Rekursionstiefe mit O(N) wächst, ist vermutlich schon der
Algorithmus für Rekursion ungünstig gewählt. Bei den Verfahren, die Rekursion
"schön" nutzen handelt es sich hingegen oft um solche mit Rekursionstiefen in
Richtung O(log N) und da muss N schon sehr groß werden, wenn einem für kleine
Stackframes der Speicher ausgehen soll (wer große Arrays per alloca o.ä. auf den
Stack packt sollte besser auch die maximale Stackgröße unter Kontrolle haben:
OMP_STACKSIZE o.ä.). Für solche Algorithmen kommt man dann auch mit einer
nichtrekursiven Implementierung selten darum herum, den Stack auf dem Heap noch
mal nachzubauen, was wiederum wie von Rainer W. schon angedeutet deutlich
weniger effizient ist als das simple Verschieben des Stack-Pointers.

Gerade auf Micro-Controllern ohne virtuellen Speicher wachsen ja Heap und Stack
aufeinander zu, so dass ein Tausch von Stack- gegen Heap-Speicherverbrauch mir
keinen Gewinn zu versprechen scheint. Deshalb halte ich ein grundsätzliches
Verbot von rekursiven Programmen für den falschen Ansatz. Wenn man neue
Mitarbeiter hat, die mit naiven Programmen irgendwelche Implementierungsgrenzen
sprengen, helfen Code-Review und Gespräche mit erfahreneren Kollegen langfristig
mehr.

Wenn die besagten Arrays auf dem Stack ein Problem sind, kann vielleicht sogar
der Compiler helfen: die ifort Option -heap-arrays hilft, den Stack-Platzbedarf
effektiv zu begrenzen, ohne Kleinkram auf den Heap zu verschieben. Es wird wohl
noch ein paar Iterationen des Compilers dauern, bis dieses Feature es von
Fortran zu C schafft, aber so lange kann man mit den Interop-Features ja den
betreffenden Teil in Fortran schreiben.

Mal schauen wie sehr man in dclc für die Erwähnung des F-Wortes gesteinigt wird. ;-)

Thomas
Thomas Koenig
2016-03-31 14:38:51 UTC
Permalink
Post by Thomas Jahns
Die Entscheidung hängt aber (nicht im Prinzip sondern in der Menge des
tatsächlich benötigten Speicher auf dem Stack) auch von der Art der Rekursion
ab. Wenn die Rekursionstiefe mit O(N) wächst, ist vermutlich schon der
Algorithmus für Rekursion ungünstig gewählt. Bei den Verfahren, die Rekursion
"schön" nutzen handelt es sich hingegen oft um solche mit Rekursionstiefen in
Richtung O(log N) und da muss N schon sehr groß werden, wenn einem für kleine
Stackframes der Speicher ausgehen soll (wer große Arrays per alloca o.ä. auf den
OMP_STACKSIZE o.ä.).
... und dann kommen da solche Algorithmen um die Ecke wie Quicksort, die
nur in den meisten Fällen O(log N) haben und manchmal halt O(N).

Vielleicht sollte man dann halt doch Heapsort nehmen.
Post by Thomas Jahns
Wenn die besagten Arrays auf dem Stack ein Problem sind, kann vielleicht sogar
der Compiler helfen: die ifort Option -heap-arrays hilft, den Stack-Platzbedarf
effektiv zu begrenzen, ohne Kleinkram auf den Heap zu verschieben. Es wird wohl
noch ein paar Iterationen des Compilers dauern, bis dieses Feature es von
Fortran zu C schafft, aber so lange kann man mit den Interop-Features ja den
betreffenden Teil in Fortran schreiben.
Mal schauen wie sehr man in dclc für die Erwähnung des F-Wortes gesteinigt wird. ;-)
Jehova :-)

Fortran ist für Systeme mit kleinen Ressourcen doch eine relativ
umfangreiche Sprache. Natürlich ist so was sie ALLOCATABLE gut,
wenn man keine Speicherlecks haben möchte. Aber ein einziges.
Fortran-Statement kann halt doch eine ganze Menge an C-äquivalenten
Instruktionen erzeugen. (Wer gfortran installiert hat, kann sich
das mal mit -fdump-tree-original ansehen).

Ich kenne niemanden, der ernsthaft Fortran im embedded-Bereich
einsetzt, zumindest kann ich mich an keine entsprechendedn
Bug Reports erinnern.

Ist für embedded nicht eher Ada geeignet?
Thomas Jahns
2016-03-31 15:09:46 UTC
Permalink
Post by Thomas Koenig
Post by Thomas Jahns
Die Entscheidung hängt aber (nicht im Prinzip sondern in der Menge des
tatsächlich benötigten Speicher auf dem Stack) auch von der Art der Rekursion
ab. Wenn die Rekursionstiefe mit O(N) wächst, ist vermutlich schon der
Algorithmus für Rekursion ungünstig gewählt. Bei den Verfahren, die Rekursion
"schön" nutzen handelt es sich hingegen oft um solche mit Rekursionstiefen in
Richtung O(log N) und da muss N schon sehr groß werden, wenn einem für kleine
Stackframes der Speicher ausgehen soll (wer große Arrays per alloca o.ä. auf den
OMP_STACKSIZE o.ä.).
... und dann kommen da solche Algorithmen um die Ecke wie Quicksort, die
nur in den meisten Fällen O(log N) haben und manchmal halt O(N).
Durch geeignete Wahl des Pivot-Elementes kann man aber das praktisch
ausschliessen (solange es nicht gerade mit einem Angreifer zu tun hat, der
Eingabe und Zufallszahlengenerator unter seine Kontrolle bringen konnte).
Post by Thomas Koenig
Vielleicht sollte man dann halt doch Heapsort nehmen.
Dass die geeignete Wahl des Algorithmus entscheidend ist, freut das
Informatikerherz doch.
Post by Thomas Koenig
Fortran ist für Systeme mit kleinen Ressourcen doch eine relativ
umfangreiche Sprache. Natürlich ist so was sie ALLOCATABLE gut,
wenn man keine Speicherlecks haben möchte. Aber ein einziges.
Fortran-Statement kann halt doch eine ganze Menge an C-äquivalenten
Instruktionen erzeugen. (Wer gfortran installiert hat, kann sich
das mal mit -fdump-tree-original ansehen).
Mehr mit weniger ausdrücken zu können muss ja kein Nachteil sein. Die kritischen
Unterschiede zu C entstehen nach meiner Erfahrung nur an einer Sorte von
Anweisungen: Array-Zuweisungen und unzusammenhängende Array-Argumente können bei
ungünstiger LHS und RHS bzw. Dummy-Argument recht großvolumige Kopien erzwingen.
Dafür können aber die meisten Compiler eine Warnung generieren (ifort -check
arg_temp_created).
Post by Thomas Koenig
Ich kenne niemanden, der ernsthaft Fortran im embedded-Bereich
einsetzt, zumindest kann ich mich an keine entsprechendedn
Bug Reports erinnern.
Da erwarte ich jetzt anders: da Fortran ja deutlich älter ist und daher schon
eine mögliche Wahl darstellte, als C noch in der Entstehung begriffen war. Da
ich Fortran im HPC-Umfeld einsetze bin ich immer wieder erstaunt, auf welch
kleinen Maschinen Fortran im wissenschaftlichen Umfeld eingesetzt wird, z.B.
Raumsonden.
Post by Thomas Koenig
Ist für embedded nicht eher Ada geeignet?
Zumindest bei allem was fliegt ist es wohl beliebt. Bei uns hat es kaum Chancen
wegen zu geringer Verbreitung.

Thomas
Stefan Reuther
2016-03-31 16:07:08 UTC
Permalink
Post by Thomas Jahns
Post by Stefan Reuther
Der handfeste Vorteil beim Verzicht auf Rekursion ist, dass man mit
relativ einfachen, robusten Tools Dinge wie den maximalen Stack-
verbrauch ermitteln kann. Das ist bei der Auswahl des Mikrocontrollers
genauso nützlich wie bei der Allokation von Thread-Stacks. Wenn man
diese Tools nicht mehr nutzen kann, muss man sich diese Gedanken eben
selbst machen und dies auch dokumentieren.
Man kann natürlich auch die Finger in die Ohren stecken, "*lalala* die
sind ja alle doof *lalala*" rufen, und hoffen, dass im Hintergrund ein
Kernel nebst virtuellem Speicher läuft, der die notwendigen Bytes für
den Stack schon irgendwo herzaubert.
Die Entscheidung hängt aber (nicht im Prinzip sondern in der Menge des
tatsächlich benötigten Speicher auf dem Stack) auch von der Art der
Rekursion ab. Wenn die Rekursionstiefe mit O(N) wächst, ist vermutlich
schon der Algorithmus für Rekursion ungünstig gewählt. Bei den
Verfahren, die Rekursion "schön" nutzen handelt es sich hingegen oft um
solche mit Rekursionstiefen in Richtung O(log N) und da muss N schon
sehr groß werden, wenn einem für kleine Stackframes der Speicher
ausgehen soll (wer große Arrays per alloca o.ä. auf den Stack packt
OMP_STACKSIZE o.ä.).
Wichtiger finde ich die Frage, ob die Stacktiefe in irgendeiner Weise
nutzergesteuert ist, zum Beispiel rekursives Verzeichnis-Einlesen. Ich
will ja nicht, dass jemand, der den Diskeditor bedienen kann, mir mein
Gerät aufmacht.
Post by Thomas Jahns
Für solche Algorithmen kommt man dann auch mit
einer nichtrekursiven Implementierung selten darum herum, den Stack auf
dem Heap noch mal nachzubauen, was wiederum wie von Rainer W. schon
angedeutet deutlich weniger effizient ist als das simple Verschieben des
Stack-Pointers.
Es hat ein deutlich besseres Fehlerverhalten: 'malloc' kann einen Fehler
melden (und tut das auf entsprechenden Plattformen auch), eine
Stack-Allokation kann nur die weiße Fahne aka SIGSEGV hissen.

Heap hat auch ein deutlich besseres Verhalten bezüglich der
Vorhersehbarkeit: wenn ich in einer Iteration malloc(100) schreibe,
allokiere ich halt 100 Bytes pro Iteration. Wieviel Speicher allokiert
wird, wenn ich
void foo(struct x* p) {
if (need_recurse(p)) {
foo(p->next);
}
}
schreibe, weiß ich nicht (wieviele Variablen spillt der Compiler auf den
Stack? Wieviel red zone ist pro Frame reserviert?), dafür wird es
wahrscheinlich (genau weiß man das ja nicht) deutlich mehr, wenn jemand
das zu
void foo(struct x* p) {
if (need_recurse(p)) {
foo(p->next);
} else {
char tmp[100];
sprintf(tmp, "%x", p);
}
}
ändert. Ich muss also dran denken, dass stattdessen als
void foo1(struct x* p) {
char tmp[100];
sprintf(tmp, "%x", p);
}
void foo(struct x* p) {
if (need_recurse(p)) {
foo(p->next);
} else {
foo1(p);
}
}
zu schreiben, und darauf hoffen, dass das nicht ein übereifriger Kollege
oder Compiler-Optimierer wieder zurückändert.

Auf einem System mit in Megabyte gemessenem Stack pro Thread ist mir das
egal. Auf einem kleineren System nicht.
Post by Thomas Jahns
Gerade auf Micro-Controllern ohne virtuellen Speicher wachsen ja Heap
und Stack aufeinander zu, so dass ein Tausch von Stack- gegen
Heap-Speicherverbrauch mir keinen Gewinn zu versprechen scheint. Deshalb
halte ich ein grundsätzliches Verbot von rekursiven Programmen für den
falschen Ansatz. Wenn man neue Mitarbeiter hat, die mit naiven
Programmen irgendwelche Implementierungsgrenzen sprengen, helfen
Code-Review und Gespräche mit erfahreneren Kollegen langfristig mehr.
Codierregeln stecken den Rahmen. Der erfahrene Kollege weiß dann, wann
man die Regeln brechen darf.


Stefan
Rainer Weikusat
2016-03-31 20:44:52 UTC
Permalink
Stefan Reuther <***@arcor.de> writes:

[...]
Post by Stefan Reuther
Post by Thomas Jahns
Für solche Algorithmen kommt man dann auch mit
einer nichtrekursiven Implementierung selten darum herum, den Stack auf
dem Heap noch mal nachzubauen, was wiederum wie von Rainer W. schon
angedeutet deutlich weniger effizient ist als das simple Verschieben des
Stack-Pointers.
Es hat ein deutlich besseres Fehlerverhalten: 'malloc' kann einen Fehler
melden (und tut das auf entsprechenden Plattformen auch), eine
Stack-Allokation kann nur die weiße Fahne aka SIGSEGV hissen.
Es gibt allerdings rekursive Loesungen fuer Probleme von denen von
vorneherein bekannt ist, dass sie auf den Stack passen werden. ZB
folgendes, die Idee aus einem Programm geklaut, das Dennis Ritchie vor
ein paar Jahren zusammen mit einer Lovecraft-Homage veroeffentlicht
hat:

--------
#include <stdio.h>

static char *utoa_(unsigned x, char *p)
{
if (x) (p = utoa_(x / 10, p))[-1] = x % 10 + '0';
return p + 1;
}

static void utoa(unsigned x, char *p)
{
if (x) utoa_(x, p)[-1] = 0;
else *p = '0', p[1] = 0;
}

int main(void)
{
char buf[1024];

srand(time(NULL));
utoa(rand(), buf);
puts(buf);

return 0;
}
--------

NB: Das ist ein kuenstliches Beispiel, weil mir aus dem Stegreif gerade
kein richtiges einfaellt. Ich hatte aber schon welche.
Post by Stefan Reuther
Heap hat auch ein deutlich besseres Verhalten bezüglich der
Vorhersehbarkeit: wenn ich in einer Iteration malloc(100) schreibe,
allokiere ich halt 100 Bytes pro Iteration.
Dann forderst du pro Iteration einen Speicherblock an, der mindestens
100 Byte gross ist. Unter geeignet widrigen Umstaenden (SLOB-Allocator
mit 'worst fit') koennte das eine Menge mehr sein.
Rainer Weikusat
2016-04-01 12:07:46 UTC
Permalink
Rainer Weikusat <***@talktalk.net> writes:

[...]
Post by Rainer Weikusat
die Idee aus einem Programm geklaut, das Dennis Ritchie vor
ein paar Jahren zusammen mit einer Lovecraft-Homage veroeffentlicht
--------
#include <stdio.h>
static char *utoa_(unsigned x, char *p)
{
if (x) (p = utoa_(x / 10, p))[-1] = x % 10 + '0';
return p + 1;
}
static void utoa(unsigned x, char *p)
{
if (x) utoa_(x, p)[-1] = 0;
else *p = '0', p[1] = 0;
}
int main(void)
{
char buf[1024];
srand(time(NULL));
utoa(rand(), buf);
puts(buf);
return 0;
}
--------
NB: Das ist ein kuenstliches Beispiel, weil mir aus dem Stegreif gerade
kein richtiges einfaellt. Ich hatte aber schon welche.
Es ist auch kein besonders sinnvolles Beispiel weil die
Speicherverwaltung, die hier noetig waere, sehr einfach ist:

--------
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static void utoa(unsigned x, char *p)
{
char tmp[32], *pt;

pt = tmp + sizeof(tmp);
*--pt = 0;
do *--pt = x % 10 + '0'; while (x /= 10);

while (*p = *pt) ++p, ++pt; /* strcpy(p, pt); */
}

int main(void)
{
char buf[1024];

srand(time(NULL));
utoa(rand(), buf);
puts(buf);

return 0;
}
--------

Es ist aber nicht ganz einzusehen warum die rekursive Loesung in einer
Situation, in der maximal zwoelf Funktionsaufrufe und ein zusaetzlicher
Speicherverbrauch von weniger als 200 Byte (fuer 32-bit Zeiger) keine
Rolle spielen, was haeufig der Fall sein wird, 'strengstens' verboten
sein sollte.

NB: Das soll unter anderem auch zeigen, dass es sinnvoll sein kann,
Dinge rueckwaerts zu tun, und das Endmarker unter Umstaenden zu
einfacherem Code fuehren.
G.B.
2016-04-01 16:54:25 UTC
Permalink
Post by Rainer Weikusat
Es ist aber nicht ganz einzusehen warum die rekursive Loesung in einer
Situation, in der maximal zwoelf Funktionsaufrufe und ein zusaetzlicher
Speicherverbrauch von weniger als 200 Byte (fuer 32-bit Zeiger) keine
Rolle spielen, was haeufig der Fall sein wird, 'strengstens' verboten
sein sollte.
Ein mögliches Argument dafür/dagegen sind zeitgenössische Seitengrößen
bzw. der Rahmen auf dem Stack (~4k); in einer 8bit- oder 16bit-Umgebung
in Subdaumennagelgröße mögen die dann noch weniger als 200 Bytes
doch eine Rolle spielen; vielleicht ergibt sich in Summe auch ein
messbarer Effekt bei nach Zugriffsgeschwindigkeit gestaffeltem Speicher,
sofern der für den stack genutzt wird.
Stefan Reuther
2016-04-01 19:36:13 UTC
Permalink
Post by Rainer Weikusat
Post by Rainer Weikusat
static char *utoa_(unsigned x, char *p)
{
if (x) (p = utoa_(x / 10, p))[-1] = x % 10 + '0';
return p + 1;
}
static void utoa(unsigned x, char *p)
{
if (x) utoa_(x, p)[-1] = 0;
else *p = '0', p[1] = 0;
}
[...]
Post by Rainer Weikusat
static void utoa(unsigned x, char *p)
{
char tmp[32], *pt;
pt = tmp + sizeof(tmp);
*--pt = 0;
do *--pt = x % 10 + '0'; while (x /= 10);
while (*p = *pt) ++p, ++pt; /* strcpy(p, pt); */
}
[...]
Post by Rainer Weikusat
Es ist aber nicht ganz einzusehen warum die rekursive Loesung in einer
Situation, in der maximal zwoelf Funktionsaufrufe und ein zusaetzlicher
Speicherverbrauch von weniger als 200 Byte (fuer 32-bit Zeiger) keine
Rolle spielen, was haeufig der Fall sein wird, 'strengstens' verboten
sein sollte.
Ich wiederhole mich glaube ich wenn ich sage: Rekursion ist auch in
MISRA nicht 'strengstens' verboten. Man soll halt nur Gründe dafür
nennen können die über "ist mir halt so aus der Tastatur gefallen"
hinausgehen.

Konkret wäre das z.B. in deinem Fall die Dokumentation, dass das maximal
zwölf Aufrufebenen sind und warum das der Fall ist (Wertebereich der
Eingabedaten). Als Auditor möchte ich dann an der Stelle vielleicht noch
ein 'static_assert(UINT_MAX <= 0xFFFFFFFF);' sehen, um das
sicherzustellen. Aber mehr nicht.

Niemand bestreitet, dass es Dinge gibt, die rekursiv am sinnvollsten zu
realisieren sind.


Stefan
Andreas Burmester
2016-04-21 04:19:00 UTC
Permalink
Post by Rainer Weikusat
[...] Dennis Ritchie [...]
static char *utoa_(unsigned x, char *p)
{
if (x) (p = utoa_(x / 10, p))[-1] = x % 10 + '0';
return p + 1;
}
static void utoa(unsigned x, char *p)
{
if (x) utoa_(x, p)[-1] = 0;
else *p = '0', p[1] = 0;
}
static void utoa(unsigned x, char *p)
{
char tmp[32], *pt;
pt = tmp + sizeof(tmp);
*--pt = 0;
do *--pt = x % 10 + '0'; while (x /= 10);
while (*p = *pt) ++p, ++pt; /* strcpy(p, pt); */
}
static void utoa(unsigned x, char *d) {
char tmp[32], *p = tmp;

do *p++ = x % 10 + '0'; while( x /= 10 );
do *d++ = *--p; while( p > tmp );
*d = 0;
}

Wobei beide 32er fragwürdig sind: Weil 32 / log(2) < 107, versagt's wenn
unsigned aus mehr als 106 Bits besteht (bei hinreichend gesetzten). Kann
ja nun gut angehen.

Schön wäre es, wenn etwas wie

#include <limits.h>

#define STR0(ARG) #ARG
#define STR(ARG) STR0(ARG)

char tmp[sizeof(STR(UINT_MAX))];

funktionieren würde; muß aber nicht, weil nicht garantiert ist, dass
UINT_MAX zu einer erwarteten dezimalen Konstante expandiert. Könnte gar
etwas witziges wie -1U sein, was als String zur Dimensionierung von tmp
sicher zu kurz wäre.

Davon ab gefällt mir DMRs Version am besten - zwar nicht am leichtesten
les- & verstehbar, aber am elegantesten, IMHO.

b.
Peter J. Holzer
2016-04-24 09:31:52 UTC
Permalink
Post by Andreas Burmester
Wobei beide 32er fragwürdig sind: Weil 32 / log(2) < 107, versagt's wenn
unsigned aus mehr als 106 Bits besteht (bei hinreichend gesetzten). Kann
ja nun gut angehen.
Schön wäre es, wenn etwas wie
#include <limits.h>
#define STR0(ARG) #ARG
#define STR(ARG) STR0(ARG)
char tmp[sizeof(STR(UINT_MAX))];
funktionieren würde; muß aber nicht, weil nicht garantiert ist, dass
UINT_MAX zu einer erwarteten dezimalen Konstante expandiert. Könnte gar
etwas witziges wie -1U sein, was als String zur Dimensionierung von tmp
sicher zu kurz wäre.
Allerdings ist (sizeof(t) * CHAR_BIT) sicher eine obere Grenze für die
Anzahl der Binärziffern in t (für integrale Typen) und daraus lässt sich
die maximale Zahl der Dezimalziffern berechnen. Der Faktor konvergiert
gegen ln(2)/ln(10), ist für kleine Zahlen aber größer.

#define digits(t) ((sizeof(t) * CHAR_BIT) * 36 / 100)

char tmp[digits(unsigned int) + 1];

funktioniert für alle unsigned int mit mindestens 12 Bit (also alle, da
unsigned int mindestens 16 Bit hat). Nimmt man 41 als Faktor,
funktioniert es auch für unsigned char, selbst wenn CHAR_BIT==10 ist
(der ungünstigste Fall).

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Andreas Burmester
2016-05-05 03:44:29 UTC
Permalink
Post by Peter J. Holzer
Post by Andreas Burmester
Wobei beide 32er fragwürdig sind: Weil 32 / log(2) < 107, versagt's
wenn unsigned aus mehr als 106 Bits besteht (bei hinreichend
gesetzten). Kann ja nun gut angehen.
Schön wäre es, wenn etwas wie
#include <limits.h>
#define STR0(ARG) #ARG
#define STR(ARG) STR0(ARG)
char tmp[sizeof(STR(UINT_MAX))];
funktionieren würde; muß aber nicht, weil nicht garantiert ist,
dass UINT_MAX zu einer erwarteten dezimalen Konstante expandiert.
Könnte gar etwas witziges wie -1U sein, was als String zur
Dimensionierung von tmp sicher zu kurz wäre.
Allerdings ist (sizeof(t) * CHAR_BIT) sicher eine obere Grenze für
die Anzahl der Binärziffern in t (für integrale Typen) und daraus
lässt sich die maximale Zahl der Dezimalziffern berechnen. Der Faktor
konvergiert gegen ln(2)/ln(10), ist für kleine Zahlen aber größer.
Das ist genau der Umrechnungsfaktor. Da die Basis des Logarithmus hier
egal ist, kann man auch 10 nehmen. Damit:

ln(2) / ln(10) = lg(2) / lg(10) = lg(2) = 0.3010299957...

Das habe ich oben genommen.
Post by Peter J. Holzer
#define digits(t) ((sizeof(t) * CHAR_BIT) * 36 / 100)
char tmp[digits(unsigned int) + 1];
funktioniert für alle unsigned int mit mindestens 12 Bit (also alle,
da unsigned int mindestens 16 Bit hat). Nimmt man 41 als Faktor,
funktioniert es auch für unsigned char, selbst wenn CHAR_BIT==10 ist
(der ungünstigste Fall).
301 / 1000 ist 'ne ziemlich gute Annäherung an lg(2). Da Teile von
Stellen keinen Sinn ergeben, muss man die Decke des Produktes
Binärstellen * lg(2) für die Anzahl der Dezimalstellen nehmen. Durch
vorheriges Abschneiden der Nachkommastellen durch Int-Arithmetik ist die
Decke aber gerade "+ 1". Zusammen:

#define BIT_WIDTH(TYPE) ( sizeof(TYPE) * CHAR_BIT )
#define DEC_DIGITS(TYPE) ( BIT_WIDTH(TYPE) * 301 / 1000 + 1 )

Funktioniert für alle integralen Typen, CHAR_BIT spielt da keine Rolle.

Ein möglicher Überlauf durch das "* 301" sollte auch nicht sorgen -
keine Implementation mit minimaler 16 Bit-Arithmetik wird einen
integralen Typ mit mehr als 108 Bits aufweisen können...

b.
Andreas Burmester
2016-05-05 03:58:55 UTC
Permalink
Post by Peter J. Holzer
Post by Andreas Burmester
Wobei beide 32er fragwürdig sind: Weil 32 / log(2) < 107, versagt's
wenn unsigned aus mehr als 106 Bits besteht (bei hinreichend
gesetzten). Kann ja nun gut angehen.
Schön wäre es, wenn etwas wie
#include <limits.h>
#define STR0(ARG) #ARG
#define STR(ARG) STR0(ARG)
char tmp[sizeof(STR(UINT_MAX))];
funktionieren würde; muß aber nicht, weil nicht garantiert ist,
dass UINT_MAX zu einer erwarteten dezimalen Konstante expandiert.
Könnte gar etwas witziges wie -1U sein, was als String zur
Dimensionierung von tmp sicher zu kurz wäre.
Allerdings ist (sizeof(t) * CHAR_BIT) sicher eine obere Grenze für
die Anzahl der Binärziffern in t (für integrale Typen) und daraus
lässt sich die maximale Zahl der Dezimalziffern berechnen. Der Faktor
konvergiert gegen ln(2)/ln(10), ist für kleine Zahlen aber größer.
Das ist genau der Umrechnungsfaktor. Da die Basis des Logarithmus hier
egal ist, kann man auch 10 nehmen. Damit:

ln(2) / ln(10) = lg(2) / lg(10) = lg(2) = 0.3010299957...

Das habe ich oben genommen.
Post by Peter J. Holzer
#define digits(t) ((sizeof(t) * CHAR_BIT) * 36 / 100)
char tmp[digits(unsigned int) + 1];
funktioniert für alle unsigned int mit mindestens 12 Bit (also alle,
da unsigned int mindestens 16 Bit hat). Nimmt man 41 als Faktor,
funktioniert es auch für unsigned char, selbst wenn CHAR_BIT==10 ist
(der ungünstigste Fall).
301 / 1000 ist 'ne ziemlich gute Annäherung an lg(2). Da Teile von
Stellen keinen Sinn ergeben, muss man die Decke des Produktes
Binärstellen * lg(2) für die Anzahl der Dezimalstellen nehmen. Durch
vorheriges Abschneiden der Nachkommastellen durch Int-Arithmetik ist die
Decke aber gerade "+ 1". Zusammen:

#define BIT_WIDTH(TYPE) ( sizeof(TYPE) * CHAR_BIT )
#define DEC_DIGITS(TYPE) ( BIT_WIDTH(TYPE) * 301 / 1000 + 1 )

Funktioniert für alle integralen Typen, CHAR_BIT spielt da keine Rolle.

Ein möglicher Überlauf durch das "* 301" sollte auch nicht sorgen -
keine Implementation mit minimaler 16 Bit-Arithmetik wird einen
integralen Typ mit mehr als 108 Bits aufweisen können...

b.
Peter J. Holzer
2016-05-05 09:13:51 UTC
Permalink
Post by Andreas Burmester
Post by Peter J. Holzer
#define digits(t) ((sizeof(t) * CHAR_BIT) * 36 / 100)
char tmp[digits(unsigned int) + 1];
funktioniert für alle unsigned int mit mindestens 12 Bit (also alle,
da unsigned int mindestens 16 Bit hat). Nimmt man 41 als Faktor,
funktioniert es auch für unsigned char, selbst wenn CHAR_BIT==10 ist
(der ungünstigste Fall).
301 / 1000 ist 'ne ziemlich gute Annäherung an lg(2). Da Teile von
Stellen keinen Sinn ergeben, muss man die Decke des Produktes
Binärstellen * lg(2) für die Anzahl der Dezimalstellen nehmen. Durch
vorheriges Abschneiden der Nachkommastellen durch Int-Arithmetik ist die
#define BIT_WIDTH(TYPE) ( sizeof(TYPE) * CHAR_BIT )
#define DEC_DIGITS(TYPE) ( BIT_WIDTH(TYPE) * 301 / 1000 + 1 )
Ja, das ist eleganter.
Post by Andreas Burmester
Funktioniert für alle integralen Typen,
Fast. 301/1000 ist ein bisschen kleiner lg(2). Bei genügend großen Zahlen
liefert Deine Formel also ein zu kleines Ergebnis.

Beispiel:
2**196 - 1 == 100433627766186892221372630771322662657637687111424552206335.
Das sind 60 Stellen, 196 * 301 / 1000 + 1 ist aber nur 59.

Mit 302/1000 als Faktor ist es sicher.
Post by Andreas Burmester
CHAR_BIT spielt da keine Rolle.
Jein. CHAR_BIT bestimmt, welche Integer-Größen überhaupt möglich sind.
196 z.B. ist nicht durch 8 teilbar, bei CHAR_BIT==8 funktioniert Deine
Formel also trotzdem (selbst wenn es einen 196-Bit-Typ gäbe, müsste der
(mindestens) 25 Bytes belegen und damit würde die Formel 61 ergeben).
Aber natürlich gibt es auch Vielfache von 8, wo das Ergebnis zu klein
ist (z.B. 392), also spielt CHAR_BIT für die Korrektheit der Formel
über alle theoretisch möglichen Typen keine Rolle.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Andreas Burmester
2016-05-05 17:54:28 UTC
Permalink
Post by Peter J. Holzer
Post by Andreas Burmester
301 / 1000 ist 'ne ziemlich gute Annäherung an lg(2). Da Teile von
Stellen keinen Sinn ergeben, muss man die Decke des Produktes
Binärstellen * lg(2) für die Anzahl der Dezimalstellen nehmen. Durch
vorheriges Abschneiden der Nachkommastellen durch Int-Arithmetik ist die
#define BIT_WIDTH(TYPE) ( sizeof(TYPE) * CHAR_BIT )
#define DEC_DIGITS(TYPE) ( BIT_WIDTH(TYPE) * 301 / 1000 + 1 )
Ja, das ist eleganter.
Post by Andreas Burmester
Funktioniert für alle integralen Typen,
Fast. 301/1000 ist ein bisschen kleiner lg(2). Bei genügend großen Zahlen
liefert Deine Formel also ein zu kleines Ergebnis.
2**196 - 1 ==
100433627766186892221372630771322662657637687111424552206335.
Post by Peter J. Holzer
Das sind 60 Stellen, 196 * 301 / 1000 + 1 ist aber nur 59.
Schiet. Ich hatte natürlich gesehen, dass mein Faktor 0.01% (!) zu klein
ist, bis zur 'ner Bitbreite von 128 geprüft, dass sich das nicht, und
dann nicht erwartet, so früh auswirkt.

Korrektur also: Das DEC_DIGITS von oben funktioniert bis zur 'ner
Bitbreite von 195, sonst nehme man bis 298:

#define DEC_DIGITS(TYPE) \
( BIT_WIDTH(TYPE) * 301 / 1000 + 1 + (BIT_WIDTH(TYPE) == 196) )

(Womit Schlichtheit und Eleganz über den Jordan gegangen sind.)
Post by Peter J. Holzer
Mit 302/1000 als Faktor ist es sicher.
Oder so - wenn man ungefährlichere Fehler in der anderen Richtung (zu
viel Dezimalstellen, statt zu wenig) in Kauf nehmen will & kann.

b.
Andreas Burmester
2016-05-05 18:03:45 UTC
Permalink
Post by Peter J. Holzer
Post by Andreas Burmester
301 / 1000 ist 'ne ziemlich gute Annäherung an lg(2). Da Teile von
Stellen keinen Sinn ergeben, muss man die Decke des Produktes
Binärstellen * lg(2) für die Anzahl der Dezimalstellen nehmen. Durch
vorheriges Abschneiden der Nachkommastellen durch Int-Arithmetik ist die
#define BIT_WIDTH(TYPE) ( sizeof(TYPE) * CHAR_BIT )
#define DEC_DIGITS(TYPE) ( BIT_WIDTH(TYPE) * 301 / 1000 + 1 )
Ja, das ist eleganter.
Post by Andreas Burmester
Funktioniert für alle integralen Typen,
Fast. 301/1000 ist ein bisschen kleiner lg(2). Bei genügend großen Zahlen
liefert Deine Formel also ein zu kleines Ergebnis.
2**196 - 1 ==
100433627766186892221372630771322662657637687111424552206335.
Post by Peter J. Holzer
Das sind 60 Stellen, 196 * 301 / 1000 + 1 ist aber nur 59.
Schiet. Ich hatte natürlich gesehen, dass mein Faktor 0.01% (!) zu klein
ist, bis zur 'ner Bitbreite von 128 geprüft, dass sich das nicht, und
dann nicht erwartet, so früh auswirkt.

Korrektur also: Das DEC_DIGITS von oben funktioniert bis zur 'ner
Bitbreite von 195, sonst nehme man bis 298:

#define DEC_DIGITS(TYPE) \
( BIT_WIDTH(TYPE) * 301 / 1000 + 1 + (BIT_WIDTH(TYPE) == 196) )

(Womit Schlichtheit und Eleganz über den Jordan gegangen sind.)
Post by Peter J. Holzer
Mit 302/1000 als Faktor ist es sicher.
Oder so - wenn man ungefährlichere Fehler in der anderen Richtung (zu
viel Dezimalstellen, statt zu wenig) in Kauf nehmen will & kann.

b.

Rainer Weikusat
2016-04-27 20:29:56 UTC
Permalink
Post by Rainer Weikusat
[...]
Post by Stefan Reuther
Post by Thomas Jahns
Für solche Algorithmen kommt man dann auch mit
einer nichtrekursiven Implementierung selten darum herum, den Stack auf
dem Heap noch mal nachzubauen, was wiederum wie von Rainer W. schon
angedeutet deutlich weniger effizient ist als das simple Verschieben des
Stack-Pointers.
Es hat ein deutlich besseres Fehlerverhalten: 'malloc' kann einen Fehler
melden (und tut das auf entsprechenden Plattformen auch), eine
Stack-Allokation kann nur die weiße Fahne aka SIGSEGV hissen.
Es gibt allerdings rekursive Loesungen fuer Probleme von denen von
vorneherein bekannt ist, dass sie auf den Stack passen werden.
[...]
Post by Rainer Weikusat
NB: Das ist ein kuenstliches Beispiel, weil mir aus dem Stegreif gerade
kein richtiges einfaellt. Ich hatte aber schon welche.
Interessanteres Beispiel:

static void clone_msg_procs(struct msg_proc *from, struct msg_proc **to)
{
struct msg_proc *clone;

if (!from->p) return;

clone_msg_procs(from->p, to);

clone = clone_msg_proc(from);
clone->p = *to;
*to = clone;
}

Hier soll eine einfach verkettete Liste ('Pipeline') von
Nachrichtenverarbeitungsroutinen dupliziert werden, deren letztes
Element fester Bestandteil der uebergeordneten Struktur ist und deswegen
nicht dupliziert werden soll. Anfaenglich zeigt *to auf das End-Element
der Ziel-Pipeline, hinterher soll es auf das erste (duplizierte) Element
zeigen.

Auch hier duerfte ein iteratives Aequivalent recht einfach sein, aber
warum soll ich mir das im einzelnen auseinanderpuzzlen wenn:

1. Endelement? Nichts zu tun.
2. Dupliziere was noch vor uns liegt.
3. Dupliziere das momentane Element und fuege es am Anfang ein.

genausogut funktioniert[*]?

[*] Hoffe ich mal. Bislang habe ich mir das nur ueberlegt und nicht getestet.

Wer Endrekursionen benutzt isst auch Veganer.
Peter J. Holzer
2016-03-30 21:58:29 UTC
Permalink
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Die Volkswagen-AG hat fraglos die Berechtigung, ihren angestellten
vorzuschreiben, welche Programmiersprachen diese wie verwenden sollen.
Wenn ein externer Dienstleister für uns Code schreibt, haben wir
zweifellos genau die gleiche Berechtigung, ihm vorzuschreiben, welchen
formalen Kriterien dieser Code zu entsprechen hat.
Begrenzt. Eine Vorgabe "Rekursion darf nicht benutzt werden" kann man
zwar machen, aber es ist jedem freigestellt, dass als sachfremden Unfug[*] zu
anzusehen und solche Auftraege nicht anzunehmen[**].
Genauso kann ich als Angestellter unsinnige Regeln zum Anlass nehmen, mich
zu verabschieden.

Zugegeben, im Allgemeinen fällt einem Selbständigen bzw. einer Firma die
Entscheidung, einen Auftrag nicht anzunehmen, leichter als einem
Angestellten, zu kündigen.

Aber ich kenne genug Firmen, deren Existenz von 1 oder 2 Großkunden
abhängt - deren Vorgaben müssen sie schlucken, sonst sind sie pleite.
Auf der anderen Seite dürften die meisten kompetetenten Programmierer nur
geringe Probleme haben, einen anderen Arbeitsgeber zu finden.

Als so fundamentalen Unterschied sehe ich das also nicht.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Peter J. Holzer
2016-04-02 08:29:11 UTC
Permalink
Post by Peter J. Holzer
Eine Begründung halte ich in beiden Fällen für notwendig. Einerseits
hält man (ich zumindest) sich leichter an Regeln, deren Sinn man
versteht, andererseits ist eine Begründung eine notwendige Voraussetzung
für einen Review - und man sollte jede Regel regelmäßig auf ihre
Sinnhaftigkeit überprüfen.
Gerade dazupassend in einem unserer Projektwikis gefunden. Der
Einleitungssatz zu den für dieses Projekt gültigen Regeln:

| Diese Seite ist eternal work in progress. Erstens ist sie unvollständig
| und zweitens kann jede Regel in Frage gestellt werden. Wenn eine Regel
| nicht (mehr) sinnvoll erscheint, bitte im Daily Scrum ansprechen.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Peter J. Holzer
2016-03-29 20:43:35 UTC
Permalink
Post by G.B.
Post by Rainer Weikusat
Begruendung entfaellt dank
Autoritaet.
Die Begründung von Regeln entfällt in der Regel in jedem
Einzelfall, definitionsgemäß, sonst wären alle Regeln sehr
unregelmäßig Regeln: jede nur in ihrem Einzelfall anwendbar...
Häh? Natürlich begründet man die Regel nicht bei jedem einzelnen
Anwendungsfall. Das hat Rainer wohl auch nicht gemeint. Man begründet
eine Regel, wenn man sie einführt. Für die hier erwähnten MISRA-Regeln
könnten z.B. Begründungen sein:

* Die verbotenen Konstrukte sind zwar legales C, werden aber nur extrem
selten absichtlich eingesetzt. Häufiger passieren sie unabsichtlich
(vor allem ein vergessenes break), der Leser würde also
berechtigterweise zunächst annehmen, dass ein Fehler passiert ist und
müsste sich erst vom Gegenteil überzeugen

* Solche Konstrukte sind schwer zu lesen

* Weitverbreitete Code-Analyse-Tools kommen damit nicht zurecht

Man kann diesen Begründungen jetzt zustimmen oder auch nicht, aber sie
zeigen zumindest, dass sich jemand Gedanken gemacht hat.

Mit "Ich bin der Chef bzw. Auftraggeber. Hier sind die Regeln, Du hast
Dich daran zu halten" hingegen weiß man nicht, ob derjenige, der die
Regeln aufgestellt hat, ein Kontrollfreak ist, lieber Pascal als C lesen
würde oder einen Grund gesucht hat, einen Mitarbeiter zu kündigen.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2016-03-29 15:35:04 UTC
Permalink
Rainer Weikusat <***@talktalk.net> writes:

[...]
Post by Rainer Weikusat
unterstuetzt C tatsaechlich auch Mehrwege-Verzweigungen mit der
-----
#include <stdio.h>
#define given(x) switch (x)
Falls man nichts gegen einen erforderlichen aeusseren Block hat kann man
das noch wie folgt vereinfachen:

----
#include <stdio.h>

#define given(x) switch (x)
#define when(x) if (0) case (x):
#define or if (0) default:

int main(int argc, char **argv)
{
char *n, *p;

p = "e";

given (argc - 1) {
when (0)
n = "Keine";

when (1)
{
n = "Ein";
p = "";
}

when (2)
n = "Zwei";

when (3)
n = "Drei";

when (4)
n = "Vier";

or
n = "Ganz viele";
}

printf("%s Argument%s\n", n, p);
return 0;
}
----

und - um dem ganzen ein Sahnehaeubchen aufzusetzen - wenigstens gcc 4.7
uebersetzt das zu zwei Stringtabellen und zwei Ladeoperationen (ebenso
wie switch/ case/ break)
Stefan Reuther
2016-03-29 15:46:42 UTC
Permalink
Post by Rainer Weikusat
[switch/ if]
Post by Stefan Reuther
Post by Rainer Weikusat
Post by Stefan Reuther
Ganz klar die zweite, weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.
Welche Regeln waeren das denn zum Beispiel?
Z.B. MISRA-C 2004 Regel 15.1 (required) "A switch label shall only be
used when the most closely-enclosing compound statement is the body of a
switch statement."
Oder die allfällige "nutze kein fallthrough"-Regel, seit Jahrhunderten
von lint geprüft, von MISRA formuliert als Regel 15.2 (required) "An
unconditional break statement shall terminate every non-empty switch
clause", alternativ in HIS-Subset 1.0.3 als Regel 61 oder in JSF-AV-C++
als Regel 193.
Das heisst im Grunde genommen nichts als "switch soll so benutzt werden,
als ob es ein Pascal-case waere obwohl das mit potentiell
fehlertraechtigem Mehraufwand verbunden ist". Begruendung entfaellt dank
Autoritaet.
Deine Annahme des "potentiell fehlerträchtigen Mehraufwandes" hast du
jetzt auch nicht ernsthaft begründet.

Code wird öfter gelesen als geschrieben, und im Falle von Automotive,
Aviation, Medical auch öfter mal von anderen Tools als einem Compiler
analysiert. Denen will man helfen. Und ein 'switch', das in ein 'while'
reinspringt, ist halt schwer analysierbar, genau wie ein 'goto'.

Zudem gibt es bei Teamarbeit immer das Problem, dass die anderen nicht
solche Genies sind wie man selbst und die genialen Tricks, auf die man
in der letzten Nachtschicht so gekommen ist, einfach nicht verstehen
(gilt vom Standpunkt jedes Teammitglieds aus). Die Lektion zu lernen hat
bei mir auch eine Weile gebraucht, aber inzwischen investier ich auch
mehr Zeit darein, zu verhindern, dass man etwas falsch benutzt, als es
möglichst kurz und elegant zu machen. Wobei sich das nicht mal immer
widerspricht, gerade in C++.
Post by Rainer Weikusat
Post by Stefan Reuther
Um eine Code Zeile zu sparen mach ich mir jedenfalls nicht den Aufwand,
dafür eine deviation zu verargumentieren.
Das wuerde ich wohl auch nicht tun, zumal den Leuten, die diese lustigen
Regelwerke zusammenschreiben und vorschreiben, durchaus zu goennen ist,
sich mit dem Resultat herumaergern zu muessen.
Die Leute von MISRA oder JSF haben sich zumindest deutlich mehr Mühe
gegeben als die Manager vermuten lassen, die die Codierregeln ("cool,
hat schon jemand geschrieben, kann ich einfach verwenden") ihren
Lieferanten oder Entwicklern aufdrücken. Deswegen gibt's ja bei MISRA
z.B. den gerne überlesenen Deviation-Prozess.


Stefan
Rainer Weikusat
2016-03-29 19:08:02 UTC
Permalink
Post by Stefan Reuther
Post by Rainer Weikusat
[switch/ if]
Post by Stefan Reuther
Post by Rainer Weikusat
Post by Stefan Reuther
Ganz klar die zweite, weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.
Welche Regeln waeren das denn zum Beispiel?
Z.B. MISRA-C 2004 Regel 15.1 (required) "A switch label shall only be
used when the most closely-enclosing compound statement is the body of a
switch statement."
Oder die allfällige "nutze kein fallthrough"-Regel, seit Jahrhunderten
von lint geprüft, von MISRA formuliert als Regel 15.2 (required) "An
unconditional break statement shall terminate every non-empty switch
clause", alternativ in HIS-Subset 1.0.3 als Regel 61 oder in JSF-AV-C++
als Regel 193.
Das heisst im Grunde genommen nichts als "switch soll so benutzt werden,
als ob es ein Pascal-case waere obwohl das mit potentiell
fehlertraechtigem Mehraufwand verbunden ist". Begruendung entfaellt dank
Autoritaet.
Deine Annahme des "potentiell fehlerträchtigen Mehraufwandes" hast du
jetzt auch nicht ernsthaft begründet.
Wenn man die Angelegenheit mal andersherum betrachtet, dh nicht unter
dem Aspekt "welches Features muessen wir verbieten, um unerwuenschtes zu
vermeiden" sondern "welche Features koennten wir benutzen, um
erwuenschtes zur Verfuegung zu stellen" lassen sich - wie demonstriert -
Mehrwegeverzweigungen mit der gewuenschten Semantik in C recht einfach
implementieren. Damit entfaellt die Notwendigkeit, break-Zeilen
einzufuegen, um etwas zu vermeiden, dass man gar nicht benutzen moechte.
Stefan Reuther
2016-03-30 16:42:15 UTC
Permalink
Post by Rainer Weikusat
Post by Stefan Reuther
Post by Rainer Weikusat
Das heisst im Grunde genommen nichts als "switch soll so benutzt werden,
als ob es ein Pascal-case waere obwohl das mit potentiell
fehlertraechtigem Mehraufwand verbunden ist". Begruendung entfaellt dank
Autoritaet.
Deine Annahme des "potentiell fehlerträchtigen Mehraufwandes" hast du
jetzt auch nicht ernsthaft begründet.
Wenn man die Angelegenheit mal andersherum betrachtet, dh nicht unter
dem Aspekt "welches Features muessen wir verbieten, um unerwuenschtes zu
vermeiden" sondern "welche Features koennten wir benutzen, um
erwuenschtes zur Verfuegung zu stellen" lassen sich - wie demonstriert -
Mehrwegeverzweigungen mit der gewuenschten Semantik in C recht einfach
implementieren. Damit entfaellt die Notwendigkeit, break-Zeilen
einzufuegen, um etwas zu vermeiden, dass man gar nicht benutzen moechte.
Die These, syntaxverbiegende Makros zu verwenden sei wartbarer als ein
normales Sprachmittel so wie vorgesehen zu verwenden, ist zumindest
originell. Hab ich in der Form noch nicht gehört. (Als der Herr Bourne
seine Shell geschrieben hat, war ich nicht dabei.)


Stefan
Rainer Weikusat
2016-03-30 17:18:57 UTC
Permalink
Post by Stefan Reuther
Post by Rainer Weikusat
Post by Stefan Reuther
Post by Rainer Weikusat
Das heisst im Grunde genommen nichts als "switch soll so benutzt werden,
als ob es ein Pascal-case waere obwohl das mit potentiell
fehlertraechtigem Mehraufwand verbunden ist". Begruendung entfaellt dank
Autoritaet.
Deine Annahme des "potentiell fehlerträchtigen Mehraufwandes" hast du
jetzt auch nicht ernsthaft begründet.
Wenn man die Angelegenheit mal andersherum betrachtet, dh nicht unter
dem Aspekt "welches Features muessen wir verbieten, um unerwuenschtes zu
vermeiden" sondern "welche Features koennten wir benutzen, um
erwuenschtes zur Verfuegung zu stellen" lassen sich - wie demonstriert -
Mehrwegeverzweigungen mit der gewuenschten Semantik in C recht einfach
implementieren. Damit entfaellt die Notwendigkeit, break-Zeilen
einzufuegen, um etwas zu vermeiden, dass man gar nicht benutzen moechte.
Die These, syntaxverbiegende Makros zu verwenden sei wartbarer als ein
normales Sprachmittel so wie vorgesehen zu verwenden, ist zumindest
originell.
Ich koennte mich nicht daran erinnern, eine solche These in dieser
Allgemeinheit aufgestellt zu haben,
Post by Stefan Reuther
Hab ich in der Form noch nicht gehört. (Als der Herr Bourne
seine Shell geschrieben hat, war ich nicht dabei.)
insbesondere dann nicht, wenn sie auch Dinge wie

#define fi }
#define esac }

etc beeinhalten sollte, die keine Funktion haben.
Rainer Weikusat
2016-03-28 15:10:17 UTC
Permalink
[...]
Post by Stefan Reuther
Davon ab benutze ich enum auch ausschliesslich um mehr oder minder
zusammengehoerige Integer-Konstanten
zu definieren, zB
enum {
IPH1_MAGIC = 0x876b58a6b8d7,
IPH1_VER_SHIFT = 48,
IPH1_MAGIC_MASK = (1LLU << IPH1_VER_SHIFT) - 1,
IPH1_VER_SIZE = 8
};
und das macht diese Warnung ziemlich sinnlos.
In dem Fall, ja. Das ist auch pures C, da ist das nicht zu beanstanden.
In C++ haben enums einen etwas höheren Stellenwert, da vermeide ich das
inzwischen, und das färbt dann eben auf mein C ab...
Zunaechstmal sind das inhaltlich zusammengehoerende Konstanten, die
_keine_ Aufzaehlung von sich wechselseiting ausschliessenden
Eigenschaften darstellen. Traditionell wuerde man das in C mit Makros
machen aber ich ziehe es vor, die - wo moeglich - zugunsten "richtiger"
C-Konstrukte zu vermeiden: Ein Ausdruck a la

IPH1_MAGIC_MASK = (1LLU << IPH1_VER_SHIFT) - 1,

wird vom Compiler einmal berechnet und das Resultat als Ganzzahl im Code
benutzt, wo immer der Name auftaucht (Oder wengistens gcc tut das in
diesem Fall. Allerdings ist das Verhalten undefiniert weil der Wert
nicht als int reprasentierbar ist). Das ist weniger hakelig, als wenn
man ihn so schreiben muss, dass er fuer beliebige Textersetzungen
benutzt werden kann, und durchsichtiger als einfach stattdessen

0xffffffffffff

oder - Gott bewahre -

281474976710655

zu benutzen und ich halte auch die Namen fuer sinnvoll.

Code, mit dem ich ausserdem zu tun habe, der in einer Sprache
geschrieben ist, die gar keine Unterstuetzung fuer symbolische
Konstanten, die keine Auffzaehlungstypen sind, hat (Java) benutzt
'ueblicherweise' stattdessen entweder Strings (ausgesprochen bloede
Idee, weil der Compiler nicht merkt, wenn jemand den Text falsch
geschrieben hat) oder "vom Himmel gefallene Zahlen" (manchmal mit aber
meistens ohne Erklaerung).

Das kommt mir nicht wie eine "Verbesserung durch Aufzaehlungstyp" vor.
Stefan Reuther
2016-03-29 15:50:16 UTC
Permalink
[enum]
Post by Rainer Weikusat
Post by Stefan Reuther
In dem Fall, ja. Das ist auch pures C, da ist das nicht zu beanstanden.
In C++ haben enums einen etwas höheren Stellenwert, da vermeide ich das
inzwischen, und das färbt dann eben auf mein C ab...
Zunaechstmal sind das inhaltlich zusammengehoerende Konstanten, die
_keine_ Aufzaehlung von sich wechselseiting ausschliessenden
Eigenschaften darstellen. Traditionell wuerde man das in C mit Makros
machen aber ich ziehe es vor, die - wo moeglich - zugunsten "richtiger"
C-Konstrukte zu vermeiden: Ein Ausdruck a la
IPH1_MAGIC_MASK = (1LLU << IPH1_VER_SHIFT) - 1,
wird vom Compiler einmal berechnet und das Resultat als Ganzzahl im Code
benutzt, wo immer der Name auftaucht (Oder wengistens gcc tut das in
diesem Fall. Allerdings ist das Verhalten undefiniert weil der Wert
nicht als int reprasentierbar ist). Das ist weniger hakelig, als wenn
man ihn so schreiben muss, dass er fuer beliebige Textersetzungen
benutzt werden kann, [...]
Das kommt mir nicht wie eine "Verbesserung durch Aufzaehlungstyp" vor.
Für sowas nimmt man in C++ ja auch
const int IPH1_MAGIC_MASK = (1LLU << IPH1_VER_SHIFT) - 1;
was all die gleichen Vorteile hat, und zusätzlich den Typ nach Wahl.
Beim enum hingegen
enum State { Initializing, Loading, Crashed };
State s;
passt der Compiler deutlich auf, dass man nicht
s = 7;
schreibt.

In C kann man diese Sprachkonstrukte auch so nutzen, hat aber nicht alle
der Vorteile. Eigentlich nur den der Warnung beim switch...


Stefan
Claus Reibenstein
2016-03-28 10:47:51 UTC
Permalink
Post by Rainer Weikusat
[...] weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.
Welche Regeln waeren das denn zum Beispiel?
Zum Beispiel die Regeln der strukturierten Programmierung. Oder kannst
Du mir zu den von Dir genannten Beispielen oder Deinem geposteten Code
ein gültiges Struktogramm (Nassi-Shneiderman-Diagramm, DIN 66261)
erstellen, welches dem Programmablauf entspricht?

Dass es gültiges C ist, habe ich inzwischen begriffen. Die
Bauchschmerzen bleiben jedoch.

Gruß
Claus
Rainer Weikusat
2016-03-28 11:16:30 UTC
Permalink
Post by Claus Reibenstein
Post by Rainer Weikusat
[...] weil die erste so ziemlich allen mir bekannten
Codierregeln widerspricht.
Welche Regeln waeren das denn zum Beispiel?
Zum Beispiel die Regeln der strukturierten Programmierung. Oder kannst
Du mir zu den von Dir genannten Beispielen oder Deinem geposteten Code
ein gültiges Struktogramm (Nassi-Shneiderman-Diagramm, DIN 66261)
erstellen, welches dem Programmablauf entspricht?
Da der Sprachumfang dieser Codevisualisierungssprache nicht ausreicht,
um C zu beschreiben, geht das offensichtlich nicht. Inwiefern das ein
Defekt von C oder ein Defekt eines ploetzlichen Geisteblitzes von zwei
Studenten in Jahre 1972 ist, dessen Veroeffentlichung die ACM mit der
schoenen Begruendung

I feel the best thing the authors could do is to collect all
copies of this technical report and burn them before anybody
reads them. My opinion is that it shows the inexperience and
ignorance of the authors with respect to programming in general,
and their misunderstanding of so-called structured programming

abgelehnt hat, koennte man diskutieren.
Claus Reibenstein
2016-03-25 19:41:44 UTC
Permalink
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
Spätestens hier sollte der Compiler meckern, da zu diesem case kein
switch vorhanden ist.

Wo hast Du diesen kruden Code denn her?

Gruß
Claus
Peter J. Holzer
2016-03-25 20:14:58 UTC
Permalink
Post by Claus Reibenstein
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
Spätestens hier sollte der Compiler meckern, da zu diesem case kein
switch vorhanden ist.
Warum? Da ist doch ein switch?
Post by Claus Reibenstein
Wo hast Du diesen kruden Code denn her?
Tante Google verrät es Dir ;-).

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2016-03-25 20:22:50 UTC
Permalink
Post by Peter J. Holzer
Post by Claus Reibenstein
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
[...]
Post by Peter J. Holzer
Post by Claus Reibenstein
Wo hast Du diesen kruden Code denn her?
Tante Google verrät es Dir ;-).
In diesem Fall ist der Original-Autor allerdings (ausnahmsweise)
unschuldig: Das ist von mir. Urspruenglich hatte der switch() die beiden
ersten Faelle identisch behandelt, was allerdings geaendert werden
musste. Das habe ich zunaechst mal wie im ersten Posting hingeschrieben
und war erfreut darueber, den if-block so in den switch reinbauen zu
koennen, dass genau das passiert, was passierent sollte: Falls die
Bedingung nicht gegeben ist, soll Fall 1 wie Fall 2 behandelt werden,
andernfalls wie alle anderen.
Claus Reibenstein
2016-03-26 12:16:29 UTC
Permalink
Post by Peter J. Holzer
Post by Claus Reibenstein
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
Spätestens hier sollte der Compiler meckern, da zu diesem case kein
switch vorhanden ist.
Warum? Da ist doch ein switch?
Der switch befindet sich aber außerhalb des Blocks mit dem zweiten case,
also auf einer anderen Blockebene. Somit liegt der case nicht mehr im
Scope des switch. Dass das trotzdem compiliert und offensichtlich auch
so funktioniert, wie Rainer sich das gedacht hat, würde ich unter
"undefined behavior" verbuchen.
Post by Peter J. Holzer
Post by Claus Reibenstein
Wo hast Du diesen kruden Code denn her?
Tante Google verrät es Dir ;-).
Hmmm ... wonach muss ich suchen?

Gruß
Claus
Rainer Weikusat
2016-03-26 12:49:33 UTC
Permalink
Post by Claus Reibenstein
Post by Peter J. Holzer
Post by Claus Reibenstein
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
Spätestens hier sollte der Compiler meckern, da zu diesem case kein
switch vorhanden ist.
Warum? Da ist doch ein switch?
Der switch befindet sich aber außerhalb des Blocks mit dem zweiten case,
also auf einer anderen Blockebene. Somit liegt der case nicht mehr im
Scope des switch.
Diese Ansicht ist nicht konform mit der C-Norm. Wie ich bereits
geschrieben hatte, ist die Definition von switch

switch (expression) statement

Eine Definition von statement is

compound statement

Das wiederum ist definiert als

{ block-item-list }

diese als

block-item
block-item-list block-item

und block-item als

declaration
statement

IOW, ein zusammengesetztes (compound) statement kann andere statements enthalten,
die selber auch wieder compound statements sein koennen. Es gibt auch
keine Einschraenkungen bzgl Label-Positionierung ausser

If a switch statement has an associated case or default label within the
scope of an identifier with a variably modified type, the entire
switch statement shall be within the scope of that identifier.

was hier nicht anwendbar ist.

NB: Ich lasse mich gerne eines besseren belehren, allerdings nicht durch
blosse Behauptungen.

https://en.wikipedia.org/wiki/Duff%27s_device
Rainer Weikusat
2016-03-26 13:09:11 UTC
Permalink
Post by Rainer Weikusat
Post by Claus Reibenstein
Post by Peter J. Holzer
Post by Claus Reibenstein
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
Spätestens hier sollte der Compiler meckern, da zu diesem case kein
switch vorhanden ist.
Warum? Da ist doch ein switch?
Der switch befindet sich aber außerhalb des Blocks mit dem zweiten case,
also auf einer anderen Blockebene. Somit liegt der case nicht mehr im
Scope des switch.
Diese Ansicht ist nicht konform mit der C-Norm. Wie ich bereits
geschrieben hatte, ist die Definition von switch
switch (expression) statement
"Niemand kann jemals irgendetwas tun ohne es nach sich selber zu
benennen und darueber im WWW zu gackern"[*]

http://pigeonsnest.co.uk/stuff/pigeons-device.html

[*] "Proudly without any from of web self-presentation since Mosaic was
hot" :-)
Claus Reibenstein
2016-03-26 16:30:56 UTC
Permalink
Post by Rainer Weikusat
http://pigeonsnest.co.uk/stuff/pigeons-device.html
Noch mehr Bauchschmerzen.

Gruß
Claus
Peter J. Holzer
2016-03-26 17:10:43 UTC
Permalink
Post by Claus Reibenstein
Post by Rainer Weikusat
http://pigeonsnest.co.uk/stuff/pigeons-device.html
Noch mehr Bauchschmerzen.
Und noch mehr: http://www.clifford.at/cfun/cliffdev/

(Vielleicht sollte man mal eine Liste aller "$NAME's device"s anlegen
;-).

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Juergen Ilse
2016-03-27 00:27:22 UTC
Permalink
Hallo,
Post by Claus Reibenstein
Post by Rainer Weikusat
http://pigeonsnest.co.uk/stuff/pigeons-device.html
Noch mehr Bauchschmerzen.
... und auch das ist (obwohl haesslicher Stil) voellig standardkonformes C
(zumindest soweit ich es auf den ersten Blick sehe).

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Claus Reibenstein
2016-03-26 16:30:32 UTC
Permalink
Post by Rainer Weikusat
IOW, ein zusammengesetztes (compound) statement kann andere
statements enthalten, die selber auch wieder compound statements sein
koennen.
Bis hierhin stimme ich Dir uneingeschränkt zu.
Post by Rainer Weikusat
Es gibt auch keine Einschraenkungen bzgl Label-Positionierung ausser
Doch, die gibt es: "A case or default label shall appear only in a
switch statement" (6.8.1p2). Über Labels innerhalb von
Compound-Statements oder if-Anweisungen oder Schleifen steht dort
nirgends etwas.
Post by Rainer Weikusat
NB: Ich lasse mich gerne eines besseren belehren, allerdings nicht
durch blosse Behauptungen.
Die "blossen Behauptungen" stützen sich auf das, was im Standard steht
bzw. nicht steht.
Post by Rainer Weikusat
https://en.wikipedia.org/wiki/Duff%27s_device
Ich bekomme Bauchschmerzen, wenn ich solchen Code sehe. Ich wäre nie im
Leben auf die Idee gekommen, so etwas zu programmieren, und schon gar
nicht, dass das tatsächlich gültiges C wäre und definiertes Verhalten
zeigte.

Gruß
Claus
Peter J. Holzer
2016-03-26 17:32:36 UTC
Permalink
Post by Claus Reibenstein
Post by Rainer Weikusat
IOW, ein zusammengesetztes (compound) statement kann andere
statements enthalten, die selber auch wieder compound statements sein
koennen.
Bis hierhin stimme ich Dir uneingeschränkt zu.
Post by Rainer Weikusat
Es gibt auch keine Einschraenkungen bzgl Label-Positionierung ausser
Doch, die gibt es: "A case or default label shall appear only in a
switch statement" (6.8.1p2). Über Labels innerhalb von
Compound-Statements oder if-Anweisungen oder Schleifen steht dort
nirgends etwas.
Ein switch-Statement hat die Form:

switch ( expression ) statement

Statement wiederum rekursiv definiert als:

statement:
labeled-statement
compound-statement
expression-statement
selection-statement
iteration-statement
jump-statement

compound-statement:
{ block-item-listopt }

block-item-list:
block-item
block-item-list block-item
block-item:
declaration
statement

Ein Statement kann also ein compound-statement sein (im Fall des
Teil-Statements des Switch-Statements wird das fast immer der Fall sein,
weil man sonst kein Switch braucht), das wiederum aus Statements
besteht, die wiederum Compound-Statements sein können, usw.

Das gilt ganz generell in C. Man muss nicht extra dazuschreiben "Und
übrigens, für das Statement im Switch-Statement gelten die gleichen
Regeln wie für andere Statements."

Daher endet "das Switch-Statement" in

1 switch (iph2->status) {
2 case PHASE2ST_ADDSA:
3 if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
4 case PHASE2ST_ESTABLISHED:
5 trg = &to_list;
6 break;
7 }

8 default:

9 trg = &from_list;
10 }

mit der schließenden Klammer in Zeile 10. Die beiden case-Labels sind
ganz eindeutig in diesem Switch-Statement und nicht außerhalb. Die
Einschränkung ist somit erfüllt.
Post by Claus Reibenstein
Post by Rainer Weikusat
NB: Ich lasse mich gerne eines besseren belehren, allerdings nicht
durch blosse Behauptungen.
Die "blossen Behauptungen" stützen sich auf das, was im Standard steht
bzw. nicht steht.
Post by Rainer Weikusat
https://en.wikipedia.org/wiki/Duff%27s_device
Ich bekomme Bauchschmerzen, wenn ich solchen Code sehe.
Es gibt viel C-Code, der mir Bauchschmerzen verursacht. Das heißt nicht,
dass er ungültig ist.
Post by Claus Reibenstein
Ich wäre nie im Leben auf die Idee gekommen, so etwas zu
programmieren, und schon gar nicht, dass das tatsächlich gültiges C
wäre und definiertes Verhalten zeigte.
Naja, Duff's Device habe ich wohl in der Einführungsvorlesung
kennengelernt oder vielleicht kurz danach. Sicher bevor der
ANSI-C-Standard verabschiedet wurde. Insofern kann ich nicht sagen, ob
ich selbst auf die Idee gekommen wäre - das ist für mich einfach eine
der Eigenheiten von C, die man selten bis nie verwendet, aber als
C-Programmierer natürlich kennt, so wie z.B. auch die Kommutativität des
[]-Operators.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Juergen Ilse
2016-03-27 00:32:28 UTC
Permalink
Hallo,
Post by Claus Reibenstein
Post by Rainer Weikusat
IOW, ein zusammengesetztes (compound) statement kann andere
statements enthalten, die selber auch wieder compound statements sein
koennen.
Bis hierhin stimme ich Dir uneingeschränkt zu.
Post by Rainer Weikusat
Es gibt auch keine Einschraenkungen bzgl Label-Positionierung ausser
Doch, die gibt es: "A case or default label shall appear only in a
switch statement" (6.8.1p2). Über Labels innerhalb von
Compound-Statements oder if-Anweisungen oder Schleifen steht dort
nirgends etwas.
Wenn es in einem compound statement steht, das seinerseits wieder in einem
switch-statement steht, steht es damit auch innerhalb des switch-statements.
Ja, der Abschnitt ist wirklich so zu verstehen.
Post by Claus Reibenstein
Post by Rainer Weikusat
https://en.wikipedia.org/wiki/Duff%27s_device
Ich bekomme Bauchschmerzen, wenn ich solchen Code sehe. Ich wäre nie im
Leben auf die Idee gekommen, so etwas zu programmieren, und schon gar
nicht, dass das tatsächlich gültiges C wäre und definiertes Verhalten
zeigte.
Es ist zugegebenermassen haesslich, aber trotz alledem gueltiges C und
vom Sprachstandard definiert.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Peter J. Holzer
2016-03-26 14:23:28 UTC
Permalink
Post by Claus Reibenstein
Post by Peter J. Holzer
Post by Claus Reibenstein
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
Spätestens hier sollte der Compiler meckern, da zu diesem case kein
switch vorhanden ist.
Warum? Da ist doch ein switch?
Der switch befindet sich aber außerhalb des Blocks mit dem zweiten case,
also auf einer anderen Blockebene. Somit liegt der case nicht mehr im
Scope des switch.
Das ist falsch. Ein Scope in C enthält verschachtelte Blöcke. Würdest Du
behaupten, dass in folgendem Code

{
int a;
if (...) {
a; // <===
}
}

in der markierten Zeile die Variable a nicht in scope sei, weil sie auf
einer anderen Blockebene liegt?

Ein innerer Scope kann aber einen äußeren überdecken. In

{
int a = 1;

a; # 1
{
int a = 2;
a; # 2
{
a; # 2
}
}
a; # 1

gibt es zwei Variablen a.

Ebenso gibt es in

switch(x) {
case 1: // <= a
{
case 2: // <= b
switch(y) {
case 1: // <= c
case 2: // <= d
}
case 3: // <= e
}
case 4: // <= f
}

zwei Switch-Scopes. Der äußere Scope enthält sie case Labels in Zeilen
a, b, e und f, der innere die in c und d. Wenn x den Wert 2 hat,
verzeigt das äußere switch nach Zeile b, wenn y den Wert 2 hat, das
innere nach Zeile d. Dass da noch ein Block dazwischen ist, ist
irrelevant.
Post by Claus Reibenstein
Dass das trotzdem compiliert und offensichtlich auch
so funktioniert, wie Rainer sich das gedacht hat, würde ich unter
"undefined behavior" verbuchen.
Da der Standard das Verhalten definiert, ist nicht "undefined".
Post by Claus Reibenstein
Post by Peter J. Holzer
Post by Claus Reibenstein
Wo hast Du diesen kruden Code denn her?
Tante Google verrät es Dir ;-).
Hmmm ... wonach muss ich suchen?
Nach den Switch-Labels. Da findet man Source-Code von racoon (Teil einer
ipsec-Implementation).

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Claus Reibenstein
2016-03-26 16:14:39 UTC
Permalink
Das geht auch kürzer.
Post by Peter J. Holzer
Post by Claus Reibenstein
Der switch befindet sich aber außerhalb des Blocks mit dem zweiten case,
also auf einer anderen Blockebene. Somit liegt der case nicht mehr im
Scope des switch.
Das ist falsch. Ein Scope in C enthält verschachtelte Blöcke. Würdest Du
behaupten, dass in folgendem Code
{
int a;
if (...) {
a; // <===
}
}
in der markierten Zeile die Variable a nicht in scope sei, weil sie auf
einer anderen Blockebene liegt?
Es geht hier nicht um Variablen (dieser Fall ist im Standard eindeutig
festgelegt), sondern um case-Label. Ich finde im Standard nichts, was
darauf hindeutet, dass diese in einem inneren Block liegen dürfen, und
schon gar nicht innerhalb einer inneren if-Anweisung oder Schleife.
Post by Peter J. Holzer
Post by Claus Reibenstein
Dass das trotzdem compiliert und offensichtlich auch
so funktioniert, wie Rainer sich das gedacht hat, würde ich unter
"undefined behavior" verbuchen.
Da der Standard das Verhalten definiert, ist nicht "undefined".
Wo denn? Ich finde nichts dergleichen.

Gruß
Claus
Rainer Weikusat
2016-03-26 16:36:38 UTC
Permalink
Claus Reibenstein <***@kabelmail.de> writes:

[...]
Post by Claus Reibenstein
Post by Peter J. Holzer
in der markierten Zeile die Variable a nicht in scope sei, weil sie auf
einer anderen Blockebene liegt?
Es geht hier nicht um Variablen (dieser Fall ist im Standard eindeutig
festgelegt), sondern um case-Label. Ich finde im Standard nichts, was
darauf hindeutet, dass diese in einem inneren Block liegen dürfen, und
schon gar nicht innerhalb einer inneren if-Anweisung oder Schleife.
Ein 'innerer Block' (und alles in einem inneren Block) ist nichts als
ein Statement. Das habe ich hier schon zweimal aufgedroeselt und nichts
gefunden, was eine solche Verwendung von switch, fuer die es uebrigens
auch noch historische Vorbilder gibt,

The device is legal dpANS C. I cannot quote chapter and verse, but
Larry Rosler, who was chairman of the language subcommittee (I think),
has assured me that X3J11 considered it carefully and decided that it
was legal. Somewhere I have a note from dmr certifying that all the
compilers that he believes in accept it. Of course, the device is also
legal C++, since Bjarne uses it in his book

untersagen wuerde.

Fuer den anderen Teil:

labeled-statement:
identifier : statement
case constant-expression : statement
default : statement

sowie

A case or default label shall appear only in a switch statement. Further
constraints on such labels are discussed under the switch statement.

Die einzige 'weitere Einschraenkung', die sich auf
case-Label-Positionierung bezieht, hatte ich ebenfalls bereits
angefuehrt.

Es gibt hier keinen Unterschied zwischen

switch (a) {
case 0:
if (b) ++c;

case 1:
++c;
break;

default:
++e;
}

switch (a) {
case 0:
if (b) { ++c;

case 1:
++c;
}
break;

default:
++e;
}

Das zweite ++c ist in statement 'in' einem switch-Statement, welchem ein
case-Label vorraussgeht.

Insofern das Verhalten nicht ausdruecklich undefiniert genannt wird,
muesste ein 'shall constraint' verletzt werden. Welches?
Juergen Ilse
2016-03-27 00:41:36 UTC
Permalink
Hallo,
Post by Claus Reibenstein
Es geht hier nicht um Variablen (dieser Fall ist im Standard eindeutig
festgelegt), sondern um case-Label. Ich finde im Standard nichts, was
darauf hindeutet, dass diese in einem inneren Block liegen dürfen, und
schon gar nicht innerhalb einer inneren if-Anweisung oder Schleife.
Duerfen sie aber. Der Standard verlangt, dass case-Labels innerhalb des
zugehoerigen switch liegen, dass ist aber auch dann der Fall, wenn sie
in einer schleife oder einer Verzweigung innerhalb des switch liegen.
Ja, das ist im Standard wirklich so gemeint und damit ist das Verhalten
der angefuehrten "ungewoehnlichen Code-Schnipsel" sehr wohl definiert.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Stefan Reuther
2016-03-27 11:01:19 UTC
Permalink
Post by Claus Reibenstein
Post by Peter J. Holzer
Post by Claus Reibenstein
Der switch befindet sich aber außerhalb des Blocks mit dem zweiten case,
also auf einer anderen Blockebene. Somit liegt der case nicht mehr im
Scope des switch.
Das ist falsch. Ein Scope in C enthält verschachtelte Blöcke. Würdest Du
behaupten, dass in folgendem Code
{
int a;
if (...) {
a; // <===
}
}
in der markierten Zeile die Variable a nicht in scope sei, weil sie auf
einer anderen Blockebene liegt?
Es geht hier nicht um Variablen (dieser Fall ist im Standard eindeutig
festgelegt), sondern um case-Label. Ich finde im Standard nichts, was
darauf hindeutet, dass diese in einem inneren Block liegen dürfen, und
schon gar nicht innerhalb einer inneren if-Anweisung oder Schleife.
Es steht nichts drin, das es verbietet, und die Grammatik gibt es her.
Also darf es. Verboten sind nur case/default außerhalb eines switch.

Es steht auch nichts drin, das es verbietet, innerhalb eines
Additionsausdrucks den Multiplikationsoperator zu verwenden (a+b*c+d*e),
und die Grammatik gibt es her. Also darf man das.
Post by Claus Reibenstein
Post by Peter J. Holzer
Post by Claus Reibenstein
Dass das trotzdem compiliert und offensichtlich auch
so funktioniert, wie Rainer sich das gedacht hat, würde ich unter
"undefined behavior" verbuchen.
Da der Standard das Verhalten definiert, ist nicht "undefined".
Wo denn? Ich finde nichts dergleichen.
"A switch statement causes control to jump to, into, or past the
statement that is the switch body, ..."


Stefan
Rainer Weikusat
2016-03-25 21:24:58 UTC
Permalink
Post by Claus Reibenstein
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
Spätestens hier sollte der Compiler meckern, da zu diesem case kein
switch vorhanden ist.
Grammatisch ist das korrekt: Switch ist definiert als

switch (expression) statement

und

{
if (....) {
}
}

ist ein Statement. Die semantische Definition sagt auch ausdruecklick

A case or default label is accessible only within the closest enclosing
switch statement.
Georg Bauhaus
2016-03-26 17:35:36 UTC
Permalink
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
break;
}
trg = &from_list;
}
Wer anhand der case-Labels und anderen Namen erraten kann, wo das
herkommt, bekommt einen virtuellen Harzer Handkaese.
Wenn nicht schon so viel Gehüpfe im umgebenden Text wäre, dann
erschiene mir ein offen zugegegebener Sprung als eine mögliche
Alternative:

switch (iph2->status) {
case PHASE2ST_ADDSA:
if (to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))
goto _DEFAULT;
case PHASE2ST_ESTABLISHED:
trg = &to_list;
break;

default: _DEFAULT:
trg = &from_list;
}

Vorausgesetzt freilich, dass nicht über den Einsatz grammatische Wildkräuter
Programmierer fern gehalten werden sollen, die keine Zeit für historisch
gewachsene Vielfalt und anderen vermeintlichen Hexentrunk aufbringen.
--
"HOTDOGS ARE NOT BOOKMARKS"
Springfield Elementary teaching staff
Rainer Weikusat
2016-03-27 11:20:42 UTC
Permalink
Post by Georg Bauhaus
Post by Rainer Weikusat
switch (iph2->status) {
if (!(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))) {
trg = &to_list;
break;
}
trg = &from_list;
}
Wer anhand der case-Labels und anderen Namen erraten kann, wo das
herkommt, bekommt einen virtuellen Harzer Handkaese.
Wenn nicht schon so viel Gehüpfe im umgebenden Text wäre, dann
erschiene mir ein offen zugegegebener Sprung als eine mögliche
switch (iph2->status) {
if (to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen))
goto _DEFAULT;
trg = &to_list;
break;
trg = &from_list;
}
Das halte ich nicht fuer eine gute Idee: Der Sprung ist in beiden
Faellen gleich 'offen zugegeben', wenigstens fuer jemandem, dem das
Konzpet vertraut ist, aber der Block macht das sehr viel sichtbarer als
eine goto im Fliesstext. Mit goto wuerde ich das ungefaehr so
ausdruecken:

if (iph2->status == PHASE2ST_ESTABLISHED) goto move_it;

if (iph2->status == PHASE2ST_ADDSA
&& !(to_list.new_remote && iph2->spidx_gen && !getsp(iph2->spidx_gen)))
move_it:
trg = &to_list;
else
trg = &from_list;

Aber mir persoenlich saehe das zu sehr nach einer Verrenkung aus, zu der
sich jemand genoetigt sah, weil ihm der Gedanke mit dem switch nicht
gekommen ist. Falls die Sprache eine Kontrollstruktur oder Kombination
von Kontrollstrukturen zur Verfuegung stellt, mit deren Hilfe sich die
gewuenschte Bedeutung ohne Umwege, dh ohne bedingte Verzweigungen
abhaengig von ausschliesslich dafuer eingefuehrten Hilfsvariablen,
ausdruecken laesst, sollte man die meiner Ansicht nach benutzten.
Post by Georg Bauhaus
Vorausgesetzt freilich, dass nicht über den Einsatz grammatische Wildkräuter
Programmierer fern gehalten werden sollen, die keine Zeit für historisch
gewachsene Vielfalt und anderen vermeintlichen Hexentrunk aufbringen.
Das kann man viel effizienter mit semantischen Wildkraeutern machen
wofuer gerade racoon ein schoenes Lehrbeispiel bietet: ZB wird darin
jede doppelt verkettete Liste konsequent als Baum bezeichnet um ein
relativ harmloses Beispiel anzufuehren.
Loading...