[C language] Detailed preprocessing

Hits: 0

Precompiled

1. The translation environment and execution environment of the program

1.1 Translation environment

Every time C language runs a program, an executable program .exe file will appear, so how does this change from a .c file to an .exe file?

1️⃣ All source files (the header files will be copied to the source files) are converted into object files (.obj) by the compiler
2️⃣ The object files are combined by the linker to form an executable program

The compilation process handled by the compiler here can also be divided into three parts:

1.2 [Preprocessing] -> Compile -> Assemble -> Link

Preprocessing:

1️⃣ Include header file
2️⃣ Macro replacement
3️⃣ Go to comment
test.c -> test.i

Compile:

1️⃣ Converted C language code into assembly code
2️⃣ Performed syntax analysis, lexical analysis, semantic analysis, symbol summary (global symbols such as main function, function name)
test.i -> test.s

compilation:

1️⃣ Convert assembly code into binary instructions
2️⃣ Form symbol table (function name plus address)
test.s -> test.o

Link:

1️⃣ Merge the segment table (merge the same content into one area)
2️⃣ Merge and relocate the symbol table (the address at the declaration is meaningless, the merge selection is meaningful)
test.o -> text.exe

1.3 Operating Environment

The process of program execution:

1) The program mustloaded into memory. In an environment with an operating system: Usually this is done by the operating system. In a stand-alone environment, the loading of programs must be arranged manually, possibly by placing executable code into read-only memory.
2) The execution of the program starts. Then the main function is called.
3) Start executing the program code. At this point the program will use a runtime stack (stack) to store the function’s local variables and return addresses. Programs can also use static memory. Variables stored in static memory retain their values ​​throughout the execution of the program.
4) Terminate the program. Terminates the main function normally; it may also terminate unexpectedly.

2. Detailed preprocessing

2.1 Predefined symbols

The C language provides some symbols that can be used directly:

__FILE__   //The source file to be compiled 
__LINE__   //The current line number of the file 
__DATE__   //The date the file was compiled 
__TIME__   //The time the file was compiled 
__STDC__   //If the compiler follows ANSI C, its value is 1, otherwise undefined

int main()
{
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        return EXIT_FAILURE;
    }
    for (int i = 0; i < 5; i++)
    {
        fprintf(pf, "file:%s line:%d date:%s time:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);
    }
    fclose(pf);
    pf = NULL;
    return 0;
}

2.2 #define

grammar:

#define name stuff

#define MAX 100
#define STR "abc"

int main()
{
    printf("%d %s", MAX, STR);
    return 0;
}

After preprocessing:

int main()
{
    printf("%d %s", 100, "abc");
    return 0;
}

Do not add ; after #define
Continuity:

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
             date:%s\ttime:%s\n" ,\
             __FILE__,__LINE__ ,    \
             __DATE__,__TIME__ )

2.3.1 #define define macro

#define name( parament-list ) stuff

For example, now implement a square macro:

#define SQUARE( x ) ((x) * (x))

int main()
{
    printf("%d", SQUARE(5));
    return 0;
}

Because macros directly replace code, there may be a problem of symbol priority in some cases, so you should add as many parentheses as possible.

2.3.2 #define substitution rule

There are several steps involved when expanding #define to define symbols and macros in a program.

  1. When the macro is called, the arguments are first checked to see if they contain any symbols defined by #define. If so, they
    are replaced first.
  2. The replacement text is then inserted into the program in place of the original text. For macros, parameter names are replaced by their values.
  3. Finally, the resulting file is scanned again to see if it contains any symbols defined by #define. If so, repeat the above
    process.

Note :
1. Other #define-defined symbols can appear in macro parameters and #define definitions. But with macros, recursion cannot occur.
2. When the preprocessor searches for symbols defined by #define, the contents of string constants are not searched.

2.3.3 # and

#:
write parameters into a string

#define PRINT(n) printf("the value of " #n " is %d\n", n)

int main()
{
    int n = 10;
    PRINT(n);
    return 0;
}

#n will be converted to “n”.

##:
merge symbol

#define CAT(a, b) a##b

int main()
{
    printf("%s\n", CAT("abc", "def"));
    int ABC = 1;
    printf("%d\n", CAT(A, BC));
    return 0;
}

2.3.4 Macros with side effects

#define MAX(a, b) (a) > (b) ? (a) : (b)

int main()
{
    int a = 5;
    int b = 4;
    int m = MAX(a++, b++);
    printf("%d\n", m);
    printf("%d %d", a, b);
    return 0;
}

result:

6
7 5

The original intention is to find a larger value, and this repeated calculation is a side effect.

2.3.5 Macros and functions

Macros and functions to compare sizes:

#define MAX(a, b) a > b ? a : b

int Max(int a, int b)
{
    return (a > b ? a : b);
}

Advantages of macros:

1️⃣ Functions must declare types, but macros do not. Macros are type-independent .
2️⃣ Macros are more efficient than functions , because functions need to create stack frames and pass parameters.

Disadvantages of macros:

1) Every time a macro is used, a code of the macro definition will be inserted into the program. Unless the macro is relatively short, it can significantly increase the length of the program.
2) Macros cannot be debugged.
3) Macros are not rigorous enough because they are type-independent.
4) Macros may cause problems with operator precedence, making procedures prone to errors.

2.3 #undef

This directive is used to remove a macro definition .

#define MAX 100

int main()
{
#undef MAX
    MAX;//error
    return 0;
}

2.4 Conditional compilation

When compiling a program, it is convenient if we want to compile or discard a statement (a group of statements). Because we have conditional compilation directives.

#define __DEBUG__
int main()
{
    int i = 0;
    int arr[10] = { 0 };
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
# ifdef __DEBUG__ 
        printf ( "%d\n" , arr[i]); //In order to see if the array assignment is successful. 
# endif  //__DEBUG__
    }
    return 0;
}

If the condition is met, let printf()it participate in the compilation, and if it is not satisfied, it will not participate in the compilation.

int main()
{
#if 0
    printf("abc");
#endif
    return 0;
}

Conditional compilation for multiple branches:

#define M 3

int main()
{
#if M < 5
    printf("<");
#elif M == 5
    printf("==");
#else
    printf(">");
#endif
    return 0;
}

Determine if it is defined:

#define A 1

int main()
{
#if defined (A)
//#ifdef A
//#if !defined(A)
//#ifndef A
    printf("YES");
#endif
    return 0;
}

2.5 File Inclusions

To prevent files from being included repeatedly:

#ifndef __TEST_H__
#define __TEST_H__
.....
#endif

You may also like...

Leave a Reply

Your email address will not be published.