Markro für speziellen Funktioncall

Gast #2832813
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.
#2832869
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
     ...
Gast #2832875
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.
#2833252
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.
Gast #2834506
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...
Persönliche Seite #2834706
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.
Persönliche Seite #2835011
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.

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