3

typically #define would be used to define a constant or a macro. However it is valid code to use #define in the following way.

#define MAX // does this do anything?
#define MAX 10 // I know how to treat this.

So, if I #define MAX 10, I know my pre-processor replaces all instances of MAX with 10. If someone uses #define MAX by itself however with no following replacement value, it's valid. Does this actually DO anything?

My reason for asking is that I am writing a compiler for c in c++ and handling preprocessor directives is required but I haven't been able to find out if there is any functionality I need to have when this occurs or if I just ignore this once my preprocess is done.

My first instinct is that this will create a symbol in my symbol table with no value named MAX, but it is equally possible it will do nothing.

As an add in question which is kind of bad form I know, but I'm really curious. Are there situations in real code where something like this would be used?

Thanks, Binx

Justin
  • 203
  • 1
  • 13
  • Just to clarify, I want to know how #define SOMETHING would work completely alone, In my example code I gave it a value directly after so I am aware that the #define MAX is basically doing nothing in that case. – Justin Feb 09 '16 at 20:10
  • If you define something, it gets defined. So if you check later whether it defined or not, you can find if it defined. That's the point – Lol4t0 Feb 09 '16 at 20:10
  • @lol4t0 I don't see the purpose of that, isn't it just treated the same as an identifier? – Justin Feb 09 '16 at 20:12
  • Nope. Defined names are preprocessor names. Preprocessor has its own scope and only knows/can check names in that scope – Lol4t0 Feb 09 '16 at 20:14
  • You are insane: "My reason for asking is that I am writing a compiler for c in c++ and handling preprocessor directives". You need other resources, stackoverflow is no good for that (unless you find another insane person). –  Feb 09 '16 at 20:22
  • You'll probably want to read through section 6.10 of the C standard carefully. The preprocessor has some subtleties that aren't immediately obvious. Better still (at least for a first cut at things) start by using Boost Wave, and probably spend some time studying its operation before embarking on your own. – Jerry Coffin Feb 09 '16 at 20:22
  • @DieterLücking I definitely AM using plenty of other resources, I just couldn't find the answer I was looking for by just reading other things about processor directives. I read a bunch of stuff for roughly 2 hours before I decided I would come here and ask for help. Currently I have my lexical analysis done, I now have my preprocessing done except for one small error and next up is going to be some sort of parser, not sure if I am going to use recursive decent or something else yet. Need to do more research to see whats going to be the simplist. Thanks for the help =D – Justin Feb 09 '16 at 20:48
  • 2 hours is _nothing_! – Lightness Races in Orbit Feb 10 '16 at 00:26
  • I think 2 hours is quite a while for a question as simple as this one – Justin Feb 12 '16 at 22:38

5 Answers5

10

It creates a symbol with a blank definition, which can later be used in other preprocessor operations. There are a few things it can be used for:

1) Branching.

Consider the following:

#define ARBITRARY_SYMBOL

// ...

#ifdef ARBITRARY_SYMBOL
    someCode();
#else   /* ARBITRARY_SYMBOL */
    someOtherCode();
#endif  /* ARBITRARY_SYMBOL */

The existence of a symbol can be used to branch, selectively choosing the proper code for the situation. A good use of this is handling platform-specific equivalent code:

#if defined(_WIN32) || defined(_WIN64)
    windowsCode();
#elif defined(__unix__)
    unixCode();
#endif /* platform branching */

This can also be used to dummy code out, based on the situation. For example, if you want to have a function that only exists while debugging, you might have something like this:

#ifdef DEBUG
    return_type function(parameter_list) {
        function_body;
    }
#endif  /* DEBUG */

1A) Header guards.

Building on the above, header guards are a means of dummying out an entire header if it's already included in a project that spans multiple source files.

#ifndef HEADER_GUARD
#define HEADER_GUARD

// Header...

#endif  /* HEADER_GUARD */

2) Dummying out a symbol.

You can also use defines with blank definitions to dummy out a symbol, when combined with branching. Consider the following:

#ifdef _WIN32
    #define STDCALL __stdcall
    #define CDECL __cdecl
    // etc.
#elif defined(__unix__)
    #define STDCALL
    #define CDECL
#endif  /* platform-specific */

// ...

void CDECL cdeclFunc(int, int, char, const std::string&, bool);
// Compiles as void __cdecl cdeclFunc(/* args */) on Windows.
// Compiles as void cdeclFunc(/* args */) on *nix.

Doing something like this allows you to write platform-independent code, but with the ability to specify the calling convention on Windows platforms. [Note that the header windef.h does this, defining CDECL, PASCAL, and WINAPI as blank symbols on platforms that don't support them.] This can also be used in other situations, whenever you need a preprocessor symbol to only expand to something else under certain conditions.

3) Documentation.

Blank macros can also be used to document code, since the preprocessor can strip them out. Microsoft is fond of this approach, using it in windef.h for the IN and OUT symbols often seen in Windows function prototypes.

There are likely other uses as well, but those are the only ones I can think of off the top of my head.

9

A typical example are header guards:

#ifndef MYHEADER
#define MYHEADER
...
#endif

You can test if something is defined with #ifdef / ifndef.

alain
  • 11,939
  • 2
  • 31
  • 51
2

It doesn't "do" anything in the sense that it will not add anything to a line of code

#define MAX

int x = 1 + 2; MAX // here MAX does nothing

but what an empty define does is allow you to conditionally do certain things like

#ifdef DEBUG
  // do thing
#endif

Similarly header guards use the existance of a macro to indicate if a file has already been included in a translation unit or not.

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
1

#define does a character-for-character replacement. If you give no value, then the identifier is replaced by...nothing. Now this may seem strange. We often use this just to create an identifier whose existence can be checked with #ifdef or #ifndef. The most common use is in what are called "inclusion guards".

In your own preprocessor implementation, I see no reason to treat this as a special case. The behavior is the same as any other #define statement:

  1. Add a symbol/value pair to the symbol table.

  2. Whenever there is an occurrence of the symbol, replace it with its value.

Most likely, step 2 will never occur for a symbol with no value. However, if it does, the symbol is simply removed since its value is empty.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
1

The C Preprocessor (CPP) creates a definitions table for all variables defined with the #define macro. As the CPP passes through the code, it does at least two things with this information.

First, it does a token replacement for the defined macro.

#define MAX(a,b)  (a > b) ? (a) : (b)
MAX(1,2); // becomes (1 > 2) ? (1) : (2);

Second, it allows for those definitions to be searched for with other preprocessor macros such as #ifdef, #ifndef, #undef, or CPP extensions like #if defined(MACRO_NAME).

This allows for flexibility in using macro definitions in those cases when the value is not important, but the fact that a token is defined is important.

This allows for code like the following:

// DEBUG is never defined, so this code would
// get excluded when it reaches the compiler.

#ifdef DEBUG
// ... debug printing statements
#endif
callyalater
  • 3,102
  • 8
  • 20
  • 27