Quantcast
Channel: Анал со зрелой
Viewing all articles
Browse latest Browse all 47

#ifdef vs #if and if

$
0
0

Compile-time settings and whatnot often make use of #define#ifdef, and #if. I would like to make the case that using none of these is the best option, and plain old if-statements ought to be used most of the time.

Here’s a typical example:

#ifdef DEBUG
for ( int i = 0; i < page_count; ++i )
{
	Page* page = pages + i;

	if ( ptr >= page->memory || ptr < page->memory )
	{
		Node* next = page->freelist;

		while ( next )
			if ( next == ptr )
				ASSERT( "double free detected" );
	}
}
#endif

Some debug code runs inside of a paged allocator to try and detect and report double free. This code can be quite slow, so we only want it to run in debug mode.

How would the use of an if statement be better in this situation compared to #ifdef DEBUG? The problem is that the DEBUG define is assumed to only exist in debug builds. Once the DEBUG define is run through the preprocessor it usually just disappears, as it’s often defined as nothing. Code can not always safely assume the define exists, and the #syntax must be used.

If instead we redefine DEBUG like so:

// 1 for debug builds, 0 for other
#define DEBUG 1

Any code can use integer literals as apart of any code. This includes if statements, function calls, templates, or what have you. We can re-write the first example to:

if ( DEBUG )
{
	for ( int i = 0; i < page_count; ++i )
	{
		Page* page = pages + i;

		if ( ptr >= page->memory || ptr < page->memory )
		{
			Node* next = page->freelist;

			while ( next )
				if ( next == ptr )
					ASSERT( "double free detected" );
		}
	}
}

Compilers will strip away the first if statement resulting in the same run-time code as the first example. The else keyword can also be used after this if statement. If in the future, for any reason, it becomes a necessity to turn debug features on or off at run-time the DEBUG macro can be swapped out for some kind of global watch variable — suddenly the DEBUG macro becomes more immune to code maturation. An example:

// An fake example function
void DoThings( char* input, int do_verification )
{
	if ( do_verification )
		Verify( input );

	// ...
}

// Used like:
DoThings( input, DEBUG );

Since the macro becomes a literal a mixture of both compile-time and run-time checks can be used freely as desired. An if statement can check the compile-time literal, and also check for run-time pointers at the same time. No #ifdef necessary. The #ifdef takes up an entire line in the source file, and is often (in some code editors) automatically shifted all the way to the left of the text file. These quirks can be avoided by not using #ifdef at all.

Here’s an example of single line for each #directive, vs using plain C code:

#ifdef DEBUG
	SetIterationCount( 10 );
#else
	SetIterationCount( 5 );
#endif

In the above example the difference in indentation can be visually jarring, and it’s fairly annoying to re-type the same thing multiple times in multiple places. Instead we can do something more like:

SetIterationCount( DEBUG ? 10 : 5 );

Using a literal can also play a bit nicer with MSVC’s intellisense. This can be useful for inspecting compile-time constants and debugging in general.

TwitterRedditFacebookShare


Viewing all articles
Browse latest Browse all 47

Trending Articles