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) ?
Ralph S. schrieb:> Unter riscv-none-elf-gcc (Version 14.2.0) funktioniert das nicht
Was heißt "funktioniert nicht"?
Bekommst Du eine Fehlermeldung, oder macht der compilierte Code etwas
anderes als Du erwartest?
Am gezeigten Code-Fragment ist micht falsch, und auch nichts, was eine
bestimmte Compiler-Version oder -Option erfordert.
Zeige mehr vom Quelltext und die Fehlermeldung.
Ralph S. schrieb:> Gibt es einen Compilerschalter mit dem das so> funktioniert wie im ersten Beispiel
Du buddelst auf der falschen Baustelle.
> Ist charlieA_set ein Makro?
Eigentlich sollte man Makros mit Großbuchstaben benennen, damit sowas
nicht passiert, bzw. auffällig ist.
Arduino F. schrieb:> 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!
Frank M. schrieb:> Meine Vermutung ist auch, dass es sich um ein schlecht gekapseltes Makro> handelt,
Das würde allerdings, auch bei einem anderen C/C++ Compiler, genau so
ins Essen brechen.
Oder?
Mir fällt es schwer, da die Ursache zu sehen.
Arduino F. schrieb:> Das würde allerdings, auch bei einem anderen C/C++ Compiler, genau so> ins Essen brechen.
Wenn das Macro für eine andere Plattform (arm-none vs. riscv-none)
anders aussieht, dann nicht unbedingt.
Nun ja....
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.
Nemopuk schrieb:> Zeige mehr vom Quelltext und die Fehlermeldung.
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.
1
#define led_init() PC0_output_init()
2
#define led_set() PC0_set()
3
#define led_clr() PC0_clr()
4
5
void dummyout(int val)
6
{
7
}
8
9
int main()
10
{
11
volatile int a,c;
12
SystemInit();
13
led_init();
14
c= 0;
15
while(1)
16
{
17
if ((c % 9) == 0) dummyout(4); else dummyout(9);
18
if ((c % 13) == 0) led_set(); else led_clr();
19
dummyout(a);
20
}
21
}
Harald K. schrieb:> Bekommst Du eine Fehlermeldung, oder macht der compilierte Code etwas> anderes als Du erwartest?
Die "dummyout" Anweisungen werden klaglos übersetzt, die "led_set"
Anweisungen produzieren diesen Fehler:
1
scratch.c: In function 'main':
2
scratch.c:54:35: error: 'else' without a previous 'if'
Frank M. schrieb:> 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).
Frank M. schrieb:> 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.
Ralph S. schrieb:> #define led_init() PC0_output_init()> #define led_set() PC0_set()> #define led_clr() PC0_clr()
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).
Ralph S. schrieb:> 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.
Oliver
Oliver S. schrieb:> 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.
Man sollte schon wissen, was man tut.
Nemopuk schrieb:> 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:> Denn gerade dafür sind Makros ja gemacht.
Für Funktionen sind Funktionen gemacht worden.
Axel S. schrieb:> Und ältere Compiler können es u.U. gar nicht.
Das "Problem" haben wir seit mehr als 15 Jahren nicht mehr. Halte dich
doch nicht unnötig mit Einschränkungen auf, die schon lange überwunden
sind!
Axel S. schrieb: 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:
1. Kleinschreibung des Makro Bezeichners
2. 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.
Ralph S. schrieb:> 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.
Ralph S. schrieb:> die gleichen> Standartoptionen
Nochmal in die Ecke stell, es gibt keine Art des Stehens.
Peter D. schrieb:> Stell Dich also sofort in die Ecke.
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.
Arduino F. schrieb:> Die Verstöße:> 1. Kleinschreibung des Makro Bezeichners> 2. 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:
1. Es hat wie eine gewöhnliche Funktion eine in Klammern eingeschlossene
Argumentliste. Hat das Makro keine Argumente, bleibt der Raum
zwischen den Klammern leer.
2. Der Makroaufruf kann überall dort stehen, wo auch ein Funktionsaufruf
stehen kann, also insbesondere auch innerhalb einer if-Anweisung.
3. 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.
Arduino F. schrieb:> 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.
Michael schrieb:> 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.
Michael schrieb:> 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.
Arduino F. schrieb:>> 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!
Das habe ich auch nicht behauptet. Ich weiß, dass du zwar ein Fan von
Arduino bist, aber nicht zu dessen Entwicklergruppe gehörst.
Arduino F. schrieb:> 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.
Peter D. schrieb:> Ralph S. schrieb:>> die gleichen>> Standartoptionen>> 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?
Harald K. schrieb:> 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.
Yalu X. schrieb:> 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.
Harald K. schrieb:> 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.
Nemopuk schrieb:> 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.
Harald K. schrieb:> 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.
Yalu X. schrieb:> 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.
Yalu X. schrieb:> 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.
Nemopuk schrieb:> 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.
Oder kannst du ein Beispiel nennen?
Yalu X. schrieb:> 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.
Harald K. schrieb:> 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.
Harald K. schrieb:> 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'
auch bei Funktionsaufrufen auswirft (was der Compiler natürlich nicht
getan hat), aber wie gesagt hätte ich geschworen (Meineid ?) dass dem so
ist.
Peter D. schrieb:> Ja, beides stimmt. Stell Dich also sofort in die Ecke.
ich sehe das jetzt einmal sportlich und mit einem Augenzwinkern: Ich
stand schon in der Ecke :-)
Peter D. schrieb:> Ralph S. schrieb:>> die gleichen>> Standartoptionen>> 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'");
4
}
Arduino F. schrieb:> Merke:> Selbst die schlauesten Leute können dumme Ideen haben.
Ich gehöre zwar nicht zu den schlauesten Leuten, aber dumme Ideen habe
ich trotzdem.
Yalu X. schrieb:> 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),
Ralph
Ralph S. schrieb:> 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?
Ralph S. schrieb:> 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.
Arduino F. schrieb:> 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).
Oliver S. schrieb:> Udo K. schrieb:>> Es muss erst mal funktionieren.>> 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.
> Damit geht mehr als mit Templates in C++
Aua!
> auf Kosten der Typsicherheit.
Wen Typsicherheit nicht interessiert....
Soweit mir bekannt ist die "Template engine" Turing vollständig.
Der Präprozessor nicht.
Udo K. schrieb:> Oliver S. schrieb:>> Udo K. schrieb:>>> Es muss erst mal funktionieren.>>>> 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.
Oliver
> Template und Inline Mist
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.
Ralph S. schrieb:> 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.
Arduino F. schrieb:> 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.
Axel R. schrieb:> 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.
Udo K. schrieb:> 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.Arduino F. schrieb:>> Damit geht mehr als mit Templates in C++> Aua!
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 :)
Yalu X. schrieb:> 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 runden Klammern sind am falschen Ort.
Arduino F. schrieb:> 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.
Arduino F. schrieb:> 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 :-)
Axel R. schrieb:> 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.
Peter D. schrieb:> 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.
Harald K. schrieb:> In C++ aber kann auch die Implementierung einer Memberfunktion (weder> static noch inline) einer Klasse in einer Headerdatei untergebracht> werden.
Eine solche Methode ist implizit inline.
> inline zu nutzen ist halt ein Fall von Optimierungsbemühungen;
Dass inline wirklich inlined Code erzeugt ist eher eine historische
Annahme.
Hat mit der heutigen (C++)Bedeutung von inline wenig zu tun.
https://en.cppreference.com/w/cpp/language/inline.html
Axel R. schrieb:> 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".
Peter D. schrieb:> 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
Arduino F. schrieb:> 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.
Yalu X. schrieb:> 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).
Udo K. schrieb:> 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.
Rolf M. schrieb:> 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 und z.B. #ifdef um zwischen den
verschiedenen µC/µP zu unterscheiden.
Aber das wars dann wohl auch schon so ziemlich.
Nachtrag: #pragma once
Rolf M. schrieb:>> 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.
Yalu X. schrieb:> Das ist IMHO aber nur in C++ so.
Das ist auch mein bisheriger Stand.
Daher auch meine vorherige Frage.
Worauf dann eine der Antworten war:
Harald K. schrieb:> Das kann C praktisch schon immer.
Und das stimmt eben nicht!
Gut, vielleicht war meine Frage auch nicht exakt genug gestellt.
Yalu X. schrieb:> Rolf M. schrieb:>>> 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.
Arduino F. schrieb:> Rolf M. schrieb:>> 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.
Rolf M. schrieb:> 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. :-)
Arduino F. schrieb:> 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.
Arduino F. schrieb:> 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.
Kann mir mal jemand diesen Satz erklären:
"Die Makroargumente werden genau ein einziges Mal ausgewertet, wie
dies auch bei einem Funktionsaufruf der Fall ist."
Warum soll/darf in einem C-Funktionsaufruf ein Argument nur einmal
ausgewertet werden?
Jörg G. schrieb:> Warum soll/darf in einem C-Funktionsaufruf ein Argument nur einmal> ausgewertet werden?
weil x++ sonst mehrfach ausgeführt wird.
Wenn x vorher 1 ist, ist es nachher überraschender-Weise 3 oder mehr.
Das gleiche Problem, wenn das Argument einen Funktionsaufruf beinhaltet.
Johann L. schrieb:> Arduino F. schrieb:>> 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.
Udo K. schrieb:> Denk mal an f(i++) oder an f(ResetAfter2ndCall())...
Das zweite ist mir ersichtlich, aber das erste versteh ich nicht.
Mit
1
#include<stdio.h>
2
3
voidTest(intx);
4
5
intmain()
6
{
7
intx=3;
8
Test(x++);
9
return(0);
10
}
11
12
voidTest(intx)
13
{
14
printf("x: %d\n",x);
15
printf("x: %d\n",x);
16
return;
17
}
wird erwartungsgemäß
x: 3
x: 3
ausgegeben
Gibt es ein konkretes Beispiel? Gibt es zu dem Thema allgemein was im
Internet?
OT: Kann man im Forum eigentlich einen Zeilenumbruch erzwingen?
Bei mir werden die normal eingetippten ignoriert (so wie dieser gerade).
Jörg G. schrieb:> OT: Kann man im Forum eigentlich einen Zeilenumbruch erzwingen?> Bei mir werden die normal eingetippten ignoriert (so wie dieser gerade).
Mobilbrowser?
Im richtigen gehts.
Harald K. schrieb:> Jörg G. schrieb:>> OT: Kann man im Forum eigentlich einen Zeilenumbruch erzwingen?>> Bei mir werden die normal eingetippten ignoriert (so wie dieser gerade).>> Mobilbrowser?>> Im richtigen gehts.
Nein, Firefox unter Windows!
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.
Veit D. schrieb:> 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.
Harald K. schrieb:> Jörg G. schrieb:>> OT: Kann man im Forum eigentlich einen Zeilenumbruch erzwingen?>> Bei mir werden die normal eingetippten ignoriert (so wie dieser gerade).>> Mobilbrowser?>> Im richtigen gehts.
Es geht, allerdings zeigt die Vorschau es falsch an.
Jörg G. schrieb:> Ich glaube, ich habe den Satz> ...> missverstanden
Das denke ich auch.
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.
Jörg G. schrieb:> 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.
Bruno V. schrieb:> Es ist meist möglich. Die meisten Programmierer von Makros lassen ihre> Nutzer eher aus Nachlässigkeit in diese Falle tappen.
Wobei der Nutzer auch die Dokumentation lesen könnte.
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.
Johann L. schrieb:> 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.
Bauform B. schrieb:> Johann L. schrieb:>> 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.
Johann L. schrieb:> 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.
Johann L. schrieb:> Portierbarkeit bekommt man mit #ifdef__GNUC__.
Naja, "Portierbarkeit", die dann darin besteht, daß das Konstrukt mit
anderen Compilern nicht funktioniert ...
Harald K. schrieb:> Johann L. schrieb:>> Portierbarkeit bekommt man mit #ifdef__GNUC__.>> 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).
Johann L. schrieb:> 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.
Jörg G. schrieb:> OT: Kann man im Forum eigentlich einen Zeilenumbruch erzwingen?> Bei mir werden die normal eingetippten ignoriert (so wie dieser gerade).
Zwei Leerzeichen ans Zeilenende, dann Enter.