Hallo,
ich möchte meine Tasteneingabe und abfrage vereinfachen. Hierzu bietet
sich folgendes Makro an;
#define readPinHigh (P,bit) ((P&(1<<bit))
Das gleiche könnte aber auch mit einer Funktion realisiert werden;
char readPinHigh (char P, char bit)
{ P&(1<<bit);
_delay_ms(10); } // Tastenentprellung
(1) Welchen Vor/Nachteil hat das Makro gegenüber der Funktion aus meinem
Bsp?
(2) Wie können, in einem Makro, mehrere Befehle hintereinander
geschrieben werden? Bsp: _delay_ms(1); // dahinter
> (2) Wie können, in einem Makro, mehrere Befehle hintereinander> geschrieben werden? Bsp: _delay_ms(1); // dahinter
wenn man das unbedingt braucht, missbraucht man dazu eine do { ... }
while(0) - Schleife, da diese sich syntaktisch in jedem Kontext wie ein
(1) Statement verhält.
Der Vorteil einer Funktion gegenüber dem Makro: du hast die
Typsicherheit, der Compiler wird die ggf. eine Warnung ausgeben, wenn
die Parameter nicht stimmen.
Und du hast auch die Seiteneffekte vom Makro nicht.
Bespiel:
Eine funktion heisst push-pop & parameter auf stack.
Makro bedeutet der Code ist N-mal vorhanden.
Um einen Pin zu toggeln nimmt man natuerlich das Makro, weils eh nur ein
ASM statement ist.
i-Troll (c) schrieb:> Eine funktion heisst push-pop & parameter auf stack.
Gähn.
Function Inlining wurden vor ungefähr 50 Jahren erfunden.
Seit dieser Zeit ist das kein Argument mehr.
__Son´s Bersi__ schrieb:> (2) Wie können, in einem Makro, mehrere Befehle hintereinander> geschrieben werden? Bsp: _delay_ms(1); // dahinter
In einer Zeile:
#define readPinHigh(P,bit) ((P&(1<<bit)); _delay_ms(1);
In zwei Zeilen:
#define readPinHigh(P,bit) ((P&(1<<bit)); \
_delay_ms(1);
Macros sollte man am besten ganz vermeiden. Ein gute optimierender
Compiler macht heute das gleiche.
Insbesondere Anfänger werden auch nur damit verwirrt.
DANKE!
Bin gerade im Fachbuch über Makros gestolpert und habe das Thema so
interpretiert, dass diese als "Allheilmittel" eingestzt werden können.
Dem ist wohl nicht so.
An wann ist es sinnhafte, mit Makros zu arbeiten? (Bitte für einen
Anfänger verständlich formulieren)
__Son´s Bersi__ schrieb:> An wann ist es sinnhafte, mit Makros zu arbeiten? (Bitte für einen> Anfänger verständlich formulieren)
Wenn das Makro nicht zu kompliziert ist und keine 'Nebenwirkungen' zu
erwarten sind.
Makros benutzt man in C zb um konstanten Werten einen 'Namen' zu geben.
1
#define ANZAHL_ELEMENTE 5
2
3
intWerte[ANZAHL_ELEMENTE];
4
5
....
6
intmain()
7
{
8
...
9
for(i=0;i<ANZAHL_ELEMENTE;i++)
10
Werte[i]=i;
11
...
12
}
soll sich mein Array vergrößern, dann genügt es das MAkro zu ändern und
der Compiler passt mir den Rest des Programms daran an, weil er ja an
allen Stellen die Textersetzung von ANZAHL_ELEMENTE durch den jeweiligen
Zahlenwert macht.
Das ist nicht nur eine Komfort-Funktion sondern hat auch etwas mit
Programmsicherheit zu tun. Denn eines muss man auch mal akzeptieren: Wir
Menschen machen dumme Fehler, indem wir Dinge vergessen.
Makros kannst du auch benutzen, wenn es Sinn macht, den Programmtext zu
vereinfachen, bzw. wenn du durch Einführung von Makronamen den Code
semantisch besser verstehbar machen kannst.
Im Prinzip kannst du ja deine Abfrage schreiben als
if( PIND & ( 1 << PD0 ) )
das ist eine völlig legitime Schreibweise, um einen Pin abzufragen.
Aber: es ist auch etwas unbefriedigend. Denn aus dem Code geht so erst
mal nicht hervor, was die Absicht an dieser Stelle ist - der Code
erzählt mir nichts ausser das hier offenbar ein Pin abgefragt wird.
#define OK_KEY PD0
...
if( PIND & ( 1 << OK_KEY ) )
schon besser. Jetzt seh ich schon mal im Code, dass es offenbar etwas
mit der Taste zu tun hat, die am Gehäuse mit 'OK' beschriftet ist. Das
ist schon mal viel besser, weil es den Code in einen gewissen
Zusammenhang mit seiner Funktion rückt. Aber ich kann noch weiter gehen
#define KEY_PRESSED(x) (PIND & ( 1 << (x))
#define OK_KEY PD0
...
if( KEY_PRESSED( OK_KEY ) )
jetzt fängt die Sache an, interessant zu werden. Denn jetzt beginnt der
Code, obwohl es immer noch der gleiche Code ist wie der, mit dem wir
angefangen haben!, mir etwas zu erzählen! Wenn ich mir diese Codestelle
ansehen, dann kann ich im Code selber lesen, was der Zweck an dieser
Stelle ist.
Zurück zur Frage: Makro oder Funktion
Klar könnte man das ganze so auch mit Funktionen erreichen. Nur ist bei
diesem Makro KEY_PRESSED nicht mit Nebenwirkungen zu rechnen. Daher ist
(für mich) ein Makro an dieser Stelle ok. Aber wesentlich komplexer
sollte der Makroinhalt dann nicht mehr werden! Alles was komplizierter
ist, speziell wenn das x dann mehrmals im Makro vorkommt, ist in einer
Funktion besser aufgehoben. Immer drann denken: Makros sind einfach nur
Textersetzungen und der Teil des Compilers, der diese Ersetzung
vornimmt, der kümmert sich nicht großartig um Regeln - der ersetzt
einfach nur den Text! Und das kann manchmal überraschende Ergebnisse
liefern, wenn man nicht genau hinsieht bzw. das Makro schlampig
schreibt.
1
#define TWICE(x) 2*x
2
3
j=5;
4
i=TWICE(j);
5
6
// i hat hier den Wert 10.
7
// Soweit so gut. Aber
8
9
k=TWICE(j+2);
10
11
// k kriegt hier den Wert 12 und nicht (wie beabsichtigt) 14
12
// weil j ja 5 war und j + 2 dann 7 ergibt und das doppelte von
13
// 7 wäre ja 14
14
// tatsächlich passiert hier aber etwas ganz anderes.
15
//
16
// Textersetzung!
17
// aus TWICE( j + 2)
18
// wird 2*j + 2
19
// und mit einem Wert von 5 für j wird (Punkt vor Strichrechnung)
20
// daraus eben 12
Makros werden vom Präprozessor behandelt, der im Grunde nur ein
glorifizierter Texteditor ist, der den Text für den eigentlichen
Compiler aufbereitet. Von C versteht dieser Präprozessor nicht viel. Für
ihn ist dein Programm einfach nur ein Text, den er durch Befehle die
trickreicherweise selbst im Text enthalten sind, modifiziert. Nicht
mehr.
Der eigentliche Compiler kriegt Makros nicht mehr zu Gesicht, daher kann
der auch keine C-Analysen mehr damit machen. Wohingegen Funktionen
selbstverständlich Bestandteil des Sprachumfangds von C sind und daher
vom Compiler vollinhaltlich geprüft und durch die Analysen gejagt
werden.
Und das ist eigentlich der Hauptunterschied:
Makros sind einfach nur Textersetzungen. Was immer sich durch die
Textersetzung ergibt, das ergibt sich und interessiert den Präprozessor
nicht weiter.
Funktionen hingegen sind Bestandteil des Sprachkerns von C. Der Compiler
kümmert sich darum, dass da alles mit rechten Dingen zu geht.
Im Zweifelsfall lieber Funktionen bevborzugen. Es sei denn, du willst
etwas erreichen, was mit C-Mitteln nicht machbar ist.
__Son´s Bersi__ schrieb:> (1) Welchen Vor/Nachteil hat das Makro gegenüber der Funktion aus meinem> Bsp?
Das Makro hat hier überhaupt keinen Vorteil. Der einzige "Vorteil" von
Makros ist, dass sie keine bestimmten Datentypen verlangen. Beispiel:
1
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
Das funktioniert sowohl mit Integern aller Größen und Vorzeichen als
auch mit float/double.
Außerdem hat man syntaktisch mehr Möglichkeiten:
Das funktioniert mit jeder Integer-Variable. Mit einer Funktion könnte
man das nur für einen Variablentyp realisieren, auf den man einen Zeiger
übergeben muss. Den Wert einer "by value" übergebenen Variable kann man
von einer Funktion aus ja nicht ändern.
Die Nachteile von Makros wurden schon genannt: Wenn darin die Operanden
mehrfach vorkommen (wie im ersten Beispiel), kann das Nebeneffekte
haben. Beispielsweise bei Aufrufen wie:
1
MAX(funktion1(),funktion2());
Hier wird entweder funktion1() oder funktion2() zwei Mal aufgerufen,
ohne dass es offensichtlich ist. Oder das klassische Beispiel von oben
mit zahl++, das zu undefiniertem Verhalten führt. Das hat man mit
Funktionen nicht.
In Deinem Fall muss Du aber weder Werte im Makro ändern, noch brauchst
Du verschiedene Datentypen, also nimm eine Funktion. Damit sie geinlint
wird und es keine Namenskonflikte mit anderen Dateien gibt, solltest Du
sie static definieren. Außerdem gibt es das Schlüsselwort "inline", mit
dem Du Funktionen kennzeichnen kannst, die geinlint werden sollen.
Und nicht vergessen: Wenn eine Funktion einen Wert zurückgeben soll,
dann muss sie das mit return machen:
Noch eine Nebenbemerkung: Funktionen sollten immer genau eine Sache
machen, und zwar die, die ihr Name suggeriert. Deine Funktion liest aber
nicht nur den Pin, sondern sie vertrödelt auch noch 10 ms. Besser wäre
also etwas wie:
1
#define BUTTON_A PA0
2
#define BUTTON_B PA1
3
#define BUTTON_INPUT_REG PINA
4
5
staticinlineboolbuttonPressed(uint8_tbutton)
6
{
7
boolpressed=BUTTON_INPUT_REG&(1<<button);
8
_delay_ms(10);
9
returnpressed;
10
}
11
12
// Aufruf
13
if(buttonPressed(BUTTON_A)){
14
// mach etwas
15
}
Hier macht die Funktion genau das, was sie verspricht: Sie gibt zurück,
ob der Taster gedrückt wurde oder nicht. Wie sie das erreicht, ist ihre
Sache. Wenn einem später mal eine bessere Methode einfällt, wie man
Buttons entprellt, muss man nur die Funktion ändern und nicht den Code,
der sie aufruft.
__Son´s Bersi__ schrieb:> Bin gerade im Fachbuch über Makros gestolpert und habe das Thema so>> interpretiert, dass diese als "Allheilmittel" eingestzt werden können.
Hat eine ehemaliger Kollege von mir als Allheilmittel verwendet und
damit wunderbaren Spaghetti-Code erzeugt.
Der hat es so exzessiv betrieben, dass man - um überhaupt noch eine Idee
zu bekommen, was das Programm macht - das Ganze erst mal durch den
Makro-Präprozessor jagen und sich hinterher die Ausgabe anschauen
musste.
Fabian O. schrieb:> Der einzige "Vorteil" von> Makros ist, dass sie keine bestimmten Datentypen verlangen.
Das ist kein Vorteil mehr, weil ...
Fabian O. schrieb:> Mit einer Funktion könnte> man das nur für einen Variablentyp realisieren, auf den man einen Zeiger> übergeben muss.
... es doch geht. Dafür gibt es C++-templates, welche man auf eine
Funktion anwendet.
Ja, ich weiss, im Thread-Titel steht "C" :-)
__Son´s Bersi__ schrieb:> Bin gerade im Fachbuch über Makros gestolpert und habe das Thema so> interpretiert, dass diese als "Allheilmittel" eingestzt werden können.
Wie alt ist das Buch :-)
Weiter oben wurde "static inline" erwähnt, für verschiedene Datentypen
gibt es C++-templates. Auch die Multiline-Problematik und
Stringification ist in der Template-Variante m. E. nach besser gelöst.
Als ich mit µC-Programmierung noch nich so weit war, hab ich in einem
Programm auch mal Makros gesehen. Da ich in der Assembler-Programmierung
vorher noch nichts von Makros gehört habe (meine Einzige
Programmiererfahrung war PHP), hab ich damit ein bisschen gespielt. Ich
habe damit ansich nur Zeitverzögerungen gemacht. wait_10us (inhalt: 10
nop's), wait_120us (inhalt: 12 wait_10us "aufrufe") und wait_200us
(inhalt: 20 wait_10us "aufrufe"). Ich dachte halt, dass das quasi wie
Funktionen funktioniert. Ergebnis war dann, dass ich brennen wollte, er
aber meinte, das Programm passt nicht in den µC ;)
Dann hab ich mir mal angeguckt, was ein Makro wirklich macht und dann
wurde mir klar, dass mein ganzer Code übersät von nop's war ;)
Roland H. schrieb:> ... es doch geht. Dafür gibt es C++-templates, welche man auf eine> Funktion anwendet.
In C gibt es das ja meines wissens nicht. Aber gabs in C nicht die
Möglichkeit Funktionen zu überlagern? Oder kam das auch erst bei C++?
PittyJ schrieb:> Macros sollte man am besten ganz vermeiden. Ein gute optimierender> Compiler macht heute das gleiche.
12. Use macros instead of functions for tasks that generates less than
2-3 lines assembly code.
Quelle: ATMEL AVR035
Bartl schrieb:> Use macros instead of functions..
Die Quelle war Atmel, gelle?
Ich schließe mich da dem Vor-Vorredner an: Macros braucht man selten bis
überhaupt nicht und sollte sie daher besser vermeiden, da sie eine
Fehlerquelle ersten Ranges sind.
Und unser TO war eher auf Tastenabfragen und deren Entprellung aus:
__Son´s Bersi__ schrieb:> // Tastenentprellung
Tja, da ist ein Macro eigentlich genau so untauglich wie eine
gleichartige Funktion, denn das saubere Erfassen von Tastendrücken,
deren Entprellung und sowas wie Repetieren sind Aufgaben, die ein
zeitliches Regime erfordern, also zumeist in einer von einem Systemtick
getriebenen Interrupt-Routine abgehandelt werden müssen. Da ist etwas
mehr Nachdenken angesagt als sich bloß mit der Frage nach Macro oder
Funktion zu befassen.
W.S.
W.S. schrieb:> Die Quelle war Atmel, gelle?
Ja und recht haben sie auch. Für die meisten 2-3 Assemblerzeilen
Funktionen wird das Flash sparen, der auf einem kleinem µC kostbar sein
kann.
> Ich schließe mich da dem Vor-Vorredner an: Macros braucht man selten bis> überhaupt nicht und sollte sie daher besser vermeiden, da sie eine> Fehlerquelle ersten Ranges sind.
ersten Ranges? Dann solltest du nochmal programmieren lernen. Ich kann
mich nicht mehr an meinen letzten Makrofehler erinnern
Ich finde, dass Makros besonders auf kleinen µC aus Flash- und
Effizienz-Gründen (Bspw. Loopunrolling ist durch Makros einfach möglich)
durchaus eine Berechtigung haben.
PS: Dein Inlining funktioniert nur solange die Funktionen in der selben
Datei sind. Sobald externe Dateien hinzukommen zerstört avrgcc die
mögliche Performance durch sein striktes ABI.
Fallobst schrieb:> PS: Dein Inlining funktioniert nur solange die Funktionen in der selben> Datei sind. Sobald externe Dateien hinzukommen zerstört avrgcc die> mögliche Performance durch sein striktes ABI.
Makros funktionieren auch nur, wenn sie in der gleichen Datei sind.
Wenn man Inlines zusätzlich als "static" definiert, dann werden sie nur
als Funktionen erzeugt, wenn sie auch so benötigt werden.
A. K. schrieb:> Wenn man Inlines zusätzlich als "static" definiert, dann werden sie nur> als Funktionen erzeugt, wenn sie auch so benötigt werden.
Schade nur, dass externe inlines nicht funktionieren.
Damit wäre die externe Inline-Funktion wieder eine normale. Ein Beispiel
zu dem dann entstehenden ABI-Overhead von avrgcc:
Für eine min-Funktion, die eigentlich keine zusätzlichen Register
benötigt, sichert avrgcc im worst-case trotzdem ca. 16 Register, da er
die Funktion nicht kennt. Ergibt 64 verbrauchte Cpu-Zyklen + 64 Byte
unnützer Flashverbrauch pro worst-case Situation!
Für solche kleinen Funktionen sind und bleiben Markos in einer
Headerdatei einfach sinnvoller (wie Atmel es auch vorschlägt).
us73 schrieb:> __Son´s Bersi__ schrieb:>> (2) Wie können, in einem Makro, mehrere Befehle hintereinander>> geschrieben werden? Bsp: _delay_ms(1); // dahinter>> In einer Zeile:> #define readPinHigh(P,bit) ((P&(1<<bit)); _delay_ms(1);
Nur um das nochmal nachzuhalten: Das ist vermutlich genau das
Fehlerträchtige Makro-Programmieren was hier von einigen angeprangert
wird.
Diese Makrodefinition hat mindestens drei Probleme:
1) Das Semikolon am Ende sorgt dafür, dass seine Verwendung die übliche
Codestruktur durchbricht (und ggf. syntax-features des Editors
durcheinanderbringt):
1
foo();
2
readPinHigh(P,bit)
3
bar;
2) Mehrere Befehle hintereinander ohne eine Klammerstruktur gehen bei
z.B. if()-Statements ohne eigene Klammern kaputt:
1
if(foo)
2
readPinHigh(P,bit)
dabei wird das _delay_ms(1) ausgeführt, unabängig vom Wahrheitswert von
foo.
3) Die Verwendung der Argumente kann schieflaufen, da hier eine pure
Textersetzung stattfindet:
1
readPinHigh(P,foo?3:6)
wird ersetzt zu ((P&(1<<foo ? 3 : 6)); _delay_ms(1);
und aufgrund der Operator-Präzedenz wird das als (1 << foo) ? 3 : 6
ausgewertet, was eigentlich immer zu 3 evaluieren sollte.
Korrekt wäre das Makro etwa so:
1
#define readPinHigh(P,bit) do { (((P)&(1<<(bit))); _delay_ms(1); } while (0)
Fallobst schrieb:> Schade nur, dass externe inlines nicht funktionieren.
Wie gut das aber externe Makros funktionieren!
Dieser Vergleich zwischen Makros mit bekanntem Code und Funktionen mit
unbekanntem Code ist doch völliger Unfug.
Wenn der Compiler den Code einer Funktion nicht kennt, dann kann er sie
nicht inlinen. Wenn der Compiler den Code eines Makros nicht kennt, dann
auch nicht. Mit static verzierte Inline-Funktionen lassen sich ebensogut
in Includes unterbringen wie Makros.
Klar, external Inlines sind keine Alternative zu Makros. Aber wer zwingt
dich denn eigentlich, die Inlines unbedingt external zu machen?