Ich habe eine dämliche Frage, für die ich mich ja eigentlich schämen muß, aber ich komme so auf die Schnelle nicht drauf ( smile ich freue mich auf die negativen Lesenswert Bewertungen, aber auch auf die, möglicherweise, Lösung, die dann 1000 negative Bewertungen wert ist)
Unter avr-gcc und arm-none-eabi-gcc funktioniert in meinen Sourcen folgendes (alte Codes von mir halt):
1
if (bl & 0x01) charlieA_set(); else charlieA_clr();
Unter riscv-none-elf-gcc (Version 14.2.0) funktioniert das nicht und ich muß das hier (mit geschweiften Klammern schreiben.
Gibt es einen Compilerschalter mit dem das so funktioniert wie im ersten Beispiel (augenscheinlich verwende ich für's Compileren die gleichen Standartoptionen) ?
Eigentlich sollte man Makros mit Großbuchstaben benennen, damit sowas
nicht passiert, bzw. auffällig ist.
Wenn er es in Großbuchstaben schreibt, behebt er damit nicht das Problem. Es hat hier gerade keinen Sinn, über Konventionen zu diskutieren.
Meine Vermutung ist auch, dass es sich um ein schlecht gekapseltes Makro handelt, was aus zwei oder mehr Statements besteht. Damit passt dann ohne Klammern der else-Zweig nicht mehr.
Abhilfe:
Die Macros in
1
do{....}while(0)
einbetten.
Oder grundsätzlich geschweifte Klammern nach if, while, for verwenden, was ich persönlich immer mache. Der Source wird lesbarer und eine nachträgliche Einfügung eines zusätzlichen Statements in den if- oder else-Zweig führt nicht mehr zu einer nachträglichen zwangsweisen Einfügung von geschweiften Klammern, denn diese sind ja schon da!
Da können wir uns ja glücklich schätzen, dass das exakte Fehlerbild und auch das vermeidliche Makro geheim bleiben müssen. So kann man, völlig ungestört durch Fakten, herrlich im Nebel stochern.
Ich provoziere das Verhalten einmal mit dem folgenden Code. Aus den obigen Antworten geht hervor, dass das Verhalten in der if-else Bedingung tatsächlich darauf zurück zu führen ist, dass die Anweisungen Makros sind.
Wenn er es in Großbuchstaben schreibt, behebt er damit nicht das
Problem. Es hat hier gerade keinen Sinn, über Konventionen zu
diskutieren.
:-) Danke .... Ich weiß, dass es eine allgemeine Konvention ist, Makros groß zu schreiben, aber für einige Fälle im Programm gefällt mir das bis heute nicht.
Meine Vermutung ist auch, dass es sich um ein schlecht gekapseltes Makro
handelt, was aus zwei oder mehr Statements besteht. Damit passt dann
ohne Klammern der else-Zweig nicht mehr.
Ganz genau das ist es (wie ich das jetzt herausfinde).
Oder grundsätzlich geschweifte Klammern nach if, while, for verwenden,
was ich persönlich immer mache.
Mittlerweile mache ich das auch, dieses Verhalten habe ich immer dann, wenn ich alten Code (eben "Jugendsünden" von mir) portiere und wiederverwende.
Die if-else Bedingung wird korrekt übersetzt, wenn man bspw. das Makro ähnlich dem hier kapselt:
1
#define led_set() ({PC0_set();})
Scheinbar sind die Makros der GPIO-Portzugriffe wie bspw. PC0_set(), die ich für avr-gcc und arm-none-eabi-gcc geschrieben habe besser gekappselt, als die, die für riscv-none-elf-gcc (und innerhalb dieser weitere Makros verwendet werden, die nicht von mir sind).
Abschließend vielen Dank für Euro Antworten (und ein schönes Wochenende für Euch)
Tue dir einen Gefallen und vermeide künftig die Verwendung von Makros anstelle von Funktionen. Das provoziert nur Probleme und schwer verständliche Fehlermeldungen.
Solche einzeiligen Funktionen optimiert der gcc schon weg. Darum musst du dich nicht manuell kümmern.
Wenn Du wirklich herausfinden willst, was da passiert, sieh Dir an, wie PC0_set definiert ist. Das wird wenigstens bei einem Deiner beiden Compiler wieder ein Macro sein.
Zum Thema Macros anstelle von Funktionsaufrufen hat Nepomuk schon fast alles wesentliche geschrieben.
Wenn Du tatsächlich glaubst, an so einer Stelle bereits optimieren zu müssen, denke a) an "premature optimisation is the root of all evil" und b) daran, daß Du auch komplette Funktionen via /inline/ deklarieren kannst. Und denke nochmal an a).
Ich weiß, dass es eine allgemeine Konvention ist, Makros
groß zu schreiben, aber für einige Fälle im Programm gefällt mir das bis
heute nicht.
Das ist ja das schöne an C: man kann sich damit nicht nur selber ins Knie schiessen, man hat sogar die freie Wahl des Kalibers, und auch, wie oft es vorher noch durch die Brust und Auge gehen soll.
Das ist ja das schöne an C: man kann sich damit nicht nur selber ins
Knie schiessen, man hat sogar die freie Wahl des Kalibers, und auch, wie
oft es vorher noch durch die Brust und Auge gehen soll.
Tue dir einen Gefallen und vermeide künftig die Verwendung von Makros
anstelle von Funktionen. Das provoziert nur Probleme und schwer
verständliche Fehlermeldungen.
Diese Ansicht teile ich nicht. Denn gerade dafür sind Makros ja gemacht. Und bewaffnet mit der Fehlermeldung kann man dem Compiler ja den präprozessierten Quelltext entlocken (gcc -E) und sieht das Problem dann meist sofort.
Solche einzeiligen Funktionen optimiert der gcc schon weg. Darum musst
du dich nicht manuell kümmern.
Jein. Die Optimierung ist nicht das vordringliche Problem. Obwohl es natürlich lästig ist, wenn der Compiler für eine einzige Instruktion einen Funktionsaufruf mit Prolog und Epilog konstruiert. Und der Compiler optimiert es ja auch nicht immer und automatisch weg. Und ältere Compiler können es u.U. gar nicht. Das sind mir zuviele Wenns.
Ich gestalte meine µC-Programme meist so, daß ich Hardware-Zugriffe (insbesondere Port-Zugriffe) kapsele. Also ein einfaches HAL das in hal.h kommt und überall includiert wird wo ich es brauche. Und da sind es der Einfachheit halber eben Makros, die wie Funktionen aussehen. Natürlich könnte ich das in Deklaration und Definition trennen, aber wozu? Das brigt (mir) keinen Mehrwert. Und was irgendwer als "reine Lehre" propagiert, geht mir am A**** vorbei. Zumal die "reine Lehre" oft nur die "aktuelle Mode" ist. Ich bin zu alt um der Mode hinterher zu laufen.
Axel S. schrieb im Beitrag #7913519: Und was irgendwer als "reine
Lehre" propagiert, geht mir am A**** vorbei. Zumal die "reine Lehre" oft
nur die "aktuelle Mode" ist. Ich bin zu alt um der Mode hinterher zu
laufen.
Das eingangs Problem, ist das Produkt aus mehreren Verstößen gegen die abgelehnten modernen Methoden. Diese "modernen" Empfehlungen für Makros sind sicher schon 1/2 Jahrhundert alt
Die Verstöße:
Kleinschreibung des Makro Bezeichners
Vortäuschung einer Funktion durch die völlig unnützen ()
Das ist eine der klassischen Methoden, wie man sich selber und andere verarschen kann.
Einzig und alleine weil die Verarschung hübscher aussieht, als die Realität.
Klarer: Eine Dummheit.
Und da sind es der Einfachheit halber eben Makros,
die wie Funktionen aussehen.
Wenn es wie ein Funktionsaufruf aussieht, sollte es sich auch wie eine Funktion verhalten. Das entspricht dem "Prinzip der geringsten Verwunderung".
Der TO ist in die selbst gestellte Falle getappt.
Und deine Argumentation sagt mir, dass du auch mit einem Fuß darin stehst.
Das nur weil die Falle schöner aussieht, als die Wahrheit.
Ich habe eine dämliche Frage, für die ich mich ja eigentlich schämen
muß
Ja, beides stimmt. Stell Dich also sofort in die Ecke.
Bloß nicht das Target benennen und auf keinen Fall die erste Fehlermeldung C&P (d.h. im exakten Wortlaut).
Wer antworten will, soll es ja schön schwer haben.
Nur echte Weicheier posten eine Codefrage so vollständig, daß man sie dem Compiler vorsetzen kann.
Für die AVRs benutze ich meine "sbit.h", die alle IO-Pins einfach als Bitvariable definiert. Bitvariablen kann man nämlich direkt zuweisen oder testen ohne umständliches if/else Gemäre.
Wenn jemand einen Fehler nicht selber finden kann ist es völlig normal dass er nicht weiß, welcher Kontext notwendig ist, für eine erfolgversprechende Fehlersuche.
Es ist völlig unangemessen sich sein eigens Ego auf Kosten Unterlegener polieren zu wollen. Eine einfache Frage/Hinweis nach mehr Kontext ist völlig ausreichend.
Nochmal in die Ecke stell, es gibt keine Art des Stehens.
Wenn man schon das Bedürfnis hat Rechtschreibfehler zu korrigieren, kann man auch darauf hinweisen dass "Standard" mit "d" am Ende geschrieben wird und das ein häufiger Fehler ist.
Auch hier ist es völlig unnötig, sich auf Kosten andere zu erbauen ohne einen einzigen Hinweis auf den Rechtschreibfehler.
Vortäuschung einer Funktion durch die völlig unnützen ()
Wenn sich ein Makro in jeder Hinsicht (also sowohl syntaktisch als auch
semantisch) wie eine gewöhnliche C/C++-Funktion verhält, ist es IMHO
völlig in Ordnung, es wie eine solche zu behandeln.
Ein Makro verhält sich wie eine gewöhnliche Funktion, wenn folgende
Bedingungen erfüllt sind:
Es hat wie eine gewöhnliche Funktion eine in Klammern eingeschlossene
Argumentliste. Hat das Makro keine Argumente, bleibt der Raum
zwischen den Klammern leer.
Der Makroaufruf kann überall dort stehen, wo auch ein Funktionsaufruf
stehen kann, also insbesondere auch innerhalb einer if-Anweisung.
Die Makroargumente werden genau ein einziges Mal ausgewertet, wie
dies auch bei einem Funktionsaufruf der Fall ist.
Bei solchen Makros können und sollten IMHO dieselben Namenskonventionen
(Groß-/Kleinschreibung, Verwendung von Underscores usw.) wie für
gewöhnliche Funktionen zur Anwendung kommen. Guter Stil ist es zudem,
wenn nebeneffektbehaftete Makros ein Verb im Namen enthalten.
Um Bedingung 2 zu erfüllen, müssen bei Makros, die mehrere Anweisungen
enthalten, diese in do { ... } while(0) eingeschlossen werden.
Alternativ kann auch ({ ... }) verwendet werden (Statement Expression),
was aber nicht standardkonform ist. Statement Expressions sind auch
manchmal notwendig, um Bedingung 3 zu erfüllen.
Bei den Makros des TE ist Bedingung 2 nicht erfüllt, weil offensichtlich
irgendwo weiter unten in der Makrohierarchie ein Makro mit mehreren
Anweisungen ohne die vorgenannten Maßnahmen definiert wurde.
Das ist eine der klassischen Methoden, wie man sich selber und andere
verarschen kann.
Einzig und alleine weil die Verarschung hübscher aussieht, als die
Realität.
Klarer: Eine Dummheit.
...
Wenn es wie ein Funktionsaufruf aussieht, sollte es sich auch wie eine
Funktion verhalten. Das entspricht dem "Prinzip der geringsten
Verwunderung".
Verarschung, Dummheit, ...
Starke Worte von einem, der sich Arduino-Fanboy nennt :)
Gerade die Arduino-Umgebung macht vor, wie man es /nicht/ machen sollte.
Mindestens die Makros min, max, abs, constrain und sq erfüllen Bedingung
3 nicht und verhalten sich damit /nicht/ wie gewöhnliche Funktionen.
Trotzdem werden deren Namen entgegen des Fanboys Forderung /nicht/ in
Großbuchstaben geschrieben, und die Doku nennt diese Makros sogar
"function". Immerhin enthält die Doku /inzwischen/ vage Hinweise darauf,
dass diese "Funktionen" gerne mal fehlerhafte Ergebnisse liefern. Besser
als diese Hinweise zu schreiben, wäre es wohl gewesen, das Problem
einfach zu fixen. C++ und der GCC bieten mehr als genug Möglichkeiten
dafür.
Wenn jemand einen Fehler nicht selber finden kann ist es völlig normal
dass er nicht weiß, welcher Kontext notwendig ist, für eine
erfolgversprechende Fehlersuche.
Nö, es ist nicht normal, eine Frage so zu stellen, als käme es auf jede Sekunde Deiner Schreibarbeit an. Soll sich gefälligst der Helfer mehr Mühe geben.
Völlig normal ist es aber, sich ein paar Gedanken zu machen, was notwendig wäre, falls jemand nicht in Deinen Kopf schauen kann.
Die exakte Fehlermeldung ist das absolut mindeste.
ohne
einen einzigen Hinweis auf den Rechtschreibfehler.
Der Hinweis ist doch deutlich genug. Und falls man darüber erst nachdenken muß, prägt es sich umso besser ein. Man könnte aber auch einfach aufhören, die rote Wellenlinie unter dem fehlerhaften Wort zu ignorieren.
Mindestens die Makros min, max, abs, constrain
und sq erfüllen Bedingung 3 nicht und verhalten
sich damit nicht wie gewöhnliche Funktionen.
Welche davon ist auf meinem Mist gewachsen?
Keine!
Dein Versuch mich dafür verantwortlich zu machen ist gründlich daneben.
Eines Moderators nicht würdig!
Starke Worte von einem, der sich Arduino-Fanboy nennt :)
Ja, von mir aus...
Ich verstehe sogar den Wunsch, mich auf diese Art und weise diskriminieren/verletzen zu wollen.
Ist es doch mit eine der typischen Arduino Basher Methoden.
Noch weit unter den Niveau einer Bordsteinkante.
Von mir aus könnt ihr euch weiter gerne mit solchen Fake Funktionen rumschlagen...
Wie dumm das laufen kann sehen wir im Eingangsposting.
Oder eben auch wenn eine der drei genannten Fake Funktionen mit z.B. i++ aufgerufen wird.
Ich verstehe sogar den Wunsch, mich auf diese Art und weise
diskriminieren/verletzen zu wollen.
Ich will dich damit nicht diskriminieren oder verletzen, sondern war
nur etwas erstaunt darüber, dass ausgerechnet ein Arduino-Fanboy die
Arduino-Macher der Verarsche und der Dummheit bezichtigt. Ich finde es
zwar genauso wie du nicht gut, was die Jungs da fabriziert haben, aber
ich würde sie deswegen nicht mit solchen Worten beschimpfen oder gar
beleidigen.
Es bleibt allerdings die Frage, warum man Dinge in Macros verpackt, die so tun, als ob sie eine Funktion wären.
Das ist /premature optimisation/, denn das, was man damit wohl erzielen will (kein Funktionsaufruf, kein Overhead deswegen) kann der Compiler auch selbst - "inline" ist das magische Wort dafür.
aber ich würde sie deswegen nicht mit solchen Worten beschimpfen
oder gar beleidigen.
Punkt 1:
Ich habe nicht angefangen über die Arduino Infrastruktur zu schimpfen,
Das hast du gemacht und mir gerade in den Mund gelegt.
Punkt 2:
Der TO hast sich mit seinen Makros selber verarscht und dann das if bzw. den Compiler und seine Schalter verantwortlich gemacht.
Und ja ich halte es für dumm, Funktionen mit Makros vorzutäuschen.
Egal wer das tut.
Punkt 3:
Es macht einen Unterschied, ob ich ein Verhalten oder eine Person für Dumm erkläre.
Denn, ein Verhalten kann sich ändern. Eine dumme Person kann das nicht.
Also nein, ich habe keine Person als dumm bezeichnet.
Und damit auch niemanden beleidigt.
Merke:
Selbst die schlauesten Leute können dumme Ideen haben.
Nochmal in die Ecke stell, es gibt keine Art des Stehens.
Mein passiv gekühlter PC hat genau eine Standart-Option: hochkant. Seitlich ist extra ein großer gelber Pfeil aufgedruckt.
Die andere Lehre aus diesem Thread: wenn man etwas als Funktion formulieren kann, baut man gefälligst kein Macro! Was spricht denn dagegen? Jedenfalls, wenn man mit arm-none-eabi-gcc oder riscv-none-elf-gcc unterwegs ist?
Das ist premature optimisation, denn das, was man damit wohl erzielen
will (kein Funktionsaufruf, kein Overhead deswegen) kann der Compiler
auch selbst - "inline" ist das magische Wort dafür.
Das "inline"-Schlüsselwort ist nur ein Vorschlag an den Compiler. Was
dieser damit macht, ist seine Sache und hängt u.a. vom Compilertyp, der
Version und der ausgewählten Optimierungsstufe ab. Wenn man das "inline"
weglässt, kann der Compiler einen Funktionsaufruf dennoch inlinen, wenn
er es für sinnvoll hält. Der GCC tut dies bei kleinen Funktionen und
eingeschalteter Optimierung auch recht häufig, so dass die Verwendung
von "inline" oft gar keinen Unterschied macht.
Um dem Programmierer etwas mehr Kontrolle zu geben, bieten die meisten
Compiler die Möglichkeit, Inlining zu erzwingen (bspw. GCC und Clang mit
attribute((always_inline))), aber dies ist nicht für alle Compiler
einheitlich und schon gar nicht vom C-Standard abgedeckt.
Garantiertes, standardkonformes und portables Inlining ist deswegen nur
mit Makros möglich.
Da Mikrocontrollerprogramme in der Regel ohnehin nur eingeschränkt
portabel sind, verwende ich dort gerne Inline-Funktionen statt
function-like Makros, dann aber oft mit dem always_inline-Attribut, um
nichts dem Zufall zu überlassen.
Das "inline"-Schlüsselwort ist nur ein Vorschlag an den Compiler.
Gewiss, aber die Fälle, wo man sich als Programmierer ernsthaft Gedanken darüber machen muss, die Fälle, wo der Overhead normale Funktionsaufrufe tatsächlich ein Problem darstellt, sind verschwindend gering.
Das ist genauso wie das von manchen Leuten betriebene krampfhafte Vermeiden von Floating-Point-Arithmetik oder der Verwendung von /printf/.
Die Leute haben irgendwann irgendwo mal aufgeschnappt, daß das Probleme bereiten kann, viel Speicher verbrauchen kann, und deswegen meiden sie es wie der Teufel das Weihwasser, völlig egal, ob ihr µC nicht doch zig Kilobyte brachliegendes Flashrom hat, und auch völlig egal, ob ihr µC nicht Größenordnungen schneller ist als der, für den das mal als Problem beschrieben wurde.
Und das ist es, was ich /premature optimisation/ nenne. Unnötiger fehlerträchtiger Aufriss, um ein nicht existierendes Problem zu umgehen.
Ein Makro garantiert auch nicht dass der Code ge-inlined wird. Insbesondere mit der bei AVR beliebten Optimizer Stufe -s zerlegt der Compiler Funktionen mit wiederholten Zeilen (auch Makros) oft in Unterfunktionen.
Wer Board-spezifische I/O Zugriffe unbedingt in einer *.h Datei sammeln will, darf das gerne tun. Das geht mit ganzen Funktionen ebenso, wie mit Makros.
Und das ist es, was ich premature optimisation nenne.
Premature optimisation ist nicht per se schlecht.
Ja, sie ist es, wenn sie ohne Nachzudenken durchgeführt wird.
Sie ist es nicht bei der Entwicklung von HALs, Bibliotheken und
sonstigen für die häufige Wiederverwendung vorgesehenen Softwaremodulen,
da in diesen Fällen damit gerechnet werden muss, dass der Code auch in
zeitkritischen Anwendungen zum Einsatz kommt.
Sie ist es auch nicht, wenn vor vornherein feststeht, dass CPU-Zeit und
Speicher knapp werden, weil bspw. von extern vorgegebene Randbedingungen
keine überdimensionierte Hardware erlauben.
All diejenigen, die auf premature optimisation verzichten, kommen evtl.
irgendwann in eine Situation, in der eine "postmature" optimisation
erforderlich ist.
Was ich damit sagen möchte: Auch wenn Compiler immer besser optimieren,
sollte man die Handoptimierung nicht grundsätzlich verdammen. Natürlich
sollte sie mit Sinn und Verstand durchgeführt werden. Gerade bei so
einfachen Dingen wie das Schalten von Output-Pins beträgt der Overhead
eines Funktionsaufrufs oft ein Vielfaches der eigentlichen Operation,
dabei ist der Aufwand für die Optimierung per Makro oder always_inline
minimal.
Unnötiger fehlerträchtiger Aufriss, um ein nicht existierendes Problem
zu umgehen.
Im konkreten Fall wurde der Fehler direkt durch den Compiler aufgedeckt.
Das Problem war lediglich, dass der (wohl noch etwas unerfahrene) TE die
Fehlermeldung nicht richtig einordnen konnte. Andere hingegen (Udo K.
und Frank M.) gaben – obwohl sie die Fehlermeldung nicht einmal kannten
– sofort den richtigen Hinweis auf die Fehlerursache, und Frank lieferte
sogar gleich die Lösung mit.
Beim nächsten Mal wird auch der TE nicht mehr lange suchen müssen.
Ein Makro garantiert auch nicht dass der Code ge-inlined wird.
Insbesondere mit der bei AVR beliebten Optimizer Stufe -s zerlegt der
Compiler Funktionen mit wiederholten Zeilen (auch Makros) oft in
Unterfunktionen.
Wirklich? Auch bei so einfachen Dingen wie dem Beschreiben von
I/O-Registern? Das hatte ich noch nie erlebt (zumindest ist es mit noch
nie aufgefallen. Aber das kann natürlich sein und ist auch völlig
richtig, denn mit der Option -Os dagt der Programmier dem Compiler:
"Mach den Code so klein wie möglich, und sei es auf Kosten der
Geschwindigkeit"
Wenn die Geschwindigkeit Priorität hat, wird er die Option -O2 oder -O3
wählen.
Unnötiger fehlerträchtiger Aufriss, um ein nicht existierendes Problem
zu umgehen.
Manche Programmiere fühlen sich überlegen, weil sie die Tricks der 80er Jahre immer noch beherrschen. Ich kann das gut nachempfinden. Aus dem Grund hatte ich lange Zeit das Arduino System abgelehnt. Mir ist das erst im Nachhinein bewusst geworden.
Gerade bei so
einfachen Dingen wie das Schalten von Output-Pins beträgt der Overhead
eines Funktionsaufrufs oft ein Vielfaches der eigentlichen Operation
Aber gerade diese Funktionsaufrufe optimiert der Compiler bereits weg.
Wirklich? Auch bei so einfachen Dingen wie dem Beschreiben von I/O-Registern?
Ein simples SBI oder CBI wird der Compiler nicht zerlegen, sondern umgekehrt den eventuell überflüssigen Funktionsrahmen drumherum entfernen (falls vorhanden). Er tut das auch oft bei geringfügig komplexeren Funktionen, die nur wenige Assembler Befehle ergeben.
Bei der Diskussion sollte man im Hinterkopf behalten, das Funktionsaufrufe beim AVR "billiger" sind, als z.B bei ARM Cortex-M basierten Mikrocontrollern.
Insbesondere mit der bei AVR beliebten Optimizer Stufe -s zerlegt der
Compiler Funktionen mit wiederholten Zeilen (auch Makros) oft in
Unterfunktionen.
Nein, soweit ich weiß gibt es eine solche Optimierung (Code Outlining) nicht im GCC; egal für welche Sprache, egal für welches Target.
Wirklich? Auch bei so einfachen Dingen wie dem Beschreiben von
I/O-Registern? Das hatte ich noch nie erlebt (zumindest ist es mit noch
nie aufgefallen. Aber das kann natürlich sein und ist auch völlig
richtig, denn mit der Option -Os dagt der Programmier dem Compiler:
"Mach den Code so klein wie möglich, und sei es auf Kosten der
Geschwindigkeit"
Das wäre -Oz. Aber selbst mit -Oz kann / macht GCC kein Code-Outlining.
Wenn Du wirklich herausfinden willst, was da passiert, sieh Dir an, wie
PC0_set definiert ist. Das wird wenigstens bei einem Deiner beiden
Compiler wieder ein Macro sein.
Na ja, PC0_set ist ein Makro von mir, aaaaaber dieses ruft seinerseits wieder ein Makro aus CH32FUN auf, von dem ich dachte, dass es eine Funktion und nicht selbst ein Makro ist (Fehler by me).
Das wird sich erledigen, wenn ich mein Setup durch eigene GPIO-Funktionen von CH32FUN ersetzt habe, bis dahin habe ich das in PC0_set und Konsorten gekappsel.
Wenn Du tatsächlich glaubst, an so einer Stelle bereits optimieren zu
müssen, denke a) an "premature optimisation is the root of all evil" und
b) daran, daß Du auch komplette Funktionen via inline deklarieren
kannst. Und denke nochmal an a).
Das hat bei mir mit Optimierung noch nichts zu tun, sondern ist dem Grund geschuldet, dass ich für die von mir verwendeten Controllerfamilien immer eine Include-Datei habe, bei der ich im Stile von PC0_set etc. GPIO's nach demselben Muster anspreche.
Grundsätzlich ist die Anfrage hier einem Irrtum meinerseits geschuldet, ich dachte, dass der Compiler die Fehlermeldung:
1
scratch.c: In function 'main':
2
scratch.c:54:35: error: 'else' without a previous 'if'
Nochmal in die Ecke stell, es gibt keine Art des Stehens.
Vielleicht meinte ich ein Kofferwort für "Kunst des Stehens" ? Nein, für ein falsch geschriebenes Wort stelle ich mich nicht in die Ecke, denn das hier ist ein Technikforum und kein Forum für Deutschlehrer, aber, weil es manche Lehrer so mögen:
1
for (int i= 0; i< 100; i++)
2
{
3
printf("\n\rIch schreibe nie wieder Standard mit 't'");
Im konkreten Fall wurde der Fehler direkt durch den Compiler aufgedeckt.
Das Problem war lediglich, dass der (wohl noch etwas unerfahrene) TE die
Fehlermeldung nicht richtig einordnen konnte. Andere hingegen (Udo K.
und Frank M.) gaben – obwohl sie die Fehlermeldung nicht einmal kannten
– sofort den richtigen Hinweis auf die Fehlerursache, und Frank lieferte
sogar gleich die Lösung mit.
:-) :-) hätte ich richtig hingesehen, hätte ich das wohl auch richtig einordnen können. Umso erstaunlicher ist, dass Udo und Frank (ohne die Fehlermeldung zu kennen) das korrekt eingeordnet haben: Respekt.
Hier jedoch eine "Frage" (mit Anmerkung): Ich kann mich garantiert nicht mit Yalu, Jörg oder Frank messen, aber ab wann gilt man nicht mehr als unerfahren? (Ich programmiere seit fast 40 Jahren auch mit C, muß ich mich jetzt noch einmal schämen und noch einmal in die Ecke stellen?)
Ich sollte mich an einen meiner Leitsprüche halten: Wer lesen kann, ist klar im Vorteil (hier dann Fehlermeldungen).
:-) :) auch wenn ich mich vor Peinlichkeit in die Ecke stellen mußte ein herzliches Danke an die, die geholfen haben (bis zur nächsten Peinlichkeit),
Das hat bei mir mit Optimierung noch nichts zu tun, sondern ist dem
Grund geschuldet, dass ich für die von mir verwendeten
Controllerfamilien immer eine Include-Datei habe, bei der ich im Stile
von PC0_set etc. GPIO's nach demselben Muster anspreche.
Ich bin in den moderneren C Eigenheiten nicht ganz so fit wie in C++...
In C++ ist es mittlerweile so, dass man Code in *.h Dateien unterbringen kann. Ohne define.
In dem man Funktionen als static oder inline deklariert.
Kann C das mittlerweile auch?
Hier jedoch eine "Frage" (mit Anmerkung): Ich kann mich garantiert nicht
mit Yalu, Jörg oder Frank messen, aber ab wann gilt man nicht mehr als
unerfahren? (Ich programmiere seit fast 40 Jahren auch mit C, muß ich
mich jetzt noch einmal schämen und noch einmal in die Ecke stellen?)
Als erfahren gilt man, wenn man in if Ausdrücken Konstanten nicht mehr verwendet. Über unnötige Klammern wird noch gestritten :-)
1
if((c%13)==0)SchiessMirInsKnie();
2
// wird zu
3
if(c%UART_BUFFER_SIZE==0)IstBesserLesbarSo();
Makros sind meiner Meinung nach in Ordnung, wenn man damit was macht was in reinem C nicht geht. Der Präprozessor ist das eigentlich geniale Ding in C, der macht die Sprache praxistauglich. Damit geht mehr als mit Templates in C++ auf Kosten der Typsicherheit.
Aber schämen musst du dich deswegen nicht. Wie immer gilt: Es muss erst mal funktionieren.
Dann definieren wir mal für eine RGB-LED die drei RGB-Werte für die Farbe "PINK" bei einem Arduino mega2560. Musste ich dann in "ALTROSA" umtaufen. Ging halt nicht. Alle anderen Farben ließen sich definieren. Darauf komme erstmal einer.
Soviel zu Makros. Danach schaut man dann gehauer hin.
Ich bin in den moderneren C Eigenheiten nicht ganz so fit wie in C++...
In C++ ist es mittlerweile so, dass man Code in *.h Dateien unterbringen
kann. Ohne define.
In dem man Funktionen als static oder inline deklariert.
Kann C das mittlerweile auch?
Das ist eine richtig üble Entwicklung in C++, die zum Glück wieder rückläufig ist dank LTO. Du hast kein klar definiertes Interface mehr, sondern ein Mischmasch aus externen Deklarationen, und vom Modul intern verwendeten Funktionen, dazu eine Wurscht aus unlesbarem Code mit schlechter Formatierung und automatisch generierter Dokumentation als Draufgabe. Die Idee eines Moduls wird dadurch zu Grabe getragen: Kapselung und Wiederverwendbarkeit von Code. Also trenn sowas auf in *.h (Deklarationen für Anwender), *.hpp (der Template und Inline Mist) und *.cpp (wird immer weniger).
Wenn verständlicher, robuster und wartbarer Code keine Rolle spielt,
kann man das so sehen.
Klar sind das alles erstrebenswerte Ziele, aber der Zweck eines
Programms ist die Funktion.
Wenns nicht mehr als die blinkende LED am Mikroprozessor sein soll, vielleicht. Ansonsten ist die reine Funktion zwar eine notwendige, aber keine hinreichende Bedingung für ein Stück Software.
Ich bevorzuge in C++ *.h only, verzichte wenn möglich auf *.cpp
u.A. wegen: Denn so sind alle Methoden per default inline.
Deine Abwertung "Template Mist", zeigt sehr schön deine Gesinnung.
Ich halte, deine Wertung define vs. Template für gründlich daneben.
Wie auch immer, du musst damit leben, nicht ich, also mach mal weiter so.
Na ja, PC0_set ist ein Makro von mir, aaaaaber dieses ruft seinerseits
wieder ein Makro aus CH32FUN auf, von dem ich dachte, dass es eine
Funktion und nicht selbst ein Makro ist (Fehler by me).
In ch32fun.h, Zeile 895 sind die geschweiften Klammern und das Semikolon
überflüssig und schädlich
Um das Makro ohne Probleme auch als Teilausdruck innerhalb eines
übergeordneten Ausdrucks verwenden zu können (was aber selten sinnvoll
ist), setzt man das Ganze am besten noch in runde Klammern.
Ich bin in den moderneren C Eigenheiten nicht ganz so fit wie in C++...
In C++ ist es mittlerweile so, dass man Code in *.h Dateien unterbringen
kann. Ohne define.
In dem man Funktionen als static oder inline deklariert.
Kann C das mittlerweile auch?
Das kann C praktisch schon immer.
In C++ aber kann auch die Implementierung einer Memberfunktion (weder static noch inline) einer Klasse in einer Headerdatei untergebracht werden.
/inline/ zu nutzen ist halt ein Fall von Optimierungsbemühungen; Funktionen als /static/ in einer Headerdatei zu definieren, sorgt für aufgeblähten Code (da die Funktionen in jede "translation unit", die die Headerdatei einbindet, hineinkopiert werden). Gut, das kann der Optimierer dann wieder aufräumen ...
Als guten Stil betrache nicht nur ich so etwas nicht, nicht in C.
Aber da sehe ich auch die Verwendung von Macros, insbesondere mehrfach geschachtelte Macros nicht als guten Stil an.
Dann definieren wir mal für eine RGB-LED die drei RGB-Werte für die
Farbe "PINK" bei einem Arduino mega2560. Musste ich dann in "ALTROSA"
umtaufen. Ging halt nicht. Alle anderen Farben ließen sich definieren.
Darauf komme erstmal einer.
Soviel zu Makros. Danach schaut man dann gehauer hin.
Dieses Problem (mehrfach belegte Symbole, neudeutsch "name clash") ist
nicht auf Makros beschränkt, sondern kann bspw. auch bei Variablen- und
Funktionsnamen auftreten.
Der Präprozessor ist das eigentlich geniale Ding in C, der macht die
Sprache praxistauglich. Damit geht mehr als mit Templates in C++ auf
Kosten der Typsicherheit.
Die richtig guten Makros gibt es sowieso nur in Lisp. Die können
praktisch alles, was C-Makros und C++-Templates können, aber noch sehr,
sehr viel mehr :)
Um das Makro ohne Probleme auch als Teilausdruck innerhalb eines
übergeordneten Ausdrucks verwenden zu können (was aber selten sinnvoll
ist), setzt man das Ganze am besten noch in runde Klammern.
#define (funDigitalWrite( pin, value ) GpioOf( pin )->BSHR =
1<<((!(value))*16 + ((pin) & 0xf)))
Deine Abwertung "Template Mist", zeigt sehr schön deine Gesinnung.
Ich halte, deine Wertung define vs. Template für gründlich daneben.
Wie auch immer, du musst damit leben, nicht ich, also mach mal weiter
so.
Das Problem mit Templates ist halt das sie unleserlich sind. Schau einfach mal in die STL rein, wenn du es nicht glaubst. Makros haben dasselbe Problem. Hätte C++ die wichtigen Typen wie vector, array und string als eingebaute Typen festgelegt, gäbe es viele viele graue Haare weniger.
Soweit mir bekannt ist die "Template engine" Turing vollständig.
Der Präprozessor nicht.
Der Präprozessor ist Turing Complete, wenn auch mit vielen Verrenkungen.
Und der Präprozessor muss sich nicht an die C Regeln halten, man kann also noch viel mehr damit anstellen :-)
Dann definieren wir mal für eine RGB-LED die drei RGB-Werte für die
Farbe "PINK" bei einem Arduino mega2560. Musste ich dann in "ALTROSA"
umtaufen. Ging halt nicht.
Einfach die Fehlermeldung lesen, die stößt Dich direkt mit der Nase auf Deinen Fehler.
Du kannst ja #undef verwenden, dann geht es auch. Wenn Port K nur Ausgang ist, brauchst Du ja PINK nicht.
Die Compilerbauer geben sich schon große Mühe, sinnvolle Fehlermeldungen zu erzeugen.
Wir hatte mal einen Programmierer für unsere Anlage, der hat fast alle Fehler auf "Insufficient Water Cooling" geleitet. Das wurde dann zum Running Gag, d.h. daran lag es nie. Wenn ein Kunde uns dieses Fehlermeldung durchsagte, mußte immer ein Servicetechniker ausrücken.
Einfach die Fehlermeldung lesen, die stößt Dich direkt mit der Nase auf
Deinen Fehler.
Man hätte die komplette Fehlermeldung damals lesen sollen, stimmt!
Schon der Verweis auf die io.h, die pgmspace.h und die Arduino.h hätte einem zu Denken geben müssen.
Ich hatte mich seinerzeit nur über das volatile und die fehlende Klammer gewundert.
1
staticconstuint16_tPINK=0xF81F;
1
In file included from c:\arduino_portable_1.8.13\portable\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include\avr\io.h:99:0,
2
from c:\arduino_portable_1.8.13\portable\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include\avr\pgmspace.h:90,
3
from C:\Arduino_portable_1.8.13\portable\packages\arduino\hardware\avr\1.8.5\cores\arduino/Arduino.h:28,
4
from C:\Users\ar\AppData\Local\Temp\arduino_build_25786\sketch\LHM600_11.ino.cpp:1:
5
LHM600_11:35:23: error: expected unqualified-id before 'volatile'
6
static const uint16_t PINK = 0xF81F;
7
^
8
LHM600_11:35:23: error: expected ')' before 'volatile'
9
LHM600_11:35:23: error: expected ')' before 'volatile'
ja, mit "#undef PINK" funktioniert es, weil wir das PIN-Register vom PortK tatsächlich nicht brauchen. Nice. ;)
1
#undef PINK
2
staticconstuint16_tPINK=0xF81F;
1
Der Sketch verwendet 28262 Bytes (11%) des Programmspeicherplatzes. Das Maximum sind 253952 Bytes.
Besten Dank, wäre ich selbst nicht drauf gekommen, das so zu lösen.
Bei mir tat es halt "ALTROSA", was ich als pragmatische Lösung empfand.
Man hätte die komplette Fehlermeldung damals lesen sollen, stimmt!
Die Fehlermeldung ist umfangreicher, weil einmal ein Macro und danach eine Variable deklariert wird.
Ich würde daher Variablen nicht großschreiben. Man kann ja globale Variablen mit einem Großbuchstaben am Anfang schreiben "Pink".
Man kann ja globale
Variablen mit einem Großbuchstaben am Anfang schreiben "Pink".
Schwierig.
Ich weiß, dass es verschiedene Ansichten zu gutem Stil gibt.
Somit auch gerne Streit darüber.
Mein Vorschlag:
Variablen-, Methoden- und Funktionsbezeichner beginnen mit einem Kleinbuchstaben.
Selbst definierte Typen mit einem Großbuchstaben.
Defines, komplett in Großbuchstaben.
Längere Bezeichner in KamelHöckerSchreibweise.
Auf Unterstriche ganz verzichten.
Der letzte Punkt mag für C anders aussehen, wenn man Module baut.
z.B. alle Gui Dinge mit einem gui_ beginnen lassen. (gui_init())
Für C++ verwendet man da eher die Geltungsbereiche der Klassen oder Namespaces
Ich bin in den moderneren C Eigenheiten nicht ganz so fit wie in C++...
In C++ ist es mittlerweile so, dass man Code in *.h Dateien unterbringen
kann. Ohne define.
In dem man Funktionen als static oder inline deklariert.
Kann C das mittlerweile auch?
Ja, aber das mit dem inline ist ein hochmodernes Feature. Das gibt es im C-Standard erst seit 26 Jahren, ist also noch ganz frisch. Ganz anders bei C++, wo das schon ein Jahr länger standardisiert ist.
Das "inline"-Schlüsselwort ist nur ein Vorschlag an den Compiler. Was
dieser damit macht, ist seine Sache und hängt u.a. vom Compilertyp, der
Version und der ausgewählten Optimierungsstufe ab. Wenn man das "inline"
weglässt, kann der Compiler einen Funktionsaufruf dennoch inlinen, wenn
er es für sinnvoll hält.
Das ist nach Standard auch gar nicht der Effekt von "inline", auch wenn die Intention natürlich ist, damit das inlining zu ermöglichen.
Der GCC tut dies bei kleinen Funktionen und eingeschalteter Optimierung
auch recht häufig, so dass die Verwendung von "inline" oft gar keinen
Unterschied macht.
Doch, es macht einen Unterschied. Mit "inline" wird die one-definition-Rule außer Kraft gesetzt. Es darf im Programm auch ohne static mehr als eine Definition der Funktion geben, solange die Definitionen alle gleich sind.
Es gibt übrigens bei gcc auch das Attribut gnu::always inline, mit dem man das Inlining tatsächlich forcieren kann (sofern möglich).
Der Präprozessor ist das eigentlich geniale
Ding in C, der macht die Sprache praxistauglich. Damit geht mehr als
mit Templates in C++ auf Kosten der Typsicherheit.
C++ hat recht viel Aufwand getrieben, um Makros weitgehend überflüssig zu machen. Heute gibt's nur wenige Fälle, in denen man die noch braucht.
Der GCC tut dies bei kleinen Funktionen und eingeschalteter Optimierung
auch recht häufig, so dass die Verwendung von "inline" oft gar keinen
Unterschied macht.
Doch, es macht einen Unterschied. Mit "inline" wird die
one-definition-Rule außer Kraft gesetzt.
Das ist IMHO aber nur in C++ so. Oder in C mittlerweile auch?
Sinnvoll ist dieses Feature eigentlich nur dann, wenn man die Funktion
auch in Übersetzungseinheiten (nicht ingelinet) aufrufen möchte, die die
Funktjonsdefinition nicht kennen. Sonst kann die One-Definition-Rule
auch ganz einfach mit einem "static" davor erfüllt werden, was ja
meistens auch gemacht wird.
Der GCC tut dies bei kleinen Funktionen und eingeschalteter Optimierung
auch recht häufig, so dass die Verwendung von "inline" oft gar keinen
Unterschied macht.
Doch, es macht einen Unterschied. Mit "inline" wird die
one-definition-Rule außer Kraft gesetzt.
Das ist IMHO aber nur in C++ so. Oder in C mittlerweile auch?
Nein, in C ist es etwas anders. Meinem Verständnis nach läuft das so: inline wirkt da im Bezug auf die Linkage eher so ähnlich wie static. Man muss sich aber von Hand darum kümmern, dass im Programm eine (und genau eine) nicht-inline-Version der Funktion existiert, für den Fall, dass der Compiler es doch nicht inlinen will.
Sinnvoll ist dieses Feature eigentlich nur dann, wenn man die Funktion
auch in Übersetzungseinheiten (nicht ingelinet) aufrufen möchte, die die
Funktjonsdefinition nicht kennen. Sonst kann die One-Definition-Rule
auch ganz einfach mit einem "static" davor erfüllt werden, was ja
meistens auch gemacht wird.
Das static hat auch den Vorteil, dass der Compiler nicht noch zusätzlich eine nicht-inline-Version der Funktion generieren muss, für den Fall, dass sie doch mal von außen aufgerufen wird. Ohne static muss der Compiler das ja möglich machen. Auf der anderen Seite: Wenn der Compiler nicht inlinet, hat jede Übersetzungseinheit nachher eine eigene Kopie der Funktion.
C++ hat recht viel Aufwand getrieben, um Makros weitgehend überflüssig
zu machen. Heute gibt's nur wenige Fälle, in denen man die noch braucht.
Das ist wohl so.
#include wird noch gebraucht
Das ist kein Makro. Aber seit C++20 gibt's ja auch Module im Standard, mit denen auch #include überflüssig wird. Die scheinen aber noch nicht viel in Verwendung zu sein.
und z.B. #ifdef um zwischen den verschiedenen µC/µP zu unterscheiden.
Davon könnte man wahrscheinlich auch einiges per "if constexpr" machen.
Das ist kein Makro. Aber seit C++20 gibt's ja auch Module im Standard,
mit denen auch #include überflüssig wird. Die scheinen aber noch nicht
viel in Verwendung zu sein.
Laut meines Wissens war das für C++20 vorgesehen, kam mit C++23 und ist noch nicht fertig. Die gesamte STL muss noch überarbeitet werden. Mit Modulen wird jedenfalls vieles ganz anders werden. :-)
Soweit mir bekannt ist die "Template engine" Turing vollständig.
Wobei die Frage ist, ob das ein Vorteil ist.
Immerhin war es noch nicht einmal den Schöpfern klar, dass das Monster, das sie da geschaffen hatten, Turing-vollständig ist. Das wurde erst später "entdeckt", und soweit ich weiß nicht mal von den Entwicklern selbst.
In dem man Funktionen als static oder inline deklariert.
Kann C das mittlerweile auch?
static gab es schon immer in C.
Inline ist seit C99 im Standard, wobei Compiler wie GCC inline schon viel früher als Spracherweiterung zB in GNU-C unterstützten.
In C99 gibt es sogar 2 Varianten: static inline und extern inline.
Und in GCC gibt es noch die 3. Variante gnu_inline, welche in GCC die Semantik von extern inline war bevor C99 mit einer anderen Semantik um die Ecke kam.
gnu_inline ist m.E. wesentlich nützlicher als extern inline, zum Beispiel implementiert die AVR-LibC strlen() folgendermaßen:
1
extern__ATTR_ALWAYS_INLINE____ATTR_GNU_INLINE__
2
size_tstrlen(constchar*__s)
3
{
4
if(__builtin_constant_p(__builtin_strlen(__s)))
5
{
6
return__builtin_strlen(__s);
7
}
8
else
9
{
10
registerconstchar*__r24__asm("24")=__s;
11
registersize_t__res__asm("24");
12
__asm("%~call strlen":"=r"(__res):"r"(__r24))
13
:"30","31","memory");
14
return__res;
15
}
16
}
Und nein, das ist kein rekursiver Aufruf :-)
Der Grund ist, dass Code wie
1
intfun1(constchar*s,intc)
2
{
3
returnc+strlen(s);
4
}
übersetzt wird zu
1
fun1:
2
/* #APP */
3
rcall strlen
4
/* #NOAPP */
5
add r24, r22
6
adc r25, r23
7
ret
anstatt zu
1
fun1:
2
push r28
3
push r29
4
movw r28, r22
5
rcall strlen
6
add r24, r28
7
adc r25, r29
8
pop r29
9
pop r28
10
ret
Und zur besseren Kontrolle von (partiellem) Inlining gibt es im GCC nicht nur noinline, gnu_inline und always_inline, sondern auch noch flatten, noclone und noipa.
Soweit mir bekannt ist die "Template engine" Turing vollständig.
Wobei die Frage ist, ob das ein Vorteil ist.
Hallo Johann,
warum das soll das ein Nachteil sein? Die Sprache C/C++ ist generell komplett Turing fähig. Egal ob mit oder ohne Template Programmierung. Turing-Vollständigkeit besagt nur, dass man alle Rechenaufgaben mit dieser Sprache lösen kann. Mit Template Programmierung verlagert man diese Berechnungen in die Compilezeit. Okay, damit dauert die Kompilierung länger. Dafür hat man zur Laufzeit Rechenzeit freigeschaufelt. Ist in meinen Augen ein Vorteil. Zusätzlich bleibt auch das kompilierte Programm im Vergleich kleiner.
Immerhin war es noch nicht einmal den Schöpfern klar, dass das Monster,
das sie da geschaffen hatten, Turing-vollständig ist. Das wurde erst
später "entdeckt", und soweit ich weiß nicht mal von den Entwicklern
selbst.
Diese Aussage hat auch so einen negativen Touch. Warum? Ist auch nicht weiter schlimm, wenn die Fähigkeit eines "Projekts" erst später erkannt werden. Man kann ja nicht alles immer gleich überblicken. Das zeigt mir jedoch eines. Das die Basis von C/C++ schon durchdacht begonnen wurde, sonst wäre das nicht möglich gewesen.
Ja, aber das ist doch ein Makro und eben keine Funktion.
Ich glaube, ich habe den Satz
"Die Makroargumente werden genau ein einziges Mal ausgewertet, wie
dies auch bei einem Funktionsaufruf der Fall ist."
missverstanden und der zweite Satztteil bedeutet einfach, dass der Compiler grundsätzlich zu Beginn einer Funktion das Argument einmal auswertet (normalerweise in ein Register schreibt) und es dann bei mehrfachen Lesezugriffen innerhalb der Funktion selbstverständlich diesen Wert behält (anders als eben bei einem funktionsähnlichen Makro, s.o. Dein Beispiel) und nicht, dass der Programmierer irgendwas beachten muss.
Mit Template Programmierung verlagert man diese Berechnungen in die
Compilezeit. Okay, damit dauert die Kompilierung länger. Dafür hat man
zur Laufzeit Rechenzeit freigeschaufelt. Ist in meinen Augen ein Vorteil.
Template-Metaprogramming ist aber prima für den ioccc geeignet. Inzwischen gibt's für eine etwas weniger hirnknotenverursachende Schreibweise auch consteval-Funktionen.
Das letzte und das Eingangsbeispiel zeigen 2 perfekte Möglichkeiten sich mit Macros ins eigene Knie zu schießen.
Beide Sorten von Macros kann man jahrelang einsetzen, ohne dass es Probleme gibt, aber ändern sich die Einsatzbedingungen, gehts ins Knie.
Bei der Fehlersuche ist man sich sicher, dass die Macros funktionieren, werden so oft spät in Frage gestellt.
Der Trend, diese wie Funktionen aussehen zu lassen, verschärft das Problem eher, als dass es Lösungen anbietet.
Sicherlich kann man beide Böcke mit geschickt formulierten Dingen vermeiden, aber es sind und bleiben einfache Textersetzungen, die keine Datentypen sehen, oder gar von einander unterscheiden können.
nicht, dass der Programmierer irgendwas beachten muss.
Ich hatte das eben so verstanden, dass der Programmierer genau darauf achten soll: --> Schreibe Makros immer so, dass Argumente nur genau einmal ausgewertet werden (und nicht so, wie in den Beispielen hier, die das Problem verdeutlichen).
Es ist meist möglich. Die meisten Programmierer von Makros lassen ihre Nutzer eher aus Nachlässigkeit in diese Falle tappen.
Und zum "meist möglich": Dazu braucht es idR nicht-Standard
Erweiterungen. Hier ein GCC Beispiel mit valued Blocks und typeof:
Anzumerken ist, dass typeof sein Argument nicht auswertet.
1
#define MAX(a, b) \
2
(__extension__({ \
3
__typeof__(a) _a = (a); \
4
__typeof__(b) _b = (b); \
5
_a > _b ? _a : _b; \
6
}))
Das vereinigt dann alle Nachteile, außer diesem einen. Sonst heißt es immer, "portabel ist wichtig". Warum macht man das? Ja, klar, weil man es kann, als sportliche Herausforderung oder um Kollegen zu ärgern.
Was war eigentlich zuerst da, fgetc() als Funktion oder getc() als Macro? War getc() jemals in irgendeiner Hinsicht besser? Geschwindigkeit kann es in dem Fall ja nicht gewesen sein und Bytes spart es auch nicht.
Und zum "meist möglich": Dazu braucht es idR nicht-Standard
Erweiterungen. Hier ein GCC Beispiel mit valued Blocks und typeof:
Anzumerken ist, dass typeof sein Argument nicht auswertet.
1
>>#defineMAX(a,b) \
2
>>(__extension__({ \
3
>>__typeof__(a)_a=(a); \
4
>>__typeof__(b)_b=(b); \
5
>>_a>_b?_a:_b; \
6
>>}))
Das vereinigt dann alle Nachteile, außer diesem einen. Sonst heißt es
immer, "portabel ist wichtig". Warum macht man das? Ja, klar, weil man
es kann, als sportliche Herausforderung oder um Kollegen zu ärgern.
Nein. Lies einfach mal den Beitrag, auf welchen der Code Teil der Anwort ist. Es ist ein konkretes Beispie dafür, dass es eben nicht so einfach ist, ein Makro funktionsähnlich zu machen. MAX ist ja nicht so unüblich.
Zum Code selbst: Portierbarkeit bekommt man mit #ifdef__GNUC__. Die verwendeten Spracherweiterunge sind durchaus sehr nützlich, etwa zur Verwendung in System-Headern. Und da geht es in erster Linie um Funktionalität und nicht um Schönheit.
etwa zur
Verwendung in System-Headern. Und da geht es in erster Linie um
Funktionalität und nicht um Schönheit.
Diese Sicht hat gewisse Ähnlichkeit mit der irgendwo oben benannten Unübersichtlichkeit der Templates in der Libstdc++.
Auch dort ist Schönheit nicht das oberste Ziel, eher Performance und Nutzbarkeit.
Naja, "Portierbarkeit", die dann darin besteht, daß das Konstrukt mit
anderen Compilern nicht funktioniert ...
Irgendeinen Tod musst du sterben.
Entweder anderen Compiler bietet ähnliche Feature, oder das Feature selbst ist schicht und einfach nicht portabel -- in dem Fall hätte man besser gleich richtig designed.
Bei System-Header spielt Portabilität wiederum keine Rolle, weil diese ja Teil der Toolchain sind und somit auf den Compiler passen. (Es sei denn mann ist Clang und behauptet man wäre kompatibel zu GCC, ist es dann aber nicht).
Es ist ein konkretes Beispie dafür, dass es eben nicht so
einfach ist, ein Makro funktionsähnlich zu machen. MAX ist ja nicht so
unüblich.
Ja. Es ist nicht unüblich. Aber genau deshalb vermeiden wir diese Art von "Template"-Makros, die mit beliebigen Typen funktionieren. Und sterben den Tod einer Armada von typsicheren max-Funktionen.