www.digitalmars.com [Home] [Search] [D]

Last update Sep 12, 2002


The C Preprocessor Versus D

Back when C was invented, compiler technology was primitive. Installing a text macro preprocessor onto the front end was a straightforward and easy way to add many powerful features. The increasing size & complexity of programs have illustrated that these features come with many inherent problems. D doesn't have a preprocessor; but D provides a more scalable means to solve the same problems.

Header Files

The C Preprocessor Way

C and C++ rely heavilly on textual inclusion of header files. This frequently results in the compiler having to recompile tens of thousands of lines of code over and over again for every source file, an obvious source of slow compile times. What header files are normally used for is more appropriately done doing a symbolic, rather than textual, insertion. This is done with the import statement. Symbolic inclusion means the compiler just loads an already compiled symbol table. The needs for macro "wrappers" to prevent multiple #inclusion, funky #pragma once syntax, and incomprehensible fragile syntax for precompiled headers are simply unnecessary and irrelevant to D.
	#include <stdio.h>
	

The D Way

D uses symbolic imports:
	import stdio;
	

#pragma once

The C Preprocessor Way

C header files frequently need to be protected against being #include'd multiple times. To do it, a header file will contain the line:
	#pragma once
	
or the more portable:
	#ifndef __STDIO_INCLUDE
	#define __STDIO_INCLUDE
	... header file contents
	#endif
	

The D Way

Completely unnecessary since D does a symbolic include of import files; they only get imported once no matter how many times the import declaration appears.

#pragma pack

The C Preprocessor Way

This is used in C to adjust the alignment for structs.

The D Way

For D classes, there is no need to adjust the alignment (in fact, the compiler is free to rearrange the data fields to get the optimum layout, much as the compiler will rearrange local variables on the stack frame). For D structs that get mapped onto externally defined data structures, there is a need, and it is handled with:
	struct Foo
	{
		align (4):	// use 4 byte alignment
		...
	}
	

Macros

Preprocessor macros add powerful features and flexibility to C. But they have a downside: Here's an enumeration of the common uses for macros, and the corresponding feature in D:

  1. Defining literal constants:

    The C Preprocessor Way

    	#define VALUE	5
    	

    The D Way

    	const int VALUE = 5;
    	
  2. Creating a list of values or flags:

    The C Preprocessor Way

    	int flags:
    	#define FLAG_X	0x1
    	#define FLAG_Y	0x2
    	#define FLAG_Z	0x4
    	...
    	flags |= FLAGS_X;
    	

    The D Way

    	enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
    	FLAGS flags;
    	...
    	flags |= FLAGS.X;
    	
  3. Distinguishing between ascii chars and wchar chars:

    The C Preprocessor Way

    	#if UNICODE
    	    #define dchar	wchar_t
    	    #define TEXT(s)	L##s
    	#else
    	    #define dchar	char
    	    #define TEXT(s)	s
    	#endif
    
    	...
    	dchar h[] = TEXT("hello");
    	

    The D Way

    	import dchar;		// contains appropriate typedef for dchar
    	...
    	dchar[] h = "hello";
    	
    D's optimizer will inline the function, and will do the conversion of the string constant at compile time.

  4. Supporting legacy compilers:

    The C Preprocessor Way

    	#if PROTOTYPES
    	#define P(p)	p
    	#else
    	#define P(p)	()
    	#endif
    	int func P((int x, int y));
    	

    The D Way

    By making the D compiler open source, it will largely avoid the problem of syntactical backwards compatibility.
  5. Type aliasing:

    The C Preprocessor Way

    	#define INT 	int
    	

    The D Way

    	alias int INT;
    	
  6. Using one header file for both declaration and definition:

    The C Preprocessor Way

    	#define EXTERN extern
    	#include "declations.h"
    	#undef EXTERN
    	#define EXTERN
    	#include "declations.h"
    	
    In declarations.h:
    	EXTERN int foo;
    	

    The D Way

    The declaration and the definition are the same, so there is no need to muck with the storage class to generate both a declaration and a definition from the same source.
  7. Lightweight inline functions:

    The C Preprocessor Way

    	#define X(i)	((i) = (i) / 3)
    	

    The D Way

    	int X(int i) { return i = i / 3; }
    	
    The compiler optimizer will inline it; no efficiency is lost.
  8. Assert function file and line number information:

    The C Preprocessor Way

    	#define assert(e)	((e) || _assert(__LINE__, __FILE__))
    	

    The D Way

    assert() is a built-in expression primitive. Giving the compiler such knowledge of assert() also enables the optimizer to know about things like the _assert() function never returns.
  9. Setting function calling conventions:

    The C Preprocessor Way

    	#ifndef _CRTAPI1
    	#define _CRTAPI1 __cdecl
    	#endif
    	#ifndef _CRTAPI2
    	#define _CRTAPI2 __cdecl
    	#endif
    
    	int _CRTAPI2 func();
    	

    The D Way

    Calling conventions can be specified in blocks, so there's no need to change it for every function:
    	extern (Windows)
    	{
    	    int onefunc();
    	    int anotherfunc();
    	}
    	
  10. Hiding __near or __far pointer wierdness:

    The C Preprocessor Way

    	#define LPSTR	char FAR *
    	

    The D Way

    D doesn't support 16 bit code, mixed pointer sizes, and different kinds of pointers, and so the problem is just irrelevant.
  11. Simple generic programming:

    The C Preprocessor Way

    Selecting which function to use based on text substitution:
    	#ifdef UNICODE
    	int getValueW(wchar_t *p);
    	#define getValue getValueW
    	#else
    	int getValueA(char *p);
    	#define getValue getValueA
    	#endif
    	

    The D Way

    D enables declarations of symbols that are aliases of other symbols:
    	version (UNICODE)
    	{
    	    int getValueW(wchar[] p);
    	    alias getValueW getValue;
    	}
    	else
    	{
    	    int getValueA(char[] p);
    	    alias getValueA getValue;
    	}
    	

Conditional Compilation

The C Preprocessor Way

Conditional compilation is a powerful feature of the C preprocessor, but it has its downside:

The D Way

D supports conditional compilation:
  1. Separating version specific functionality into separate modules.
  2. The debug statement for enabling/disabling debug harnesses, extra printing, etc.
  3. The version statement for dealing with multiple versions of the program generated from a single set of sources.
  4. The if (0) statement.
  5. The /+ +/ nesting comment can be used to comment out blocks of code.

Copyright (c) 1999-2002 by Digital Mars, All Rights Reserved