Deep dive into the C macros

Not long ago I was reading an article about C macro and its potential pitfalls and author suggested not to use them more often in the code and he mentioned about some example such as following to expose the problem of macros :

#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int increment(){
static int i=45;
i+=5;
return i;
}
int main(int c,char ** v)
{
int x=20;
printf("the maximum number between %d and %d is : %d",x,increment(),MAX(x,increment()));
return 0;
}

If you try to run this code you will see a very interesting result which makes most of the programmer suspicious about accuracy of C programming language, but the problem is not C. It is resides on macros mechanism and the way compiler treat the macros.

I disassembled the complied version and following is output

.file "macroTest.c"
 .text
 .globl increment
 .type increment, @function
increment:
.LFB0:
 .cfi_startproc
 pushq %rbp
 .cfi_def_cfa_offset 16
 .cfi_offset 6, -16
 movq %rsp, %rbp
 .cfi_def_cfa_register 6
 movl i.2162(%rip), %eax
 addl $5, %eax
 movl %eax, i.2162(%rip)
 movl i.2162(%rip), %eax
 popq %rbp
 .cfi_def_cfa 7, 8
 ret
 .cfi_endproc
.LFE0:
 .size increment, .-increment
 .section .rodata
 .align 8
.LC0:
 .string "the maximum number between %d and %d is : %d"
 .text
 .globl main
 .type main, @function
main:
.LFB1:
 .cfi_startproc
 pushq %rbp
 .cfi_def_cfa_offset 16
 .cfi_offset 6, -16
 movq %rsp, %rbp
 .cfi_def_cfa_register 6
 pushq %rbx
 subq $40, %rsp
 .cfi_offset 3, -24
 movl %edi, -36(%rbp)
 movq %rsi, -48(%rbp)
 movl $20, -20(%rbp)
 movl $0, %eax
 call increment
 cmpl -20(%rbp), %eax
 jl .L4
 movl $0, %eax
 call increment
 movl %eax, %ebx
 jmp .L5
.L4:
 movl -20(%rbp), %ebx
.L5:
 movl $0, %eax
 call increment
 movl %eax, %edx
 movl -20(%rbp), %eax
 movl %ebx, %ecx
 movl %eax, %esi
 movl $.LC0, %edi
 movl $0, %eax
 call printf
 movl $0, %eax
 addq $40, %rsp
 popq %rbx
 popq %rbp
 .cfi_def_cfa 7, 8
 ret
 .cfi_endproc
.LFE1:
 .size main, .-main
 .data
 .align 4
 .type i.2162, @object
 .size i.2162, 4
i.2162:
 .long 45
 .ident "GCC: (Ubuntu/Linaro 4.7.2-4precise1) 4.7.2"
 .section .note.GNU-stack,"",@progbits

As you see there is no clue about macros in compiled code in other words all the macros will be replaced by proper C code during compilation and if you look at assembly code carefully you will see the increament() function called by main() three times rather than two times and the reason is code replacement of compiler.

printf("the maximum number between %d and %d is : %d",x,increment(),MAX(x,increment()));

In above line after calling MAX macro we send the increment() function to MAX macro whereas the defintion of MAX is :

#define MAX(x,y) ((x) > (y) ? (x) : (y))

and the compiler will translate it to something like :

((x) > (increment()) ? (x) : (increment())

as you can see we’re going to get two increment() function in MAX and one in printf() statement and thats the reason of having three calling to increment() inside assembly code.

Conclusion

I am not big fan of making tasks convoluted and I personally think for having a lean and working code its good to step aside from mixed code or combined languages when its unnecessary and this inspection shows how complex it could be in case of using an approach without knowing it thoroughly. So next time think more before making your code complex.

Happy coding!