Compileroptionen

OP #7913227
Lesenswert?

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.

1
    if (bl & 0x01) { charlieA_set(); } else { charlieA_clr(); }

Gibt es einen Compilerschalter mit dem das so funktioniert wie im ersten Beispiel (augenscheinlich verwende ich für's Compileren die gleichen Standartoptionen) ?

Moderator Persönliche Seite #7913318
Lesenswert?

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!

OP #7913415
Lesenswert?

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'
3
   54 |     if ((c % 13) == 0) led_set(); else led_clr();
4
      |                                   ^~~~

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)

#7913422
Lesenswert?

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).

#7913455
Lesenswert?

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

#7913519
Lesenswert?

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.

(Firma: Gast) #7913535
Lesenswert?

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:

  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.

#7913553
Lesenswert?

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.

Gast #7913690
Lesenswert?

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.

Moderator #7913713
Lesenswert?

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.

#7913733
Lesenswert?

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.

(Firma: Gast) #7913736
Lesenswert?

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.

Moderator #7913756
Lesenswert?

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.

#7913768
Lesenswert?

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.

(Firma: Gast) #7913770
Lesenswert?

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.

#7913774
Lesenswert?

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?

Moderator #7913802
Lesenswert?

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.

#7913869
Lesenswert?

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.

#7913881
Lesenswert?

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.

Moderator #7913889
Lesenswert?

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.

#7913901
Lesenswert?

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.

Persönliche Seite #7913940
Lesenswert?

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.

OP #7914940
Lesenswert?

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'
3
   54 |     if ((c % 13) == 0) led_set(); else led_clr();
4
      |                                   ^~~~

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

(Firma: Gast) #7914960
Lesenswert?

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?

Gast #7914964
Lesenswert?

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.

#7914969
Lesenswert?

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.

Gast #7914971
Lesenswert?

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).

#7914975
Lesenswert?

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

(Firma: Gast) #7914979
Lesenswert?

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.

Moderator #7914980
Lesenswert?

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

1
#define funDigitalWrite( pin, value ) { GpioOf( pin )->BSHR = 1<<((!(value))*16 + ((pin) & 0xf)); }

Also besser so:

1
#define funDigitalWrite( pin, value ) GpioOf( pin )->BSHR = 1<<((!(value))*16 + ((pin) & 0xf))

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.

1
#define (funDigitalWrite( pin, value ) GpioOf( pin )->BSHR = 1<<((!(value))*16 + ((pin) & 0xf)))

Den do-while(0)-Trick oder ({...}) braucht man hier nicht, da das Makro nur eine einzelne Anweisung enthält:

#7914984
Lesenswert?

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.

Moderator #7914986
Lesenswert?

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 :)

Gast #7915002
Lesenswert?

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 :-)

#7915003
Lesenswert?

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.

#7915016
Lesenswert?

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
static const uint16_t PINK   = 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
static const uint16_t PINK   = 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.

Beitrag "Arduino "static const uint16_t PINK= 0xF81F" failed"

(Firma: Gast) #7915025
Lesenswert?

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

(Firma: Gast) #7915054
Lesenswert?

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

#7915116
Lesenswert?

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.

(Firma: Gast) #7915131
Lesenswert?

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

Moderator #7915187
Lesenswert?

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.

#7915216
Lesenswert?

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.

#7915269
Lesenswert?

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. :-)

Persönliche Seite #7915332
Lesenswert?

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_t strlen (const char *__s)
3
{
4
  if (__builtin_constant_p (__builtin_strlen (__s)))
5
    {
6
      return __builtin_strlen (__s);
7
    }
8
  else
9
    {
10
      register const char *__r24 __asm("24") = __s;
11
      register size_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
int fun1 (const char *s, int c)
2
{
3
    return c + 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.

#7916331
Lesenswert?

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.

#7916360
Lesenswert?

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
void Test(int x);
4

5
int main()
6
{ 
7
  int x = 3;
8
  Test(x++);
9
  return(0);
10
}
11

12
void Test(int x)
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).

Gast #7916372
Lesenswert?

Gibt es ein konkretes Beispiel? Gibt es zu dem Thema allgemein was im Internet?

Ich war mal wieder zu langsam, aber egal:

1
#include <stdio.h>
2

3
#define Min(a,b) ((a) < (b) ? (a) : (b))
4

5
int main()
6
{ 
7
    int x = 3;
8
    printf("x: %d\n", Min(x++, 4));
9
    return(0);
10
}
#7916387
Lesenswert?

Arduino F. schrieb:

1
> 
2
> #include <stdio.h>
3
> 
4
> #define Test(x) \
5
>   printf("x: %d\n", x); \
6
>   printf("x: %d\n", x); \
7
>   printf("x: %d\n", x); \
8
>   printf("x: %d\n", x); \
9
>   printf("x: %d\n", x); \
10
>   printf("x: %d\n", x);
11
> 
12
> int main()
13
> {
14
>   int x = 3;
15
>   Test(x++);
16
>   return(0);
17
> }
18
> 
19
>

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.

#7916586
Lesenswert?

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.

(Firma: Gast) #7916888
Lesenswert?

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.

#7917069
Lesenswert?

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.

Persönliche Seite #7917137
Lesenswert?

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.

1
#define MAX(a, b)                               \
2
    (__extension__({                            \
3
            __typeof__(a) _a = (a);             \
4
            __typeof__(b) _b = (b);             \
5
            _a > _b ? _a : _b;                  \
6
    }))
7

8
int use_max (int a, int b)
9
{
10
    int c = MAX (a++, b++);
11
    return a + b + c;
12
}
13

14
int use_use_max (void)
15
{
16
    return use_max (1, 2); // return 7
17
}
#7917167
Lesenswert?

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.

Persönliche Seite #7917222
Lesenswert?

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
>> #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.

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.

Persönliche Seite #7917244
Lesenswert?

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).

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren