Forum: Mikrocontroller und Digitale Elektronik "C" Makro vs. Funktion


von __Son´s B. (bersison)


Lesenswert?

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

von Meier (Gast)


Lesenswert?

Lies deine beiden Fragen nochmal durch. Die Antwort steckt da drin.

von der mechatroniker (Gast)


Lesenswert?

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

von Hans Ulli K. (Gast)


Lesenswert?

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:
1
#define SQR (x) ((x) * (x))
2
3
int main (void)
4
{
5
        int zahl = 10;
6
7
        SQR(zahl++);
8
9
        printf("%d\n", zahl);
10
}
Rate mal welche Ausgabe du hast ...

von i-Troll (c) (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von us73 (Gast)


Lesenswert?

__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);

von PittyJ (Gast)


Lesenswert?

Macros sollte man am besten ganz vermeiden. Ein gute optimierender 
Compiler macht heute das gleiche.
Insbesondere Anfänger werden auch nur damit verwirrt.

von __Son´s B. (bersison)


Lesenswert?

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)

von Karl H. (kbuchegg)


Lesenswert?

__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
int Werte[ ANZAHL_ELEMENTE ];
4
5
....
6
int main()
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.

von Fabian O. (xfr)


Lesenswert?

__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:
1
#define SET_BIT(var, bit) ((var) |= (1ULL << (bit))
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:
1
static inline uint8_t readPinHigh(uint8_t P, uint8_t bit)
2
{
3
  uint8_t result = P & (1 << bit);
4
  _delay_ms(10);
5
  return result;
6
}

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
static inline bool buttonPressed(uint8_t button)
6
{
7
  bool pressed = BUTTON_INPUT_REG & (1 << button);
8
  _delay_ms(10);
9
  return pressed;
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.

von Gregor B. (Gast)


Lesenswert?

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

von Roland H. (batchman)


Lesenswert?

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.

von Michael S. (rbs_phoenix)


Lesenswert?

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++?

von Bartl (Gast)


Lesenswert?

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

von W.S. (Gast)


Lesenswert?

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.

von Fallobst (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Fallobst (Gast)


Lesenswert?

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

von Simon B. (nomis)


Lesenswert?

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)

Viele Grüße,
        Simon

von (prx) A. K. (prx)


Lesenswert?

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?

von us73 (Gast)


Lesenswert?

Simon Budig schrieb:
> Korrekt wäre das Makro etwa so:
> #define readPinHigh(P,bit) do { (((P)&(1<<(bit))); _delay_ms(1); } while (0)


DANKE !!!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.