Common usages of macros
Macro for defining constants
#define PI 3.14
const double PI = 3.14;
const instead. This makes sure that you explicitly type your constant.
If you can use function to achieve what you want, always use a function. Explicitly declaring this function
inline is not necessary. As you correctly noted, the
inline keyword does not really tell compiler to inline the function. It just puts a restriction on the function that you can not take address of that function. Practically, this allows you to put the function body in the header file without compiler giving you a redefinition error when you include this header in multiple C files.
Where is a function-like macro actually useful?
There are some cases where you want to remove duplication of certain code and a function-call does not let you achieve what you want.
For example a logging macro that prints line number and function name.
// Macro definition in some header file.
#define MYLOG(fmt, args...) \
fprintf(stderr, "%s:%d, " fmt "\n", __func__, __LINE__, ##args);
MYLOG("Hello World %d", 1234);
This creates a function-like macro that prints your message along with the function name and line-number from the source file. As an exercise try to see if you can avoid using a macro and achieve the same functionality.
P.S. It's not possible (at least in C or C++).
Macros as compile-time flags
This is a very genuine and justified usage of macros. If you want to do selective compilation based on some conditions (which come from either compiler command line or a Makefile), there is no other way than to use macros. However, even in this scenarios macros should be used sparingly. Having too many #ifdefs in your code makes it difficult to read. As much as possible, I would do this selective compilation by moving code to separate files.