r/C_Programming 8d ago

Question Why is this macro function's definition put in brackets?

I'm learning about macros from this website:

https://www.almabetter.com/bytes/articles/macros-in-c

The website talks about how macro functions can cause side effects that are not wanted.

The website provides the following example:

Pitfall: Macros can cause side effects if their arguments are evaluated multiple times. For example:

#define SQUARE(x) (x * x)
int result = SQUARE(++i);

Here, i will be incremented twice, leading to incorrect results.

Solution: Enclose the macro body in parentheses to ensure correct precedence. For example:

#define SQUARE(x) ((x) * (x))

Now, why does wrapping the definition of SQUARE(X) prevent i from being incremented twice?

0 Upvotes

37 comments sorted by

21

u/EpochVanquisher 8d ago

It doesn’t. It just prevents you from getting rebracketed results:

#define SQUARE(x) (x * x)
int x = SQUARE(1 + 1);

The value of x is 3, which is surprising. This is what the parentheses prevent.

3

u/LifeExperienced1 8d ago

int x = SQUARE(1 + 1) ---> (1 + 1 * 1 + 1) ---> (1 + 1 + 1) ---> (3)

Is that why?

Also why does a macro function directly paste "1 + 1" into x, whereas a normal function would evaluate "1 + 1" before pasting it into x?

10

u/aioeu 8d ago edited 8d ago

They are called "function-like macros", not macro functions. That "like" is doing a lot of heavy lifting there. Really, the only thing they have in common with function calls is that they have parentheses. Apart from that, they're completely different.

A macro just gets blindly replaced with new text, the expansion of the macro. The macro preprocessor has no idea what the text means, no idea whether the text is even valid C code, and no idea whether the text is what you want.

1

u/glasket_ 8d ago

Really, the only thing they have in common with function calls is that they have parentheses

Tbf they also take arguments. If you squint hard enough you could say they're call-by-name functions with dynamic scope and untyped arguments.

6

u/delinka 8d ago edited 8d ago

It’s not a function, it’s substitution. Macros are literally copy/paste templates. And they happen before compiling, and certainly before code is executed. Variables can’t be evaluated. Constants are evaluated by the compiler, not the pre-processor.

3

u/knowwho 8d ago edited 8d ago

Yes, they literally paste the raw text of the macro into the "call site" before the compiler runs, they perform text substitution in your source code. You can see this yourself by using your compiler's flags to run just the preprocessor.

In Clang, for example, you can use -E which dumps the preprocessed text (and some debugging output) to standard out:

$ cat test.c
#define FOO(x) (x * x)

int main() {
  return FOO(3 + 4);
}


$ clang -E test.c
# 1 "test.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 482 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "test.c" 2


int main() {
  return (3 + 4 * 3 + 4);
}

In the C model, the preprocessor is exactly what it sounds like: It preprocesses the text of your source code before it is fed to the compiler.

This is very different from function calls, where expression arguments are evaluated before the function is called.

2

u/gwenbeth 8d ago

These #define macros in a way are not part of the c language, they belong to the c pre-processor (cpp). cpp just does string substitution. with no regards to c syntax. This substituted string is then passed to the actual c compiler.

Doing it so simplistically makes sense when you only have 64KB of memory and are trying not kill your computer when compiling.

1

u/RainbowCrane 8d ago

Macros are text replacements, not function calls. old school C programmers like me used macros to eliminate the overhead of a function call for frequently used mathematical operations.

They’re also handy for simplifying repeated function calls. For example, I can’t recall the exact details, but in 1990s windows programming it was common to see a macro like this:

coordinate *location = SCREEN_COORD(window_x, window_y);

where the macro translated to some stupidly complex Windows function call like:

get_absolute_coordinates(parent_window, (window_x), (window_y), some_ridiculous_global_data_structure_required_by_windows);

Instead of relying on yourself to correctly type all that out 97 times in a source file you could define a macro to handle the boilerplate stuff and just change window_x and window_y.

The macro does no evaluation of any mathematical expression, so the safest way to guarantee minimal side effects is to fully parenthesize the arguments

7

u/DawnOnTheEdge 8d ago

The GCC statement-expression extension is the best solution using macros.

Use inline functions when you can, and macros when you have to.

6

u/WittyStick 8d ago

For demonstration, the statement expression version would be written as

#define SQUARE(x) \
    __extension__ ({ \
        typeof(x) _x = (x); \
        (_x * _x); \
    })

The word __extension__ is optional, but makes it clearer that you are using a non-standard feature.

5

u/SmokeMuch7356 8d ago

It doesn't; it's there to avoid precedence issues. Without it, SQUARE(x + 1) expands to (x + 1 * x + 1), which is not what you want. With the extra parens it expands to ((x + 1) * (x + 1)).

Unfortunately, it doesn't help with expressions that have side effects like i++; C doesn't force left-to-right evaluation, nor does it force the side effects of operators like ++ be applied immediately. (i++) * (i++) does not have a guaranteed result - the behavior is undefined. You'll get a result, but it's not guaranteed to be correct, or even consistent.

That's why macros like this are a bad idea outside of very specific circumstances.

6

u/pjl1967 8d ago

That web site has several mistakes, even in the example you cited. An expression like:

++i * ++i

is actually undefined behavior. (See C11 standard, §6.5¶2.)

For a better article, see here.

1

u/LifeExperienced1 8d ago

But do preprocessor macro functions still attempt to evaluate ++i*++i since they don’t care about type?

I’ll check out that article. Thank you!

4

u/binarycow 8d ago

Macros don't evaluate.

Macros are a search/replace mechanism only.

2

u/pjl1967 8d ago

To add to that: the preprocessor knows virtually nothing about what anything in C actually means. It knows what tokens comprise C, but that's about it. It has no idea that ++ is an operator or what it does; nor does it know anything about operator precedence.

2

u/binarycow 8d ago

It doesn't even know what tokens comprise C. It knows only what tokens comprise the preprocessor's language.

0

u/pjl1967 8d ago

Yes, it does know what tokens comprise C. See here under Paste Avoidance.

1

u/binarycow 8d ago

Hmm! Okay! Thanks for the correction.

3

u/SwordsAndElectrons 8d ago

They are not "macro functions." They are simply macros. You can call them, "function-like," if you wish, but they are just they do not evaluate anything at all.

Given the macro defined as...

#define SQUARE(x) (x * x)

... the statement...

SQUARE(++i);

... becomes...

++i * ++i;

... before the code is compiled.

A macro is a preprocessor directive. The text substitution is done before any instructions are generated. What you should be trying to understand is what the code looks like after the substitution is made, which in turn should give insight into why it behaves how it does.

1

u/glasket_ 8d ago

The macro just replaces itself with whatever the final text result is; if the macro gives you something like (++i)*(++i) then it's still UB because that will be evaluated after substitution.

Or, whenever a macro is used, you can imagine that the output is equivalent to if you had directly typed the full expression in your program. So:

#define SQUARE(x) ((x) * (x))
int sq = SQUARE(++i);

is exactly the same as

int sq = ((++i) * (++i));

The macro never truly evaluates anything, it just replaces the argument with whatever tokens/characters were passed to it.

You can see what your code looks like after preprocessing/macro expansion using -E as an argument to GCC or Clang or /P in MSVC. This way you can see what everything looks like before the actual compilation happens.

-1

u/sciencekm 8d ago

I agree that modifying a variable more than once in the same expression is undefined. However, I'm guessing that if 'i' in this particular expression is 'volatile', then the result will be consistent with any compiler that correctly supports volatile. That is, the result will always be (i + 1) * (i + 2).

4

u/pjl1967 8d ago

volatile doesn't make undefined behavior go away. The compiler might generate code that does exactly what you expect; or it might reformat your hard drive.

1

u/sciencekm 8d ago

I'm not so sure about that. In practice, is it even possible to create a correct C compiler that would produce a different result? I would like to see the machine code sequence generated.

I get it that the standard says undefined.

3

u/pjl1967 8d ago

In practice, is it even possible to create a correct C compiler that would produce a different result?

Since it's undefined behavior, what's "correct" goes out the window. The compiler could do absolutely anything and be "correct."

I would like to see the machine code sequence generated.

Feel free to use godbolt.

The machine code for compiler X for CPU Y might be just fine and do what you expect. The machine code for compiler S and CPU T might not. So all that will tell you is whether the compiler does what you expect right now. But there's no guarantee that the next release of the compiler will do the same thing.

1

u/sciencekm 8d ago

When I say correct, I mean correct C, not correct for my expectations.

I have written compilers before, as many here I'm sure have, so my understanding in this area is not just theoretical. One example is all that is needed to disprove my conjecture that it is not possible to create such a compiler.

The behavior for volatile is well defined, and this is what makes the expression in question behave very predictably.

1

u/pjl1967 7d ago

You're missing the point. Even if everything you said is true, it doesn't matter because the standard says it's undefined.

If you want to roll the dice and assume every compiler at every optimization level on every CPU now and at all times in the future behaves as you describe, be my guest.

1

u/SmokeMuch7356 7d ago

With very few exceptions C does not force left-to-right evaluation of expressions, nor does it require that the side effect of ++ be applied immediately after evaluation. volatile has no effect on that. The following is a perfectly valid evaluation sequence for i++ * i++:

t0 <- i (second i++)
t1 <- i (first i++)
r <- t0 * t1;
i <- i + 1
i <- i + 1

You'll get a result, sure, but it's not guaranteed to be consistent from build to build or even from run to run. It's not even guaranteed to be the same if it appears multiple times in the same program.

1

u/sciencekm 7d ago

The expression in question is not "i++ * i++", but "++i * ++i". I'm not talking about any generic expression, just this particular one.

With volatile:

  1. The first term to be evaluated will always write the incremented value of i to memory. It will not hold on to it in register or some temporary location.

  2. The second term to be evaluated will always read the value of i from memory just before use. It will not get it from some other temporary location. This guarantees that the value it will get is the one incremented by the evaluation of the first term.

Also, for this particular expression, the order of evaluation does not matter, because, (a) the terms are identical, (b) the operation is multiplication, and (c) the OPs data type is integer. The first term to be evaluated can be the left one or the right one.

I would love to see someone create an evaluation that creates a different result.

2

u/marc_b_reynolds 7d ago

FWIW: This explores a fair number of macro topics: https://jadlevesque.github.io/PPMP-Iceberg/

1

u/Zirias_FreeBSD 7d ago

I don't think it's very fair to downvote this because it refers to an obvious garbage source ... I mean, the question makes perfect sense because the source is garbage.

In the example it gives, a macro expansion of (++i * ++i) is changed into ((++i) * (++i)) and surprise, they are exactly equivalent and both of them are broken (undefined behavior) because side effects are unsequenced against each other.

The unfortunate truth is: Standard C doesn't offer a way to avoid such macro (mis-)use.

It still makes sense to always fully parenthesize function-like macros, because that does avoid "precedence issues" as pointed out by other answers.

1

u/flyingron 7d ago

Well, other than not using macros. There are more issues using macros for "pseudofunctions" and these are easily fixed by just using real (possibly inline) functions.

2

u/Zirias_FreeBSD 7d ago

Sure. And for the example here, this would work perfectly well. But then, there are quite some things you can do with macros that can't just be translated to inline functions.

I'd say for real-life code, make sure it's obvious that something is a macro, and make it the caller's responsibility to avoid the pitfalls (which is a pretty common philosophy with C).

0

u/flyingron 7d ago

I guess if you call undefined bheavior "perfectly well" then go for it. You'd not last ten minutes in my company where we value correct and maintainable code.

2

u/Zirias_FreeBSD 7d ago

maybe question your interpretation first if it doesn't make any sense? I was talking about your (inline) functions.

1

u/stef_eda 7d ago

#define SQUARE(X) (x * x)

...

SQUARE(1 + 2); ===> (1 + 2 * 1 + 2) ===> (5). Not probably what you wanted.

1

u/flyingron 7d ago

Both examples will attempt to increment i twice. This is not just an "incorrect result" but undefined behavior.

The correct version:

inline int SQUARE(int x) { return x*x; };

What wrapping parentheses does is avoid syntactical confusion:

 int result = SQUARE(x+5);

would evaluate to

int result = x + 5 * x + 5;

Wrapping parentheses around it would give the "expected" answer.