Scope of macros

08 Aug 2013

Hi all,

It is my understanding that macros are done by the pre-processor, so if you declare a macro in the main() it should be useable in other header files. I have a program which exhibits some strange behaviour that seems to contradict this. I've stripped it down to the bare basics. The program is below:

In main.c:

#include "mbed.h"

//#define USE_RTC

#include "DataLog.h"

DataLog d;

int main() {
    d.StartLogging();    
    while(1) {
        __wfi();
    }
}

In DataLog.h:

#include "mbed.h"

#ifndef DATA_LOG_H
#define DATA_LOG_H

class DataLog
{
    public:
        void StartLogging(void);
        
    protected:
        void Log(void);
        char FileName[256];
#ifndef USE_RTC
        Ticker SampleTimer;
#endif
        void CheckIfDefined(void)
        {   
#ifndef USE_RTC
            fprintf(stderr, "USE_RTC not defined in h\n");
#else
            fprintf(stderr, "USE_RTC is defined in h\n");
#endif
        }
};
#endif

In DataLog.cpp

#include "mbed.h"
#include "DataLog.h"

void DataLog::StartLogging(void)
{
#ifndef USE_RTC
    SampleTimer.attach(this, &DataLog::Log, 1);
    fprintf(stderr, "USE_RTC not defined in cpp\n");
#else
    fprintf(stderr, "USE_RTC is defined in cpp\n");
#endif
    CheckIfDefined();
};

void DataLog::Log(void)
{   
    fprintf(stderr, "Logging\n");
};

When USE_RTC isn't defined in the main, the program writes the following to the terminal:

Quote:

USE_RTC not defined in cpp

USE_RTC not defined in h

and then starts calling DataLog::Log() every second. This is what I would expect to see. If however, USE_RTC is defined in main (comments removed), the program writes the same message to the terminal, i.e.:

Quote:

USE_RTC not defined in cpp

USE_RTC not defined in h

but doesn't start logging. So it is doing the fprintfs like USE_RTC is not defined, but behaving like USE_RTC is defined.

This is adding highly unpredictable behaviour to the code. For example, if you comment out the definition of FileName and run the code with USE_RTC defined, nothing gets written to the terminal; the program just hangs.

Please could someone explain what is going on?

Thanks Tim

08 Aug 2013

Hello Tim,

a scope of preprocessor directives stays within one translation unit. It does not even obey C/C++ scopes, a preprocesor goes from the top to the bottom.

If USE_RTC is not defined, the behavior is obvious. The second case when you define USE_RTC in main code file, means that that define is known only in that code file and all it includes. Here's an output from preprocessor (short version of it):

using namespace mbed;
using namespace std;

# 2 "main.cpp"
  
# 1 "DataLog.h"
# 2 "DataLog.h"

class DataLog
{
    public:
        void StartLogging(void);

    protected:
        void Log(void);
        char FileName[256];

        void CheckIfDefined(void)
        {
            fprintf((&std:: __stderr), "USE_RTC is defined in h\n");
        }
};
# 6 "main.cpp"
 
DataLog d;
 
int main() {
    d.StartLogging();    
    while(1) {

    }
}

datalog preprocessor output:

# 2 "DataLog.cpp"
# 1 "DataLog.h"
# 2 "DataLog.h"

class DataLog
{
    public:
        void StartLogging(void);
    protected:
        void Log(void);
        char FileName[256];
        Ticker SampleTimer;
        void CheckIfDefined(void)
        {
           fprintf((&std:: __stderr), "USE_RTC not defined in h\n");
        }
};
# 3 "DataLog.cpp"

void DataLog::StartLogging(void)
{
    SampleTimer.attach(this, &DataLog::Log, 1);
    fprintf((&std:: __stderr), "USE_RTC not defined in cpp\n");
    CheckIfDefined();
};

void DataLog::Log(void)
{
    fprintf((&std:: __stderr), "Logging\n");
};

That proves that in main it's defined and everything what includes but not used there. Small addition, what I noticed, you include mbed everywhere. Be aware of header circular dependency problem and that if you can forward declare something to avoid including entire header file, do it . that's just small advice which might come handy later when your project increase in size .

Regards,
0xc0170

09 Aug 2013

Hi Martin,

Thanks for the explanation. If I follow correctly, when main is pre-compiled it inserts the class structure for DataLog, and because USE_RTC is in scope it will allocate memory according to the class structure with USE_RTC defined; then when DataLog is pre-processed, USE_RTC is out of scope so the address tables etc are compiled according to the class structure with USE_RTC not defined.

I hadn't realised how dangerous macros were! I will try to avoid them in the future.

Thanks for your help.

Tim