2014년 2월 27일 목요일

#pragma once 와 #pragma pack 에 관하여.

C 언어의 전처리 지시자로서 #pragma 라는 것이 있다. 이것은 다른 전처리 지시자와 달리 컴파일하고자 하는 컴파일러에 의존적인 내용들이 들어가 있다. 즉, #pragma 뒤에 오는 구문은 컴파일러마다 다르게 정의되어 있다.

ISO C (표준 C)에서는 C90(ISO/IEC 9899:1990) 까지는 #pragma 의 표준 구문이 존재하지 않았다. 즉, 순전히 기능을 컴파일러에 맡겼다. 그런데 C99(ISO/IEC 9899:1999) 에서 #pragma에 표준 구문이 추가 되었다. 그 표준 구문은 #pragma STDC .... 라고 입력하면 되는데. 이 글에서 이를 다룰 것이 아니므로 여기까지만 언급하고 넘어가겠다.

#pragma 구문의 가장 대표적인 예는 바로 이것이다.

#pragma once

결론적으로 말하면 일반적으로 header 파일(.h) 의 맨 위에 저 구문을 집어 넣으며, 중복 include 를 방지하는 데에 사용한다. 아래의 예를 보자.

[info.h]
#pragma once

typedef struct _information {
    int age;
    double height;
} INFO;

INFO info = { 80, 158.5 };


[isold.h]
#include "info.h"

int is_old( int age )
{
    if( info.age >= age ) return 1;
    return 0;
}


[istall.h]
#include "info.h"

int is_tall( double height )
{
    if( info.height >= height ) return 1;
    return 0;
}


[main.c]
#include "isold.h"
#include "istall.h"
#include <stdio.h>

int main()
{
    int old = is_old(35);
    int tall = is_tall(193.2);

    if(old) printf("old ");
    if(tall) printf("tall ");

    putchar('\n');
    return 0;
}


위의 main.c 를 build 한다고 하자. main.c 는 stdio.h 외에 두 개의 파일을 include 하고 있는데, 그 두 개의 파일은 모두 info.h 를 include 하고 있다. 즉 main.c의 입장에서 보면 info.h가 두 번 include 되므로 struct _information 의 타입 재정의와 struct 의 전역 변수가 중복정의된다. 이럴 경우 info.h 의 맨 위에 #pragma once 를 넣으면 한 번만 include 되게 해 준다는 것인데....

애석하게도 #pragma once 는 모든 표준 C compiler 에서 사용 가능한 것이 아니다. 특히 대부분의 UNIX 와 Linux 에서는 지원하지 않는다. 따라서 프로그램의 이식성을 위해 "아직까지는" #pragma once 대신 아래와 같이 전통적인 방법(#ifndef, #define, #endif)으로 info.h 를 작성하는 것이 좋다.

[info.h 수정]
#ifndef _INFO_H_
#define _INFO_H_

typedef struct _information {
    int age;
    double height;
} INFO;

INFO info = { 80, 158.5 };

#endif


마지막으로 위에서 본인이 "아직까지는" 이라는 곳에 포인트를 둔 이유를 이야기하고자 한다. 비록 #pragma once 가 C 표준은 아니지만 현재 여러 표준 C 컴파일러에서 #pragma once 의 기능 채택 비율이 낮지 않다는 것이다. 최근에 Linux GCC 컴파일러는 #pragma once 의 기능을 한 때 없애버렸다가 다시 채택하였다(버전 3.4).  이런 추세라면 #pragma once 도 결국엔 범용적으로 쓰이지 않을까 조심스럽게 예상은 하지만.... 아직은 전통적인 방법을 추천하고 싶다.

참고 : http://en.wikipedia.org/wiki/Pragma_once


다음............
또 하나의 많이 사용되는 구문으로 #pragma pack 이라는 것이 있다. 일단 이 것이 왜 나왔는지를 살짝 이야기하겠다.

32비트 OS를 예를 들어 설명하면, CPU는 메모리를 4바이트씩 끊어서 읽는다. 따라서 메모리에서 변수 access를 빠르게 하기 위하여 중간에 1, 2바이트 씩을 비우고 할당하는 경우가 생긴다. 다음을 보자.

struct A {
    char c;
    int i;
};

위의 경우 sizeof( struct A ) 는 5가 예상되지만 실제로는 8 (혹은 OS의 종류에 따라서 그 이상 또는 그 이하)이 된다 . 이런 경우는 위의 struct 구문을 그대로 네트워크에 실어보낸다든지 했을 때에 변수 디코딩에 약간의 문제가 발생할 수 있다.

메모리 access 에 관계 없이 sizeof( struct A ) 가 5가 나오도록 하려고 사용하는 구문이 #pragma pack 이다. 보통 구조체와 공용체(, 그리고 C++의 경우 클래스) 사이즈의 조정이 필요한 구문의 위와 아래에 사용을 한다. 하지만 이 또한 표준이 아니므로 컴파일러 마다 #pragma pack 사용법이 다르다.

Visual C 컴파일러는 다음과 같이 사용한다.

// 현재의 pack 사이즈를 저장하지 않고 사이즈를 2로 설정
#pragma pack(2)

// 현재의 pack 사이즈를 저장하고 pack 사이즈를 1로 설정
#pragma pack(push, 1)
 . . . . .
// 저장한 pack 사이즈로 변경
#pragma pack(pop)


Linux GCC 컴파일러는 다음과 같이 사용한다.

// 현재의 pack 사이즈를 1로 설정
#pragma pack(1)
 . . . . .
// 최초의 pack 사이즈로 변경
#pragma pack()

단 최근의 GCC 컴파일러는 (4.0 이상) Visual C 컴파일러의 스타일도 호환 가능하다고 언급이 되어 있다.

결론은....

C 프로그래밍을 할 때 예전처럼 OS dependent 한 코드만을 작성한다면 모르겠지만, 오늘날처럼 이식성이 중요한 코드를 작성하는 경우에는 C 표준, 그리고 각 컴파일러의 지원 여부 등을 고려하여 좀 더 범용성 있는 코드를 작성하려는 노력이 필요하다.

### Wafting ....... Done !!!
### ...
### ;;

댓글 없음:

댓글 쓰기