I suggest you start with the GCC Macros documentation which provides quite a bit of interesting information about the GCC implementation of the C Preprocessor.
Clay Bridges in his answer provides a couple of examples of using the C Preprocessor. The one about the Order language is interesting with a number of examples. The author of Order does bring up one issue that he/she ran into, C Preprocessor implementations may not fully implement more recent standards.
In general using the C Preprocessor to develop some kind of a bastardized language such as what Steve Bourne did when writing the Bourne Shell for Unix is an activity that I would consider suitable grounds for rendition followed by multiple water boarding sessions.
The main thing to remember about the C Preprocessor is that it is manipulating text tokens. So the C Preprocessor will allow quite a bit of tinkering with syntax. For instance the following macro, which compiles with Visual Studio 2005 without errors, shows the unintuitive text manipulation possible.
#define TESTOP(a,x,y,op,z) a (x) op (y); z
void f(void)
{
int i = 0, j = 5;
TESTOP( ,i,j,+=, );
TESTOP( ,i,(j + 2),+=, );
TESTOP({,i,(j + 2),+=,});
}
However you do need to understand and work around some of the limitations of the C Preprocessor when pushing the boundaries. See the GCC topic Macro Pitfalls for some of the issues to consider.
And you can use the C Preprocessor as a general macro and text preprocessor that targets some tool other than the C compiler. For instance the older imake utility for build automation used the C Preprocessor to provide an extensive macro facility.
Where I have seen the C Preprocessor used most effectively was in simplifying complex code and declarations.
One case that I have seen was using the C Preprocessor to provide a state machine language which was used to create the data structures and data to describe a state machine. The resulting data structures were then used as an argument to a state machine function. This allowed multiple different state machine procedures to be written in the C Preprocessor defined language with the state machine processing done by a single function.
Microsoft, in their Microsoft Foundation Classes (MFC), used the C Preprocessor to hide quite a few of the messaging details of MFC. Once you get used to it something like the following is reasonably easy to read. Since the Visual Studio IDE had tools to generate and modify the code using the macros it was pretty straight forward for the programmer.
BEGIN_MESSAGE_MAP(CFrameworkWndDoc, CWindowDocument)
//{{AFX_MSG_MAP(CFrameworkWndDoc)
ON_WM_CHAR()
ON_WM_TIMER()
ON_MESSAGE(WU_EVS_DFLT_LOAD, OnDefaultWinLoad)
ON_MESSAGE(WU_EVS_POPUP_WINDOW, OnPopupWindowByName)
ON_MESSAGE(WU_EVS_POPDOWN_WINDOW, OnPopdownWindowByName)
ON_MESSAGE(WM_APP_CONNENGINE_MSG_RCVD, OnConnEngineMsgRcvd)
ON_MESSAGE(WM_APP_XMLMSG_MSG_RCVD, OnXmlMsgRcvd)
ON_MESSAGE(WM_APP_BIOMETRIC_MSG_RCVD, OnBiometricMsgRcvd)
ON_MESSAGE(WM_APP_SHUTDOWN_MSG, OnShutdownMsgRcvd)
ON_MESSAGE(WM_POWERBROADCAST, OnPowerMsgRcvd)
ON_MESSAGE(WM_APP_SHOW_HIDE_GROUP, OnShowHideGroupMsgRcvd)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
Especially when you see what the macros used look like:
#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)) },