2

I am trying to understand defining functions as macros and I have the following code, which I am not sure I understand:

#define MAX(i, limit) do \      
{ \     
    if (i < limit) \    
    { \ 
        i++; \
    } \ 
} while(1)      
        
void main(void)     
{       
    MAX(0, 3);  
}       

As I understand it tries to define MAX as an interval between 2 numbers? But what's the point of the infinite loop?

I have tried to store the value of MAX in a variable inside the main function, but it gives me an error saying expected an expression

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Tau TV
  • 41
  • 3
  • 3
    Welcome to StackOverflow! Please take the [tour] to learn how this site works, and please read "[ask]". -- Where is this code from? The endless loop is an apparent error, and the name is misleading. – the busybee Feb 09 '23 at 12:10
  • @thebusybee I am currently in an software developing internship, and trying to learn embedded C since it's a new field for me. This was an exercise asking me what the following code will do. I was confused since i had never seen a function written like this – Tau TV Feb 09 '23 at 12:15
  • 5
    `while(1)` was probably supposed to be `while(0)`. See https://c-faq.com/cpp/multistmt.html – Steve Summit Feb 09 '23 at 12:15
  • 1
    `MAX` is not a macro that "returns" a value, which is why you got that error. – Steve Summit Feb 09 '23 at 12:16
  • 4
    Your macro call `MAX(0,3)` expands to `do { if (0 < 3) { 0++; } while(1)`. What do you think `0++` should do? – Ian Abbott Feb 09 '23 at 12:18
  • 2
    With the correction of `while(0)` it would become a saturating incrementer. Just experiment with it, correct syntax and logical errors, call it multiple times, use your debugger, and watch the variable. – the busybee Feb 09 '23 at 12:26
  • 2
    See also [Why use apparently meaningless do-while and if-else statements in macros?](https://stackoverflow.com/questions/154136) – Steve Summit Feb 09 '23 at 12:29
  • You say you get the error: "expected an expression" hmmm... I would expect more like "lvalue required" – Support Ukraine Feb 09 '23 at 12:48
  • 1
    Also, macro arguments should generally be enclosed in parens in the body to prevent unwanted expansions. – stark Feb 09 '23 at 13:09
  • 1
    `void main(void){...}` is not valid in C (or C++) (however [it is sometimes tolerated by some compiler implementations](https://stackoverflow.com/q/38768582/645128). If your plans are to write in C, use a modern, standard compliant compiler. Not all of them require a purchase. ([gcc for Linux or Windows](https://gcc.gnu.org/install/binaries.html)) – ryyker Feb 09 '23 at 13:32
  • 1
    FWIW, the error you get should be comparable to _expression not assignable_. This simply means that because `i` in the macro is expanded to the literal `0`, resulting in the literal expression: `0++`. Thus compiler is telling you that _zero is not assignable_. Preceding the call to `MAX with a declaration: `int i = 0;`, and replacing the `0` argument with `i` allows an exe to be created, but with an infinite loop. This of coarse can be corrected by following the inner closing bracket of the if statement in the macro definition with `else break;`. – ryyker Feb 09 '23 at 13:49
  • The point of the infinite loop (`while(1)`) is that it's most likely simply wrong. Where did you find this piece of code? In some existing C code. Or did _you_ write this? – Jabberwocky Feb 09 '23 at 13:54
  • 1
    The correct answer to the question is probably "get rid of this nonsense and write `if(var < max) { var++; }`. No need for macros. – Lundin Feb 09 '23 at 14:07

4 Answers4

4

I am currently in a software developing internship, and trying to learn embedded C since it's a new field for me. This was an exercise asking me what the following code will do. I was confused since I had never seen a function written like this

You are confused because this is a trick question. The posted code makes no sense whatsoever. The MAX macro expands indeed to an infinite loop and since its first argument is a literal value, i++ expands to 0++ which is a syntax error.

The lesson to be learned is: macros are confusing, error prone and should not be used to replace functions.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    @chux-ReinstateMonica: I removed the remark on `main`, as it is irrelevant for the question about macros. – chqrlie Feb 09 '23 at 16:28
  • @Lundin: I removed this remark which is indeed irrelevant and inappropriate for embedded development... But in my experience, embedded development seems to be the place where the worst code is produced and exposing beginners so such toxic examples as the posted code is sad, but alas typical. – chqrlie Feb 09 '23 at 16:32
  • Well chances are that this was an interview question made awful on purpose, to see if the candidate picks up the obvious problems with it. – Lundin Feb 10 '23 at 07:02
2

You have to understand that before your code gets to compiler, first it goes through a preprocessor. And it basically changes your text-written code. The way it changes the code is controlled with preprocessor directives (lines that begin with #, e.g. #include, #define, ...).

In your case, you use a #define directive, and everywhere a preprocessor finds a MAX(i, limit) will be replaced with its definition.

And the output of a preprocessor is also a textual file, but a bit modified. In your case, a preprocessor will replace MAX(0, 3) with

do
{
    if (0 < 3)  
    {
        0++;
    }
} while(1)

And now the preprocessor output goes to a compiler like that.

So writing a function in a #define is not the same as writing a normal function void max(int i, int limit) { ... }.

m.k
  • 118
  • 6
2

Suppose you had a large number of statements of the form

if(a < 10) a++;

if(b < 100) b++;

if(c < 1000) c++;

In a comment, @the busybee refers to this pattern as a "saturating incrementer".

When you see a repeated pattern in code, there's a natural inclination to want to encapsulate the pattern somehow. Sometimes this is a good idea, or sometimes it's fine to just leave the repetition, if the attempt to encapsulate it ends up making things worse.

One way to encapsulate this particular pattern — I'm not going to say whether I think it's a good way or not — would be to define a function-like macro:

#define INCR_MAX(var, max) if(var < max) var++

Then you could say

INCR_MAX(a, 10);
INCR_MAX(b, 100);
INCR_MAX(c, 1000);

One reason to want to make this a function-like macro (as opposed to a true function) is that a macro can "modify its argument" — in this case, whatever variable name you hand to it as var — in a way that a true function couldn't. (That is, if your saturating incrementer were a true function, you would have to call it either as incr_max(&a, 10) or a = incr_max(a, 10), depending on how you chose to set it up.)

However, there's an issue with function-like macros and the semicolon at the end. I'm not going to explain that whole issue here; there's a big long previous SO question about it.

Applying the lesson of that other question, an "improved" INCR_MAX macro would be

#define INCR_MAX(var, max) do { if(var < max) var++; } while(0)

Finally, it appears that somewhere between your exercise and this SO question, the while(0) at the end somehow got changed to while(1). This just about has to have been an unintentional error, since while(1) makes no sense in this context whatsoever.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
0

Yeah, there's a reason you don't understand it - it's garbage.

After preprocessing, the code is

void main(void)
{
  do 
  {
    if ( 0 < 3 )
    {
      0++;
    }
  } while(1);
}

Yeah, no clue what this thing is supposed to do. The name MAX implies that it should evaluate to the larger of its two arguments, a la

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

but that's obviously not what it's doing. It's not defining an interval between two numbers, it's attempting to set the value of the first argument to the second, but in a way that doesn't make a lick of sense.

There are three problems (technically, four):

  • the compiler will yak on 0++ - a constant cannot be the operand of the ++ or -- operators;

  • If either i or limit are expressions, such as MAX(i+1, i+5) you're going to have the same problem with the ++ operator and you're going to have precedence issues;

  • assuming you fix those problems, you still have an infinite loop;

The (technical) fourth problem is ... using a macro as a function. I know, this is embedded world, and embedded world wants to minimize function call overhead. That's what the inline function specifier is supposed to buy you so you don't have to go through this heartburn.

But, okay, maybe the compiler available for the system you're working on doesn't support inline so you have to go through this exercise.

But you're going to have to go to the person who gave you this code and politely and respectfully ask, "what is this crap?"

John Bode
  • 119,563
  • 19
  • 122
  • 198