Macro versus function in C

I've always seen examples and cases where using a macro is better than using a function.

Can someone explain to me an example with a lack of macro compared to a function?

+91
c function c-preprocessor
Feb 01 '12 at 22:50
source share
11 answers

Macros are error prone because they rely on text substitution and do not perform type checking. For example, this macro:

#define square(a) a * a 

works great when using an integer:

 square(5) --> 5 * 5 --> 25 

but does very strange things when used with expressions:

 square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5 square(x++) --> x++ * x++ --> increments x twice 

Putting parentheses in arguments helps, but does not completely eliminate these problems.

When macros contain multiple statements, you may have problems with the control-flow constructs:

 #define swap(x, y) t = x; x = y; y = t; if (x < y) swap(x, y); --> if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t; 

The usual strategy to fix this is to put statements in the "do {...} while (0)" loop.

If you have two structures that contain a field with the same name but different semantics, the same macro can work with both, which will lead to strange results:

 struct shirt { int numButtons; }; struct webpage { int numButtons; }; #define num_button_holes(shirt) ((shirt).numButtons * 4) struct webpage page; page.numButtons = 2; num_button_holes(page) -> 8 

Finally, macros can be difficult to debug, leading to strange syntax or runtime errors that you need to open up for understanding (for example, using gcc -E), since debuggers cannot go through macros, as in this example:

 #define print(x, y) printf(xy) /* accidentally forgot comma */ print("foo %s", "bar") /* prints "foo %sbar" */ 

Built-in functions and constants help to avoid many of these macro problems, but are not always applicable. In cases where macros are deliberately used to determine polymorphic behavior, inadvertent polymorphism can be difficult to avoid. C ++ has a number of functions, such as templates, that help create complex polymorphic constructs in a type-safe way without using macros; see Stroustrup C ++ Programming Language for details.

+104
Feb 01 2018-12-01T00:
source share

Macro Properties :

  • Macro Pre-Processed
  • No type checking
  • Code Length Increases
  • Using a macro can have a side effect.
  • Execution speed faster
  • Before compilation, the macro name is replaced with the macro value
  • Useful when a small code appears a lot of time.
  • Macro Do Not Check Compilation Errors

Functional Functions :

  • Function Compiled
  • Type check in progress
  • The code length remains the same.
  • No side effect
  • Execution speed slower
  • During a function call, control transfer occurs
  • Useful when large code appears a lot of time.
  • Function Check Compile Errors
+33
Nov 27 '15 at 9:06
source share

Side effects are serious. Here is a typical case:

 #define min(a, b) (a < b ? a : b) min(x++, y) 

expands to:

 (x++ < y ? x++ : y) 

x doubles in one expression. (and undefined behavior)




Writing multiline macros is also a problem:

 #define foo(a,b,c) \ a += 10; \ b += 10; \ c += 10; 

They need a \ at the end of each line.




Macros cannot return anything unless you make it a single statement:

 int foo(int *a, int *b){ side_effect0(); side_effect1(); return a[0] + b[0]; } 

It is not possible to do this in a macro unless you use the GCC expression operator. (UPDATE: You can use the comma operator, though ... overlooked ... But it can still be less readable.)




Procedure: (Courtesy @ouah)

 #define min(a,b) (a < b ? a : b) min(x & 0xFF, 42) 

expands to:

 (x & 0xFF < 42 ? x & 0xFF : 42) 

But & has a lower priority than < . Thus, 0xFF < 42 is evaluated first.

+30
Feb 01 2018-12-12T00:
source share

Example 1:

 #define SQUARE(x) ((x)*(x)) int main() { int x = 2; int y = SQUARE(x++); // Undefined behavior even though it doesn't look // like it here return 0; } 

then:

 int square(int x) { return x * x; } int main() { int x = 2; int y = square(x++); // fine return 0; } 

Example 2:

 struct foo { int bar; }; #define GET_BAR(f) ((f)->bar) int main() { struct foo f; int a = GET_BAR(&f); // fine int b = GET_BAR(&a); // error, but the message won't make much sense unless you // know what the macro does return 0; } 

Compared with:

 struct foo { int bar; }; int get_bar(struct foo *f) { return f->bar; } int main() { struct foo f; int a = get_bar(&f); // fine int b = get_bar(&a); // error, but compiler complains about passing int* where // struct foo* should be given return 0; } 
+13
Feb 01 2018-12-12T00:
source share

Re-checking the parameters and code is not repeated, which can lead to bloat code. Macro syntax can also lead to any number of strange cross cases where a semicolony or order of priority can interfere. Here is a link that demonstrates the evil macro

+11
Feb 01 2018-12-12T00:
source share

If in doubt, use functions (or built-in functions).

However, the answers here mainly explain the problems with macros, and not the simple idea that macros are evil, because stupid accidents are possible.
You can know about the pitfalls and learn to avoid them. Then use macros only when there is a good reason.

There are certain exceptional cases where there are advantages to using macros, including:

  • Common functions, as indicated below, can have a macro that can be used for different types of input arguments.
  • A variable number of arguments can be displayed for different functions instead of using C va_args . For example, https://stackoverflow.com/a/414829/
  • They may optionally include local information, such as debugging lines:
    ( __FILE__ , __LINE__ , __func__ ). check the pre / post, assert conditions on failure, or even static statements, so the code will not compile if used improperly (mostly useful for debugging collections).
  • Check the input arguments, you can do tests on the input arguments, such as checking their type, sizeof, check struct . Elements are present before casting (may be useful for polymorphic types).
    Or check the array matches some length condition.
    see stack overflow.
  • While it was noted that functions perform type checking, C will also force values ​​(e.g. ints / floats). In rare cases, this can be problematic. It can be written macros that are more demanding than the function of their input arguments. see https://stackoverflow.com>
  • Using them as wrappers for functions, in some cases you may need to avoid repetition, for example ... func(FOO, "FOO"); , you can define a macro that extends the string func_wrapper(FOO); for you func_wrapper(FOO);
  • If you want to manipulate variables in the local area of ​​callers, then the pointer to the pointer works fine, but in some cases it is less worried about using a macro.
    (assignment to several variables, for each - pixel operations, this is an example that you can prefer to macro over a function ... although it still depends a lot on the context, since inline functions can be an option).

Admittedly, some of them rely on compiler extensions that are not standard C. The ifdef that you may have less portable code or should have ifdef , so they are only used if the compiler supports it.




Preventing Multiple Arguments

Noting this because it is one of the most common causes of errors in macros (for example, passing in x++ , where a macro can grow several times).

it can be written macros that avoid side effects with multiple instance arguments.

C11 General

If you like to have a square macro that works with various types and supports C11, you can do this ...

 inline float _square_fl(float a) { return a * a; } inline double _square_dbl(float a) { return a * a; } inline int _square_i(int a) { return a * a; } inline unsigned int _square_ui(unsigned int a) { return a * a; } inline short _square_s(short a) { return a * a; } inline unsigned short _square_us(unsigned short a) { return a * a; } /* ... long, char ... etc */ #define square(a) \ _Generic((a), \ float: _square_fl(a), \ double: _square_dbl(a), \ int: _square_i(a), \ unsigned int: _square_ui(a), \ short: _square_s(a), \ unsigned short: _square_us(a)) 

expression expressions

This is a compiler extension supported by GCC, Clang, EKOPath, and Intel C ++ (but not MSVC);

 #define square(a_) __extension__ ({ \ typeof(a_) a = (a_); \ (a * a); }) 



Thus, the disadvantage of macros is that you need to know in order to use them first, and that they are not supported so widely.

One of the advantages is that in this case you can use the same square function for different types.

+11
Aug 08 '14 at 18:29
source share

One drawback of macros is that debuggers read source code that does not have extended macros, so running a debugger in a macro is not necessarily useful. Needless to say, you cannot set a breakpoint inside the macro as you can with functions.

+6
Feb 01 2018-12-01T00:
source share

Adding to this answer.

Macros are inserted directly into the program by the preprocessor (since these are mainly preprocessor directives). Therefore, they inevitably use more memory than the corresponding function. On the other hand, the function takes longer to call and return results, and this overhead can be avoided with macros.

Macros also have some special tools that can help with portability of the program on different platforms.

Macros do not need to assign a data type to their arguments, unlike functions.

In general, they are useful in programming. And both macroinstructions and functions can be used depending on the circumstances.

+6
Jul 04 '15 at 14:11
source share

Functions perform type checking. This gives you an extra layer of security.

+5
Feb 02 2018-12-12T00:
source share

I did not notice in the answers above one advantage of functions over macros, which, it seems to me, are very important:

Functions can be passed as arguments, macros cannot.

A specific example: you want to write an alternative version of the standard strpbrk function that will accept, and not an explicit list of characters to search inside another line, a function (a pointer to a) that will return 0 until the character passes some test (user defined). One of the reasons you might want to do this is to use other standard library functions: instead of providing an explicit string, full punctuation, you can pass ctype.h "ispunct" instead, etc. If "ispunct" was only implemented as a macro, this will not work.

There are many other examples. For example, if your comparison is performed with a macro and not a function, you cannot pass it to stdlib.h 'qsort'.

A similar situation in Python is "printing" in version 2 compared to version 3 (non-skipping instruction versus skipping function).

+2
Apr 25 '18 at 20:55
source share

If you pass a function as an argument to a macro, it will be evaluated every time. For example, if you call one of the most popular macros:

 #define MIN(a,b) ((a)<(b) ? (a) : (b)) 

like this

 int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2)); 

functionThatTakeLongTime will be evaluated 5 times, which can significantly reduce performance

+1
Jul 28 '19 at 8:12
source share



All Articles