콘텐츠로 이동

4. C Preprocessor#

C Preprocessor 개요#

APRE*C/C++ 은 대부분의 C preprocessor 명령들(source file inclusion (#include), macro definitions (#define), and conditional inclusion (#if))들을 처리한다.

C Preprocessor의 수행#

APRE*C/C++ preprocessor는 대부분의 C preprocessor 명령들을 인지하여 효과적으로 매크로를 치환한다. 또한 파일 include, 조건적인 소스코드 포함 또는 배제도 수행한다. APRE*C/C++ preprocessor 는 preprocess단계에서 얻어진 매크로 값들을 가지고 precompile을 수행하며, 소스파일(*.sc)을 변환하여 출력파일(*.c/cpp)을 생성한다.

예제#

다음은 APRE*C/C++ preprocessor처리 예이다.

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

my_header.h 파일이 현재 디렉터리에 있으며 코드는 아래와 같다.

#define A 1

위 예제에서 APRE*C/C++ preprocessor는 먼저 my_header.h를 읽어 A라는 정의된 값을 기억하며, #if 가 처리될 때 A를 1로 치환하여 조건문을 처리하게 된다. 조건이 참이므로 변수 name 선언부분은 소스파일에 포함되게 된다. 만약 거짓이라면 변환된 소스파일에서 배제된다.

C Preprocessor 구문#

APRE*C/C++ preprocessor 에서 지원하는 C Preprocessor 구문은 #define, #undef, #include, #if, #ifdef, #ifndef, #else, #elif, #endif등이 있다.

#define, #undef#

APRE*C/C++ preprocessor에서 사용될 매크로 이름을 정의 또는 해제한다.

예제#
...
#define A
#define func()
...
#undef A
#undef func

위의 예제에서 APRE*C/C++ preprocessor는 #define구문을 처리하여 A와 func이라는 이름을 심볼테이블에 저장한다. 그러면 다음에 해당 이름이 사용될 때 매크로가 확장된다. 마지막으로 #undef를 만나면 저장된 이름을 심볼테이블에서 제거한다.

#include#

APRE*C/C++ preprocessor에 포함될 외부 소스 파일을 읽어 들여 그 파일 안에 define 매크로나 변수들을 읽어온다. 자세한 내용은 C Processor 개요의 예제를 참고한다.

#include 에 포함되는 파일은 -parse 명령행 옵션에 따라 처리 범위가 달라진다.

#if#

조건절을 확장한 후 계산하여 다음에 위치한 소스 코드를 전처리할 것인지의 여부를 결정한다.

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

위 예제에서 APRE*C/C++ preprocessor가 첫 #if를 처리하기 위해 B를 A-2로 확장하고 A는 1+1로 확장하여 결과적으로 1+1-2를 계산하여 결과값 0을 얻는다. 따라서 첫 #if에서부터 #endif까지의 소스 코드는 제거된다. 두번째 #if문은 #ifdef와 같은 기능을 하는 defined또한 처리됨을 보여준다.

#ifdef#

다음에 오는 이름이 define된 이름인지 확인한 후 소스 코드를 전처리할 지의 여부를 결정한다.

예제#
#define A
#ifdef A
int var;
#endif
...

위의 예제에서 A가 define되어 있으므로 int var;는 전처리(precompile)시 포함된다.

#ifndef#

#ifdef의 반대 조건을 가지고 있으며 define된 이름이 없을 경우 다음에 위치한 소스코드를 전처리 한다.

예제#
#define A
#ifndef A
int var;
#endif
...

A가 define되어 있으므로 int var;는 전처리시 배제된다.

#else#

#if, #ifdef, #ifndef 조건절이 모두 거짓일 경우 마지막 #else 다음에 오는 소스 코드를 전처리 한다.

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

위의 예제에서 #if조건절이 거짓이므로 #else부터 #endif까지의 소스코드를 전처리한다.

#elif#

앞선 #if, #ifdef, #ifndef 조건절이 거짓일경우 #elif 조건절을 확장한 뒤 계산하여 다음에 위치한 소스 코드를 전처리할 지의 여부를 결정한다.

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

#if조건절이 거짓이고 #elif 조건절이 참이므로 #elif부터 #endif까지의 소스코드를 전처리한다.

#endif#

#if, #ifdef, #ifndef 구문들의 마지막에 위치한다.

Preprocessor 제약사항#

APRE*C/C++ Preprocessor에서 처리되지 않는 구문들과 몇몇 제약사항들에 대해 설명한다.

무시되는 구문#

몇몇 C Preprocessor 구문들은 APRE*C/C++ Preprocessor 에서 무시된다. 이들은 전처리(precompile)시 필요하지 않는 구문들이다. 예를 들어 #pragma 는 C 컴파일러에서 사용되는 구문이므로 전처리시에는 처리될 필요가 없다. 전처리시 무시되는 구문들은 아래와 같다.

##

Preprocessor 매크로 인자를 스트링 상수로 바꿔주는 구문이다.

###

매크로 정의시 두 preprocessor 토큰들을 합쳐주는 구문이다.

#error#

컴파일 타임 에러 메세지를 생성해주는 구문이다.

#pragma#

C컴파일러에게 부가적인 정보를 넘겨줄 때 사용되는 구문이다.

#line#

C 컴파일러에게 라인 넘버에 대한 정보를 알려줄 때 사용되는 구문이다.

#define 제약#

APRE*C/C++ 에서 #define를 사용하는데 있어 다음과 같은 제약이 존재한다. SQL 구문 안에서는 define된 이름이 확장되지 않는다. 따라서 SQL 구문 안에 define된 이름을 매크로의 용도로 사용하면 안 된다.

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

위의 예제에서 where절의 RESEARCH_DEPT는 40으로 확장되지 않기 때문에 에러가 발생될 것이다.

#if 제약#

#if의 조건절에 매크로 함수가 올 경우 의도하지 않는 결과를 얻을 수 있다. 되도록 사용하지 않기를 권장한다.

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

#include 제약#

APRE*C/C++ Preprocessor는 #include에 의해 포함된 헤더파일 안에 내장SQL구문이 포함되어 있을 경우에 에러를 발생한다. 또한 VARCHAR 선언도 포함되어서는 안 된다. 내장 SQL 구문이나 VARCHAR 선언이 포함된 헤더파일을 포함하고자 한다면 EXEC SQL INCLUDE 구문을 이용해야 한다.

EXEC SQL INCLUDE 를 사용하면 전처리기는 해당 헤더파일을 출력될 소스코드(.c/.cpp)파일 안에 포함시킨다. 따라서 VARCHAR선언이나 내장 SQL구문들이 헤더 파일에 포함되어도 상관없다. 하지만 #include를 사용하면 전처리기는 그 헤더파일내의 매크로 명령 부분이나 C 변수 선언부만 처리하게 된다.

Preprocessor 예제#

#define과 #ifdef를 이용해 선택적으로 삽입하는 간단한 예제를 살펴보겠다. Altibase가 define되어 있으면 호스트 변수 s_goods_alti를 삽입하고 그 외의 경우에는 s_goods_ora를 삽입한다.

예제#

< 예제 프로그램 : 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);
    }
}

ALTIBASE_APRE 매크로#

APRE*C/C++ 는 ALTIBASE_APRE라는 매크로를 미리 정의하고 있는데, 이는 소스 코드의 특정 부분을 전처리(precompile)할지의 여부를 결정할 때 사용될 수 있다. 전처리 시 필요하지 않는 아주 큰 헤더파일이 include되어 있을 경우 유용하다.

아래 예제는 header.h파일을 전처리하지 않도록 하는 ALTIBASE_APRE 매크로의 예이다.

예제#
#ifndef ALTIBASE_APRE
#include <header.h>
#endif

ALTIBASE_APRE 는 전처리 중에 정의되어 있기 때문에, header.h 파일은 읽혀지지 않을 것이다. 하지만 전처리를 마친 후, 외부 컴파일러 (C/C++ compiler)는 ALTIBASE_APRE가 정의 되어 있다는 것을 알지 못하므로 header.h파일을 포함하여 컴파일 할 것이다.

ALTIBASE_APRE 매크로는 preprocessor 구문인 #ifdef 혹은 #ifndef에서 사용될 수 있다.

주의 사항#

APRE*C/C++ Preprocessor에 의해 전처리시 주의해야 할 사항에 대해 살펴본다.

defined 매크로#

C 컴파일러의 명령행 옵션을 이용하여 매크로를 정의할 것이라면, 대부분의 경우 APRE*C/C++사용할 때도 -D 옵션을 이용하여 매크로를 정의 해야 한다. 예를 들어 C 컴파일러로 아래와 같이 컴파일 명령을 내릴 것이라면,

cc -DDEBUG ...

컴파일전, APRE*C/C++ 실행 시에도 아래와 같이 매크로를 정의해야 한다.

apre -DDEBUG ...

include 경로#

전처리 중 사용되는 include 파일들에 대한 위치는 모두 -I 옵션에 의해 지정되어 있어야 한다. 예를 들어 /home/project/include 위치에 있는 헤더파일을 참조 하고자 할 때 APRE*C/C++ 전처리, C 컴파일 시, 모두 아래처럼 include 경로를 지정해 줘야 한다.

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