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