The preprocessor is a "textual" pass through the source code. For example, you've seen something like:
#define MAXARRAYSIZE 100
If you have an expression in your code that reads:
for (i = 0; i < MAXARRAYSIZE; i++) {
when the preprocessor finishes, it's as though you wrote:
for (i = 0; i < 100; i++) {
The advantage, of course, is that you only need to change the #define to everywhere change the affected value in the source code. The preprocessor also reads function prototypes, like:
int myFunction(int val, char *buff);
and constructs an attribute list much like a symbol table that allows it to check all subsequent uses of that function for the correct argument list (i.e., an int and a pointer) and that the return value is used as an int. If also expands any macros you may have written, like:
#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
so when you write:
for (i = 0; i < ARRAYSIZE(buff) ; i++) {
the preprocessor expands it to:
for (i = 0; i < (sizeof(buff) / sizeof(buff[0])) ; i++) {
I think it helps to simply think of the preprocessor as performing textual replacements, macro expansions, and type checking via prototypes.