Skip to content

4. C Preprocessor#

C Preprocessor Overview#

The purpose of the APRE*C/C++ preprocessor is to process C preprocessor directives. The APRE*C/C++ preprocessor can handle most directives, including the #include directive for specifying source files to include, the #define directive for defining macros, and the #if directive for conditionally including source code.

How the C Preprocessor Works#

The APRE*C/C++ preprocessor recognizes most C preprocessor commands, and efficiently performs macro substitutions. It also includes files as required, and includes or excludes source text on the basis of conditions. The APRE*C/C++ preprocessor uses the macro values obtained in the preprocessing step to modify the source text, and then generates an output file.

Example#

The following example illustrates how the APRE*C/C++ preprocessor handles preprocessor directives:

#include "my_header.h"
...
#if A
char name[10];
#endif
...

Assume that the file my_header.h is in the current directory and contains the following directive:

#define A 1

In the above example, the APRE*C/C++ preprocessor first reads my_header.h and remembers the definition of the value A. Therefore, when it subsequently processes the #if condition, it substitutes 1 for A. Because the #if condition is thus true, the declaration of the name array is included in the source file (i.e. the output text). If the #if condition had evaluated to false, the name array declaration would have been excluded from the source file.

C Preprocessor Directives#

The C preprocessor directives that the APRE*C/C++ preprocessor recognizes are #define, #undef, #include, #if, #ifdef, #ifndef, #else, #elif and #endif.

#define, #undef#

#define defines the name of a macro to be used by the APRE*C/C++ preprocessor, while #undef deletes a previously defined macro.

Example#
...
#define A
#define func()
...
#undef A
#undef func

In the above example, the APRE*C/C++ preprocessor handles the #define command and stores the names A and func() in a symbol table, and then performs the required macro substitutions whenever the names A and func() subsequently appear. Finally, when the APRE*C/C++ preprocessor encounters the #undef command, it deletes the stored names from the symbol table.

#include#

This directive instructs the APRE*C/C++ preprocessor to read the specified external source file and incorporate any #define macros and variables found in the external source file.

The portion of the files referenced using #include directives that is actually incorporated in the source text varies depending on how the -parse option is set.

#if#

When the APRE*C/C++ preprocessor encounters the #if directive, it evaluates the given condition and uses the result of the evaluation as the basis on which to determine whether to include the source text between the #if and #endif directives in the precompile operation.

Example#
#define A 1 + 1
#define B A - 2
...
#if B
int var;
#endif
...
#if defined(A)
int var2;
#endif

In the above example, the APRE*C/C++ preprocessor substitutes A-2 for B, and then 1+1 for A, when evaluating the first #if condition. This results in the expression 1+1-2, which evaluates to 0, or false. Therefore, the source text between the first #if and #endif commands is not included in the output.

The second condition, #if defined, is handled in the same way as an #ifdef condition.

#ifdef#

When the APRE*C/C++ preprocessor encounters the #ifdef condition, it determines whether to include the source text between the #ifdef and #endif directives based on whether the name that follows the #ifdef keyword is a defined name.

Example#
#define A
#ifdef A
int var;
#endif
...

In the above example, the text "int var;" is included in the output of the precompile operation because A has been defined.

#ifndef#

If there is a reverse of #ifdef and there is no defined name, the next source code is preprocessed.

Example#
#define A
#ifndef A
int var;
#endif
...

In this example, the text "int var;" is excluded from the output of the precompile operation because A has been defined.

#else#

If all #if, #ifdef and #ifndef conditional clauses are false, the source code following the last #else is preprocessed.

Example#
...
#define A 0
#if A
int var1;
#else
int var2;
#endif
...

In the above example, the source text between the #else and #endif directives will be included in the output of the precompile operation because the #if condition evaluates to false.

#elif#

If this directive is provided between an #if, #ifdef or #ifndef condition and the corresponding #endif directive, and the first condition evaluates to false, then the #elif condition is evaluated, and the source text between the #elif and #endif directives is included in the output of the precompile operation if the #elif condition evaluates to true.

Example#
...
#define A 0
#define B 1
#if A
int var1;
#elif B
int var2;
#else
int var3;
#endif
...

In the above example, the source text between the #elif and #else directives is included in the output of the precompile operation, because the #if condition is not satisfied and the #elif condition is satisfied.

#endif#

This indicates the end of a block of text that is included or excluded based on the result of evaluation of an #if, #ifdef or #ifndef condition.

Limitations on the Use of the Preprocessor#

This section covers a few limitations of the APRE*C/C++ Preprocessor, including some directives that it ignores.

Ignored Directives#

The APRE*C/C++ preprocessor ignores some C preprocessor directives, because they are not necessary for the precompile operation. One example is the #pragma directive, which does not need to be processed by the precompiler because it is used only by the C compiler.

The commands that are ignored by the APRE*C/C++ preprocessor during the precompile operation are as follows:

##

This directive converts a preprocessor macro parameter to a string constant.

###

This directive merges two preprocessor tokens in a macro definition.

#error#

This directive is used to output a compile-time error message.

#pragma#

The #pragma directive is used to pass implementation-dependent information to the C compiler.

#line#

This directive is used to provide line number information to the C compiler.

Limitations on the Use of #define#

The use of the #define directive with the APRE*C/C++ precompiler is limited in one important way: names defined using the #define directive cannot be used within embedded SQL statements.

Example#
#define RESEARCH_DEPT   40
...
EXEC SQL SELECT empno, sal
    INTO :emp_number, :salary /* host arrays */
    FROM emp
    WHERE deptno = RESEARCH_DEPT;  /* INVALID! */

In the above example, the embedded SQL statement would result in an error, because 40 will not be substituted for RESEARCH_DEPT in the WHERE clause.

Limitations on the Use of #if#

When a user-defined macro function is used in an #if condition, the condition may not evaluate as expected, so it is recommended that #if conditions not contain user-defined macro functions.

Example#
#define fun(X,Y) X-Y
...
#if fun(1,1)
int var;
#else
int var2;
#endif
...

Limitations on the Use of #include#

The APRE*C/C++ Preprocessor raises an error if a header file that is included using the #include directive contains embedded SQL statements. Additionally, header files included in this way must not include declarations of VARCHAR type variables. If you need to include a header file that contains embedded SQL statements or VARCHAR declarations, you must use the EXEC SQL INCLUDE statement to include the header file.

When EXEC SQL INCLUDE is used to include a header file, the APRE*C/C++ Preprocessor includes the entire contents of the header file in the output source file (i.e. the resultant output file with the .c or .cpp filename extension.). Therefore, it is acceptable for header files included in this way to contain embedded SQL statements and VARCHAR declarations. In contrast, when the #include directive is used to include a header file, APRE only processes the header file's macro commands and C variable declarations.

Preprocessor Example#

The following simple example illustrates how the #define and #ifdef directives can be used to conditionally input data into a database. Because a macro called ALTIBASE is defined, the host variable s_goods_alti is declared. If it were not defined, the host variable s_goods_ora would be declared.

Example#

< Sample Program : macro.sc >

/******************************************************************
 * SAMPLE : MACRO
 *          1. Using #define, #if, #ifdef
 ******************************************************************/

/* specify path of header file */
EXEC SQL OPTION (INCLUDE=./include);
/* include header file for precompile */
EXEC SQL INCLUDE hostvar.h;

/* define ALTIBASE */
#define ALTIBASE

#ifdef ORACLE
#undef ORACLE
#endif

int main()
{
    /* declare host variables */
    char usr[10];
    char pwd[10];
    char conn_opt[1024];

    /* structure type */
    #ifdef ALTIBASE
    GOODS   s_goods_alti;
    #else
    GOODS   s_goods_ora;
    #endif

    printf("<INSERT>\n");

    /* set username */
    strcpy(usr, "SYS");
    /* set password */
    strcpy(pwd, "MANAGER");
    /* set various options */
    strcpy(conn_opt, "Server=127.0.0.1"); /* Port=20300 */

    /* connect to altibase server */
    EXEC SQL CONNECT :usr IDENTIFIED BY :pwd USING :conn_opt;
    /* check sqlca.sqlcode */
    if (sqlca.sqlcode != SQL_SUCCESS)
    {
        printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
        exit(1);
    }

    /* use structure host variables */

    #ifdef ALTIBASE
    strcpy(s_goods_alti.gno, "F111100010");
    strcpy(s_goods_alti.gname, "ALTIBASE");
    strcpy(s_goods_alti.goods_location, "AD0010");
    s_goods_alti.stock = 9999;
    s_goods_alti.price = 99999.99;
    #else
    strcpy(s_goods_ora.gno, "F111100011");
    strcpy(s_goods_ora.gname, "ORACLE");
    strcpy(s_goods_ora.goods_location, "AD0011");
    s_goods_ora.stock = 0001;
    s_goods_ora.price = 00000.01;
    #endif

    /* the select insertion useing #ifdef. */

    EXEC SQL INSERT INTO GOODS VALUES (
    #ifdef ALTIBASE
    :s_goods_alti
    #else
    :s_goods_ora
    #endif
    );

    printf("------------------------------------------------------------------\n");
    printf("[Structure Host Variables]\n");
    printf("------------------------------------------------------------------\n");

    /* check sqlca.sqlcode */
    if (sqlca.sqlcode == SQL_SUCCESS)
    {
        /* sqlca.sqlerrd[2] holds the rows-processed(inserted) count */
        printf("%d rows inserted\n\n", sqlca.sqlerrd[2]);
    }
    else
    {
        printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
    }

    /* disconnect */
    EXEC SQL DISCONNECT;
    /* check sqlca.sqlcode */
    if (sqlca.sqlcode != SQL_SUCCESS)
    {
        printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
    }
}

The ALTIBASE_APRE Macro#

The APRE*C/C++ preprocessor contains a predefined macro called ALTIBASE_APRE, which is used when determining whether to precompile certain portions of source code. The ALTIBASE_APRE macro is particularly useful when preprocessing source code that contains large and unnecessary header files that are not necessary for the precompile operation.

The following example illustrates the use of the ALTIBASE_APRE macro to avoid precompiling the header.h file.

Example#
#ifndef ALTIBASE_APRE
#include <header.h>
#endif

In the above example, the header.h file will not be read while the APRE*C/C++ preprocessor precompiles the source code, because the ALTIBASE_APRE macro is defined within APRE, and thus APRE evaluates the #ifndef condition as false. However, the ALTIBASE_APRE macro is not defined within a separate C or C++ compiler, and thus the compiler will evaluate the #ifndef condition as true, and include the header.h file in the compile operation.

The ALTIBASE_APRE macro is intended for use with the #ifdef and #ifndef preprocessor directives.

Consideration#

This section explains some considerations to keep in mind when using the APRE*C/C++ Preprocessor.

Defining Macros#

If a macro is defined using a command-line option when executing the C compiler, then in most cases the same macro must also be defined using the -D command-line option when using the APRE*C/C++ precompiler. For example, if a C compiler is executed from the command line as shown below:

cc -DDEBUG ...

Then the DEBUG macro should also be defined when executing the APRE*C/C++ precompiler, before executing the C compiler. This is shown below:

apre -DDEBUG ...

include Path#

All locations for include files used during preprocessing must be specified with the -I option. For example, if the header file located at /home/project/include needs to be refered, the include path must be specified as follow when APRE*C/C++ preprocessing and C compilation.

apre -I/home/project/include test.sc
cc -I/home/project/include ... test.c