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]
1
2
3
4
5
6
7
8
#pragma once
 
typedef struct _information {
    int age;
    double height;
} INFO;
 
INFO info = { 80, 158.5 };


[isold.h]
1
2
3
4
5
6
7
#include "info.h"
 
int is_old( int age )
{
    if( info.age >= age ) return 1;
    return 0;
}


[istall.h]
1
2
3
4
5
6
7
#include "info.h"
 
int is_tall( double height )
{
    if( info.height >= height ) return 1;
    return 0;
}


[main.c]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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 수정]
1
2
3
4
5
6
7
8
9
10
11
#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바이트 씩을 비우고 할당하는 경우가 생긴다. 다음을 보자.

1
2
3
4
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 !!!
### ...
### ;;

댓글 없음:

댓글 쓰기