Link to home
Start Free TrialLog in
Avatar of soodsandeep
soodsandeepFlag for India

asked on

C++ Inline fucntion vs. macro

hello.
Please, what is difference between an inline function and a macro in C++ since both are expanded in-line.
waiting..
thanks.
Avatar of hongjun
hongjun
Flag of Singapore image

The difference is in interpreting arguments. Arguments for inline functions are typed, therefore compiler can apply some type checking to that function calls.
Macro isn't type checked and does not evaluate arguments, but simply takes the string passed to the macro and replace each occurence of macro argument in the text of macro with actual string for that parameter. It may give you sometimes very surpising results. For instance macro:
   #define  MAX(x,y)  ((x)>(y)?(x):(y))
when called like this:
   y = 2;
   x = 2;
   x = MAX(++x, y);
will return 4! not 3, because in the code the macro is expanded to:
   x = ((++x)>(y)?(++x):(y))

If you define inline function for that purpose the result will be 3.
   inline int Max(int x, int y)
   {
      return x>y?x:y;
   }
There are other differences too.  For example, macro's do not obey scope properly, so if you declare a MAX() in a namespace or in a class, the macro will replace invokations of this MAX.  This either results in a compiler error incorrect behavrior.

There is nearly no justifiable reason to use macros in C++.  Any use of macros is risky, as PMazur's example and mine both illistrate.  C++ provides alternatives, like inline functions and templates.  These can replace nearly every use of macros, but will be safer and more powerful.

Do yourself a favor, forget macros exist.
There is another, possibly more significant difference. The (pre) compiler will replace every occurrence of the macri name with the defined tokenstring. IOW suppose you defined:

#define max(a,b) ((a)>(b)?(a):(b))

every occurence of max() will be replaced with (or attempted to be replaced with) the macro definition.
ASKER CERTIFIED SOLUTION
Avatar of sunnycoder
sunnycoder
Flag of India image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of wayside
wayside

Unlike a function, macros don't have to be compileable units.

For example:

#define START_LOOP (x,y) while ((x) < (y)) {

#define END_LOOP  }

Then I can do

int a=5, b=10;

START_LOOP(a,b)
// do fun stuff here
myfunc(a);
END_LOOP

This is a trivial example, but START_LOOP could be a very complicated expression.

A more sophisticated example:

#if !defined(_DEBUG) && !defined(DEBUG)
#define HANDLE_ERROR_BEGIN  int checkpoint = 0; try {
#define HANDLE_ERROR_END(x) } catch (Error* e) { \
                                            e->Push(checkpoint, x); \
                                            throw(e); } \
                               catch (...) { Error* e = new Error; \
                                            e->Push(checkpoint, x); \
                                            throw(e); }
#else
#define HANDLE_ERROR_BEGIN int checkpoint = 0;
#define HANDLE_ERROR_END(x)
#endif

You would use this like this:

int myfunc()
{
  HANDLE_ERROR_BEGIN
  int a;
  myotherfunc(a);
  // etc

 HANDLE_ERROR_END("int myfunc")
}

This allows me to trap errors in release mode, but it debug it doesn't trap them so I can debug it.

Final example -if you do any MFC programming, take a look at the definitions of BEGIN_MESSAGE_MAP/END_MESSAGE_MAP.
One big difference:  If you have a bunch of code in a macro and you are debugging your program and you come into a function like:

CS_RETCODE inv_rpc(VOIDPTR UNUSED(ptr), DBPROCESS *dbproc,
                char *rpc_name, DB_RPC_PARAM *rpc_params, ...)
{
    LOCAL_VARIABLE_DECLARATIONS
    SET_RETRY_CONFIG_VARIABLES
    for (counter = 0; counter < rpc_retry_count; counter++) {
        SET_UP_QUERY
        while (dbresults(dbproc) != NO_MORE_RESULTS)
            dbcanquery(dbproc);
        PROCESS_RETURN_INFO
    }
    CHECK_RETURN_STATUS
    return CS_SUCCEED;
}

The debugger can't see inside the macros because it isn't actual code in the executable so it jumps over it and then how do you know that the code is doing the right thing?

Not being able to debug inside macros is the very worst thing to have happen when you have a service level agreement guaranteeing less than 14 minutes of downtime a month (honestly, that's what my SLA says!)

That code above is right out of actual production code that I have to support.  Here is what one of those macros actually looks like.  I just have one question - would you not want the ability to debug into this code?

#define PROCESS_RETURN_INFO \
        if ((ret_param_num = dbnumrets(dbproc)) > 0) { \
            va_start(param_list, rpc_params); \
            for (param_ptr = rpc_params;  \
                    param_ptr->paramname != END_PARAM_TABLE; param_ptr++) { \
                param_val = va_arg(param_list, CS_BYTE *); \
                if (param_ptr->status == (CS_BYTE) DBRPCRETURN) { \
                    for (i=1; i <= ret_param_num; i++) { \
                        if (strcmp(dbretname(dbproc, (int) i),  \
                                param_ptr->paramname) == 0) { \
                            return_val = dbretdata(dbproc, (int) i); \
                            len = dbretlen(dbproc, (int) i); \
                            for (j = 0; j < len; j++) \
                                param_val[j] = return_val[j]; \
                            if (param_ptr->maxlen != -1) \
                                param_val[j] = '\0'; \
                            if (param_ptr->type == SYBCHAR) { \
                                while (param_val[--j] == ' ' && j >= 0) \
                                    param_val[j] = '\0'; \
                            } \
                            break; \
                        } \
                    } \
                } \
            } \
            va_end(param_list); \
        } \
 \
        if (dbhasretstat(dbproc)) { \
            ret_stat = dbretstatus(dbproc); \
            switch (ret_stat) { \
                case PROC_SUCCESS_STATUS: \
                    return CS_SUCCEED; \
 \
                case PROC_DEADLOCK_VICTIM: \
                    ASC_event("SYS_WARN", __LINE__, __FILE__, \
                        "Warning! RPC DeadLock Victim, Ret Stat %d, Retry %d", \
                         ret_stat, counter); \
                    ASC_sleep(rpc_retry_sleep); \
                    continue; \
 \
                case PROC_RESOURCE_ERR: \
                    ASC_event("RPCSPACE", __LINE__, __FILE__,  \
                        "Critical Resource Error; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_MISSING_OBJECT: \
                    ASC_event("RPC_ERR", __LINE__, __FILE__,  \
                      "Error: Missing RPC Object; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_DATATYPE_ERR: \
                    ASC_event("RPC_ERR", __LINE__, __FILE__,  \
                        "RPC Datatype Error; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_PERMISSION_ERR: \
                    ASC_event("RPC_ERR", __LINE__, __FILE__,  \
                        "RPC Permission Error; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_SYNTAX_ERR: \
                    ASC_event("RPC_ERR", __LINE__, __FILE__,  \
                        "RPC Syntax Error; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_MISC_ERR: \
                    ASC_event("RPC_ERR", __LINE__, __FILE__,  \
                        "Misc User RPC Error; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_NON_FATAL_INT_ERR: \
                    ASC_event("RPC_ERR", __LINE__, __FILE__,  \
                      "Non Fatal Internal Error; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_SYS_LIMIT_ERR: \
                    ASC_event("RPCSPACE", __LINE__, __FILE__,  \
                        "System Limit Reached; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_FATAL_INCON1: \
                case PROC_FATAL_INCON2: \
                    ASC_event("RPCSPACE", __LINE__, __FILE__,  \
                     "Error: Fatal Inconsistency; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_CORRUPT_TBL_IDX: \
                    ASC_event("RPCSPACE", __LINE__, __FILE__,  \
                      "RPC - Corrupt Table/Index; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_CORRUPT_DATABASE: \
                    ASC_event("RPCSPACE", __LINE__, __FILE__,  \
                        "RPC - Corrupt DataBase; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                case PROC_HARDWARE_ERR: \
                    ASC_event("RPCSPACE", __LINE__, __FILE__,  \
                        "RPC - Hardware Error; Sleeping %d Mins, Retry %d", \
                         rpc_error_sleep/60, counter); \
                    ASC_sleep(rpc_error_sleep); \
                    continue; \
 \
                default: \
                    ASC_event("RPC_ERR", __LINE__, __FILE__,  \
                      "Error: Unknown RPC Error [%d] - Failing RPC",ret_stat); \
                    return CS_FAIL; \
            } \
        } \
        else \
            return CS_SUCCEED;

#define SET_UP_QUERY \
 \
        ASC_diag(NULL, KERNEL_LEVEL, "Invoke RPC", __LINE__, __FILE__, \
            "Invoking RPC %s, using DBPROC @ %08X", rpc_name, dbproc); \
 \
        start = ASC_cur_tm(); \
        dbrpcinit(dbproc, rpc_name, (CS_SMALLINT)0); \
        if (rpc_params != NULL) { \
            va_start(param_list, rpc_params); \
            for (param_ptr = rpc_params; \
                    param_ptr->paramname!= END_PARAM_TABLE; param_ptr++) { \
                param_val = va_arg(param_list, CS_BYTE *);  \
                if (param_ptr->datalen == 1) \
                    len = 1; \
                else \
                    len = (param_ptr->type == SYBCHAR)?  \
                        strlen((char *) param_val): param_ptr->datalen; \
                if (dbrpcparam(dbproc, param_ptr->paramname, \
                        param_ptr->status, param_ptr->type, param_ptr->maxlen, \
                        len, param_val) == CS_FAIL) { \
                    ASC_event("RPC_ERR", __LINE__, __FILE__, \
                        "Error: %s dbrpcparam(%s) Failed",  \
                         rpc_name, param_ptr->paramname); \
                    return CS_FAIL; \
                } \
            } \
            va_end(param_list); \
        } \
        if (dbrpcsend(dbproc) == CS_FAIL) { \
            ASC_event("RPC_ERR", __LINE__, __FILE__, \
                "Error: dbrpcsend(%s) Failed", rpc_name); \
            return CS_FAIL; \
        } \
        if (dbsqlok(dbproc) == CS_FAIL) { \
            ASC_event("RPC_ERR", __LINE__, __FILE__, \
               "Error: dbsqlok(%s) Failed", rpc_name); \
            return CS_FAIL; \
        } \
        end = ASC_cur_tm(); \
        if (strcmp(rpc_name, "kick_start") != 0) { \
            ASC_diag(NULL, LOW_LEVEL, "Invoke RPC", __LINE__, __FILE__,  \
               "Success: dbsqlok (%s) succeeded in %.0fms",  \
                rpc_name, MS_DIFF(start, end)); \
        }

Worse yet, these aren't the only ones - there are a whole bunch of these things!  And don't tell me to change the code.  I support the app - I do not enhance it...

Jerry
jerry_jeremiah,

For that much long code, you have little options left ;o) ... you have to use macros ... most compilers will not make that big a code as inline even if you specify them as inline.

Some compilers can let you see into macros (e.g. gdb with code compiled at 3rd level using gcc) ... however I am not sure if it allows stepping through that big a macro... so capacity is quite limited indeed
Faced with debugging stuff like jerry_jeremiah's, I'd be tempted to set up my build environment to preprocess first, then build the preprocessed files. Then you could debug them...

That's definitely macro abuse. Exapanding every function out to a couple hundred lines of code must lead to incredible code bloat.
Avatar of soodsandeep

ASKER

Thanks for all the replies.
but one by Sunnycoder suited me best.
SunnyCoder, Please as you have written "..Macros are faster.."
But why macros are faster than functions(inline).


Thanks TO all again.
I would like to stress that they are just marginally faster .... My source of information had derived this conclusion emperically ...

C++ faq offers some information
http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.3