Forum: Compiler & IDEs Markro für speziellen Funktioncall


von clonephone82 (Gast)


Lesenswert?

Hallo,

Wir haben sehr viele stellen in unserem Code wo immer wieder solch ein 
Konstrukt verwendet wird um eine Funktion aufzurufen.
1
if (err == ERR_OK)
2
{
3
   err = foo();
4
}

Ich wollte das jetzt mit einem Makro leserlicher machen.
1
#define CALL(func) \
2
  if (err == ERR_OK){ \
3
    err = func; \
4
  } \

Dann kann ich ja so den Aufruf machen.
1
CALL(foo());

Mein Kollege mag Makros nicht! Und findet diese gefährlich wie auch 
immer. Seht ihr hier irgendwelche Nachteile?

Funktional ist das doch das selbe - Auch die Codegröße ist die gleiche!

Vielen Dank.

von morgul (Gast)


Lesenswert?

Außer dass ich mir einen anderen Namen als CALL aussuchen würde sollte 
das kein Problem sein. Ja, mit Makros kann man ziemlichen Mist bauen, 
muss man aber nicht...

von Oliver (Gast)


Lesenswert?

Mach eine richtige Funktion daraus, kein Makro, und überlassl dem 
Compiler mögliches inlining.

Oliver

von Klaus W. (mfgkw)


Lesenswert?

Zumindest würde ich auch den Namen deutlich aussagekräftiger machen.
Zudem arbeitet das Makro mit zwei Dingen:
- der Funktion
- der Fehlervariablen

Erstens wäre es flexibler, zweitens wesentlich prägnanter für den Leser, 
wenn man dies beides als Parameter übergibt.

Weiterhin sieht ein Makroaufruf aus wie eine ausführbare Anweisung.
Damit sie das auch weiterhin ist (ggf. mit überflüssigen Semikolon, 
später mal einem if davor und einem else dahinter oder anderen 
Spielereien), würde ich noch die do-while-Konstruktion rumbauen.

Deshalb würde ich vorschlagen:
1
#define CALL(function,error)      \
2
  do                              \
3
  {                               \
4
      if (error == ERR_OK)        \
5
      {                           \
6
          error = function();     \
7
      }                           \
8
  } while( 0 )

Konsequenterweise könnte man ERR_OK vielleicht auch noch als 
Makroparameter durchschieben, hängt davon ab...

Der gleichwertige Aufruf wäre dann:
1
CALL_FUN_IF_ERR_OK( foo, err );

Evtl. könnte man das alles aber auch weglassen.
Wenn es nämlich nur darum geht, mehrere Funktionsaufrufe zu machen, ohne 
mehrfach auf Fehler reagieren zu müssen, könnte man auch ganz ohne 
Makros so vorgehen:
1
    if( (err = f1()) != ERR_OK
2
        &&
3
        (err = f2()) != ERR_OK
4
        &&
5
        (err = f3()) != ERR_OK
6
        &&
7
        (err = f4()) != ERR_OK
8
        ...
9
      )
10
     ...

von Rolf Magnus (Gast)


Lesenswert?

Oliver schrieb:
> Mach eine richtige Funktion daraus, kein Makro, und überlassl dem
> Compiler mögliches inlining.

Das ist aber nur praktikabel, wenn die aufzurufenden Funktionen alle 
keine Parameter (oder zumindest die gleiche Parameterliste haben), sonst 
muß man sich nachher womöglich Dutzende solcher Wrapper-Funktionen 
schreiben.

von Ralf G. (ralg)


Lesenswert?

Oder wie wäre sowas? (ohne externes 'if'; Funktion wird immer aufgerufen 
und prüft selber)
1
int function(int error)
2
{
3
 if (error != ERR_OK)
4
  return error;
5
// ---
6
// eigentlicher Code
7
// ---
8
}

von clonephone82 (Gast)


Lesenswert?

vielen dank für die Beiträge!
Wir haben uns jetzt auf die Variante von Klaus geeinigt.

Danke

von clonephone82 (Gast)


Lesenswert?

Achso das mit dem do{ while(0) verstehe ich noch nicht ganz ich habs mit 
und ohne probiert funktioniert.
Was ist hier der Sinn?

von Klaus W. (mfgkw)


Lesenswert?

Natürlich sehr lobenswert :-)

Wobei ich leider vergessen hatte, das Makro in der Definition von CALL 
auf CALL_FUN_IF_ERR_OK umzubenennen, aber das habt ihr wohl schon selber 
registriert.

von Klaus W. (mfgkw)


Lesenswert?

clonephone82 schrieb:
> Achso das mit dem do{ while(0) verstehe ich noch nicht ganz ich habs mit
> und ohne probiert funktioniert.
> Was ist hier der Sinn?

Angenommen, du hast dein Original-Makro.
Dann schreibst du hin:
1
   if( blabla ) CALL( f() )
2
   else puts( "kein blabla.." );
Dann erwartest du, daß entweder das CALL gemacht wird, oder das puts().

Tatsächlich macht der Präprozessor daraus aber:
1
   if( blabla ) if (err == ERR_OK){
2
    err = f();
3
  }
4
   else puts( "kein blabla.." );

Wenn man das richtig einrückt, sieht es so aus:
1
   if( blabla )
2
     if (err == ERR_OK)
3
     {
4
        err = f();
5
     }
6
     else puts( "kein blabla.." );

D.h. das else gehört jetzt nicht mehr zum ersten if, zu dem du es haben 
möchtest, sondern zum if aus dem Makro.
Der Compiler übersetzt das, aber du weisst nicht warum das Programm 
Unfug macht.

Ähnliche Fälle lassen sich noch mehr konstruieren.

Mit dem do...while(0) wird aus allem innerhalb etwas in sich 
abgeschlossenes, was keine solchen Nebeneffekte mehr erzeugen sollte.

Sieht doof aus, hilft aber.

von Bronco (Gast)


Lesenswert?

Das Problem kann man übrigens auch vermeiden, indem man immer 
geschweifte Klammern um die Blöcke schreibt, auch wenn sie nur eine 
Anweisung enthalten.
1
if( blabla ) 
2
  { CALL( f() ); }
3
else 
4
  { puts( "kein blabla.." ); }
Gefällt mir persönlich besser...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bronco schrieb:
> Gefällt mir persönlich besser...

Das Problem dabei ist, dass du auf die Disziplin des Makro-*Anwenders*
vertrauen musst, während das do...while(0) die Sache auf Ebene des
Makro-*Autors* löst (der ja schließlich den potenziellen Seiteneffekt
des Makros kennt).

von Klaus W. (mfgkw)


Lesenswert?

Jörg Wunsch schrieb:
> während das do...while(0) die Sache auf Ebene des
> Makro-*Autors* löst

genau

Bronco schrieb:
> indem man immer
> geschweifte Klammern um die Blöcke schreibt, auch wenn sie nur eine
> Anweisung enthalten.
> ...
> Gefällt mir persönlich besser...

genau

-> es schadet ja nichts, beides zu machen

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Klaus Wachtler schrieb:
> Jörg Wunsch schrieb:
>> während das do...while(0) die Sache auf Ebene des
>> Makro-*Autors* löst
>
> genau
>
> Bronco schrieb:
>> indem man immer
>> geschweifte Klammern um die Blöcke schreibt, auch wenn sie nur eine
>> Anweisung enthalten.
>> ...
>> Gefällt mir persönlich besser...
>
> genau
>
> -> es schadet ja nichts, beides zu machen

Kann man aber nur dann, wenn man auch beide Rollen hat ;-)


Bitte reduzieren Sie die Anzahl der Zitatzeilen.
Bitte reduzieren Sie die Anzahl der Zitatzeilen.

von Klaus W. (mfgkw)


Lesenswert?

Oder die andere Rolle dazu nötigen kann :-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Weitere Möglichkeit ist, CALL als variadisches Makro zu schreiben (C99) 
und einen bewerteten Block zu verwenden.

Damit lassen sich Aufrufe schachteln und ganz genauso wie normale 
C-Ausdrücke verwenden:
 
1
extern int fi (int);
2
extern int fc (char);
3
extern int fv (void);
4
extern int fp (const char*, ...);
5
6
#define CALL(F, ERR, ARGS...)   \
7
({                              \
8
    int _e = ERR;               \
9
    if (_e)                     \
10
        _e = F (ARGS);          \
11
    _e;                         \
12
})
13
14
15
int call (int i, char c, int error)
16
{
17
  error = CALL (fi, error, i);
18
  error = CALL (fc, error, c);
19
  error = CALL (fv, error);
20
  error = CALL (fi, CALL (fi, error, i), i);
21
    
22
  if (CALL (fp, error, "%s", i)
23
      || CALL (fp, error, "%c", c, i))
24
    return 0;
25
26
  return error;
27
}
 
Damit bleibt zumindest die Quelle leserlich und sieht genauso aus wie 
eine C-Quelle, zudem muß der Eingabeparameter ERR kein Lvalue sein.

Neben der Möglickkeit, ein allgemeines CALL-Makro zuverwenden kann man 
auch für jeden Callee ein eigenes Makro zur Verfügung stellen, daß den 
ersten Parameter wie ERR verwendet und die verbleibenden an den Callee 
reicht.

von clonephone82 (Gast)


Lesenswert?

@alle vielen dank.

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.