31

I'm trying to instrument some code to catch and print error messages. Currently I'm using a macro somethng like this:

#define my_function(x) \
  switch(function(x)) { \
    case ERROR: \
      fprintf(stderr, "Error!\n"); \
      break; \
  }

Normally, I never capture the function output and this works fine. But I've found a couple cases where I also need the return value of function(). I tried something like the following, but this produces a syntax error.

#define my_function(x) \
  do { \
    int __err = function(x); \
    switch(__err) { \
      case ERROR: \
        fprintf(stderr, "Error!\n"); \
        break; \
    } \
    __err; \
  } while(0)

I could declare a global variable to hold the return value of the function, but that looks ugly and my program is multithreaded, so that's likely to cause problems. I'm hoping there's a better solution out there.

A-Sharabiani
  • 17,750
  • 17
  • 113
  • 128
Michael Mior
  • 28,107
  • 9
  • 89
  • 113

5 Answers5

119

GCC has a feature called statement expressions

So if define macro like

#define FOO(A) ({int retval; retval = do_something(A); retval;})

then you will be able to use it like

foo = FOO(bar);
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
qrdl
  • 34,062
  • 14
  • 56
  • 86
  • 3
    That's basically what I was trying to do with the above. I think @Jens was right that an inline function better suits my needs. – Michael Mior Aug 20 '10 at 19:02
  • 1
    This should be the right answer. Sometimes I can't use static inline functions (such as when I need "goto"), but define the function as a macro and using statement expressions solves my probloem. – Yuxuan Lu Nov 18 '20 at 15:23
11

This is relatively complicated code, there is not much reason to have it in a macro. Make it inline (C99) or static (C89) or both if you really want to place it in a header file. With any reasonable compiler this then should result in the same efficiency as a macro.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 5
    An inline function unfortunately cannot do some of the tricks that a macro can, such as concatenating identifiers (`FOO ## _size` from some generated code I'm using, for example). Otherwise, yes, please use inline functions. – George Hilliard Feb 02 '17 at 19:55
  • 14
    Another useful thing inline function cannot do is expanding `__FILE__` and `__LINE__` macro to generate error message where error actually happened. Plus, I personally find answers telling people not to do the things they are asking a way for unhelpful as they do not really answer the question. If you answer a question, answer the question. Any comments on the question should come only after the question is answered or just be a comment. – xiay May 24 '18 at 21:53
  • @logicor, I think you are missing the point. The question is about returning a value from such constructs. This is a feature that you can't have with macros alone in standard C. But nothing hinders you to wrap a macro *around* such a function to also deal with `__LINE__` etc – Jens Gustedt May 26 '18 at 06:28
  • @JensGustedt When you wrap a macro around the function, do you not go back to the original problem where you cannot return value from the macro expression? The only way I see it work with inline function is passing `__LINE__` as an argument to the inline function, which results in somewhat ugly code IMHO. – xiay May 30 '18 at 20:44
  • As for statement expressions, it seems gcc, clang and intel c all support it. Since MSVC have given up on C99 for years anyway, it is unlikely to be the portability hammer if you really have a C project. – xiay May 30 '18 at 20:51
  • @logicor, it seems that you completely underestimate how many different C compilers are out there. – Jens Gustedt May 31 '18 at 07:45
  • 1
    @logicor, doing this directly in the call would indeed be ugly, yes. That is why I would wrap such a call in a macro that adds `__LINE__`, `__FILE__` or `__func__`. This would be completely portable, type safe and efficient. And please all you guys voting this down, go back to the question that is answered here. There is nothing about these special macros in the question. – Jens Gustedt May 31 '18 at 07:48
7

A very late reply. But none the less. I agree inline functions are better but MACROs do offer some pretty printing fun you can't get with inline functions. I agree with @qrdl that you can indeed use statement expressions had you restructured your statements a bit. Here is how it would work with a macro -

#define my_function(x, y) ({ \
  int __err = 0; \
  do { \
    __err = function(x, y); \
    switch(__err) { \
      case ERROR: \
        fprintf(stderr, "Error!\n"); \
        break; \
    } \
  } while(0); \
  __err; \
})
gusaki
  • 741
  • 7
  • 13
4

Sorry, this is an edit...

  1. I think you just need the curly braces. No need for the do..while keywords
  2. Make sure that the backslashes are the last characters on each line (no space after).
  3. If you need to get the err value out of the macro, you can just add a parameter

Like so:

 #define my_function(x, out) \
      { \
        int __err = function(x); \
        switch(__err) { \
          case ERROR: \
            fprintf(stderr, "Error!\n"); \
            break; \
        } \
        __err; \
        (*(out)) = _err; \
      }

To preserve the pass-by-reference C paradigm, you should call my_function this way:

int output_err;

my_function(num, &output_err);

This way, later, if you decide to make my_function a real function, you don't need to change the call references.

Btw, qrdl's "Statement Expressions" is also a good way to do it.

hopia
  • 4,880
  • 7
  • 32
  • 54
  • The `do{}while(0)` is needed because it forces you to put a semicolon after `my_function(num, &output_err)`, just like after a real function. – insaneinvader Mar 05 '23 at 21:14
-4

there is no need to declare variable if your function is returning something then you can directly get that value. For example:

#define FOO(A) do_something(A)

Here do_something returns some integer. Then you can easily use it like:

int a = FOO(a);
The joker
  • 189
  • 1
  • 6
  • 3
    This doesn't work in my this situation since the macro also needs to use the output of `do_something` and the only way to take the approach you're suggesting is to call `do_something` twice. – Michael Mior Feb 24 '15 at 15:02