I suggest you start with the GCC Macros documentation , which contains some pretty interesting information about the CCC implementation of the CCC preprocessor.
The glue bridges in his answer give several examples of using the C preprocessor. One example of an ordering language is interesting in a number of examples. The author of the Order raises one problem that he / she faced, C Preprocessor implementations may not fully implement more modern standards.
In general, using the C C preprocessor to create some sort of underlined language, such as what Steve Bourne did when writing the Bourne Shell for Unix, I would consider suitable grounds for execution, and then several water landing sessions.
The main thing to remember about the C preprocessor is that it manipulates text tokens. Thus, the C preprocessor will allow you to tinker with the syntax quite a bit. For example, the following macro, which compiles with Visual Studio 2005 without errors, shows the impossibility of using non-intuitive text.
However, you need to understand and get around some of the limitations of the C preprocessor when pushing borders. See GCC Macro Pitfalls topics for some of the topics at hand.
And you can use the C preprocessor as a general macro and text preprocessor that targets some tool other than the C compiler. For example, the older imake program used C preprocessor to automate the assembly to provide an extensive macro tool.
Where I saw that the C-preprocessor used most efficiently was to simplify complex code and declarations.
In one case that I saw, the C preprocessor was used to provide the state machine language, which was used to create data structures and data to describe the state machine. The resulting data structures were then used as an argument for the state machine function. This allowed us to write down several different state machine procedures in the C preprocessor language with the state machine processing performed using one function.
Microsoft, in its Microsoft Foundation Classes (MFC), used the C preprocessor to hide quite a bit of the details of MFC messaging. Once you get used to it, it's easy enough to read something like the following. Since the Visual Studio development environment had tools for generating and modifying code using macros, it was quite simple for the programmer.
BEGIN_MESSAGE_MAP(CFrameworkWndDoc, CWindowDocument)
Especially when you see how macros look:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \ PTM_WARNING_DISABLE \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return GetThisMessageMap(); } \ const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \ { \ typedef theClass ThisClass; \ typedef baseClass TheBaseClass; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ { #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ static const AFX_MSGMAP messageMap = \ { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ return &messageMap; \ } \ PTM_WARNING_RESTORE // for Windows messages #define ON_MESSAGE(message, memberFxn) \ { message, 0, 0, 0, AfxSig_lwl, \ (AFX_PMSG)(AFX_PMSGW) \ (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \ (memberFxn)) }, #define ON_WM_TIMER() \ { WM_TIMER, 0, 0, 0, AfxSig_vw, \ (AFX_PMSG)(AFX_PMSGW) \ (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT_PTR) > ( &ThisClass :: OnTimer)) },