연재 완료/C Lang 이론

컴파일러 지시자 #pragma

라이피 (Lypi) 2019. 7. 11. 01:01
반응형

 C언어는 플랫폼 (윈도우나 안드로이드, 유닉스 등)에 상관없이 이식이 가능한 언어이지만, 언어를 이용해 고유의 실행 파일을 만들어내는 컴파일러는 플랫폼 종속적이다. 그러므로 컴파일러는 각각의 플랫폼의 고유 기능을 수행할 수 있도록 지원해야한다. 이때 사용하는 명령어가 #pragma이다. 

 

  #pragma 는 #으로 시작하므로 전처리문으로 보이지만 "컴파일러 지시자"라고 따로 분류된다. #pragma 지시자의 기본 형식은 "#pragma "token""이다. 이 "token"의 종류는 컴파일러별로 다르며 플랫폼 종속적이므로 플랫폼 이식성을 유지하려면 조건부 컴파일 지시자와 함께 사용하는 것이 좋다. 컴파일러는 #pragma 다음의 토큰을 인식할 수 없을 경우 단순히 무시하고 컴파일을 계속 수행한다.

 

  VS의 #pragma "token"은 이 페이지에서 확인할 수 있다. 토큰들중에는 내용이 굉장히 어려운 것도 있고 간단한 것도 있다. 이 포스팅에서는 간단하고 유용한 토큰만 몇가지 소개한다.

 

 

1.  #pragma once

  헤더파일 선두에 적어서 헤더파일을 한번만 포함하게 해서 컴파일 시간을 절약하고 중복 정의를 막는다.

  일부러 헤더파일을 두번씩 쓰진 않겠지만 헤더파일이 중첩되다보면 두번씩 포함되는 경우가 있으므로 이를 방지해준다. 이는 #pragma 지시자가 아닌 #ifndef 전처리문으로도 동일한 효과를 낼 수 있다. #ifndef가 C언어 표준이므로 이를 쓰는 것이 플랫폼 이식성에는 더 좋다. 아래는 사용 예시이다.


//헤더파일을 중복 포함되지 않게 하는 방법

//방법 1. #pragma once를 사용한다. 
#pragma once
#include < iostream > 
//그외 헤더파일들...

//방법 2. #ifndef를 사용한다.
#ifndef HEADER_FILE_ONCE_INCLUDE
#define HEADER_FILE_ONCE_INCLUDE
  //헤더 파일 내용들
#endif //HEADER_FILE_ONCE_INCLUDE

//방법2의 원리는 다음과 같다.
//#ifndef는 매크로가 정의되어 있지 않으면 #endif 까지의 내용을 컴파일한다.
//그 안에서 매크로를 정의하고 밑으로 헤더파일을 정의한다.
//그러면 다시 같은 구문을 만났을 때는 매크로가 정의되어 있으므로 재컴파일되지 않는다.

 

2. #pragma pack(n), 

  이후 선언되는 구조체의 메모리 정렬 방식을 지정한다. 구조체의 메모리 정렬 기본값은 8byte이고 프로젝트 설정에서 바꿀 수도 있지만 #pragma pack(n) 명령어를 쓰면 소스 중간에도 정렬 값을 바꿀 수 있다. #pragma pack()이라고 ()안을 비워서 쓰면 다시 기본값인 8로 정렬된다.

  이 정렬값은 컴파일러에 미리 지정되어 있는 스택에 저장되는데 #pragma pack(push,n)은 현재의 정렬값을 스택에 저장하고 새로운 정렬값인 n으로 바꾼다. 이 때, n을 생략하면 현재의 정렬값을 스택에 저장만 하고 따로 바꾸진 않는다. #pragma pack(pop,n)은 스택에 저장되어 있는 정렬값으로 현재의 정렬값으로 바꾸고 스택에 저장되어 있던 값은 n으로 바꾼다. 

  주로 #pragma pack(push,n)과 #pragma pack(pop)을 같이 써서 특정한 구조체의 정렬값을 임시로 바꾸는 식으로 사용된다.


#include < iostream>

struct s1 { long c1; double i1; };  //기본이 8바이트 정렬이므로 구조체 s1은 16byte를 차지한다. 8(4) + 8(8) = 16
#pragma pack(2)
struct s2 { long c2; double i2; };  //2바이트씩 정렬하게 했으므로 구조체 s2는 12바이트를 차지한다. 4(4) + 8(8) = 12
#pragma pack(push,1)
struct s3 { bool b; long ll; };     //현재의 정렬값(2)를 스택에 저장하고, 1로 정렬한다. 1(1) + 4(4) = 5
#pragma pack(pop)
struct s4 { long c2; double i2; };  //저장되어 있던 정렬값을 꺼낸다.
#pragma pack()
struct s5 { long c3; double i3; };  //다시 8바이트로 정렬되어서 구조체 s3은 16byte를 차지한다. 

using namespace std;

int main()
{
	cout << "구조체 s1의 크기 : " << sizeof(s1) << "byte" << endl;
	cout << "구조체 s2의 크기 : " << sizeof(s2) << "byte" << endl;
	cout << "구조체 s3의 크기 : " << sizeof(s3) << "byte" << endl;
	cout << "구조체 s4의 크기 : " << sizeof(s4) << "byte" << endl;
	cout << "구조체 s5의 크기 : " << sizeof(s5) << "byte" << endl;
}

 

3. #pragma region, #pragma endregion

  코드를 구역으로 나눠서 정리해서 접어두기 위해 사용한다. 컴파일에는 따로 영향을 미치지 않는다.

  구역의 시작은 #pragma region 으로 표시하고, 끝은 #pragma endregion으로 표시한다. region token 뒤에 적는 문구는 그 구역을 지칭하기 위해 적는 문구로  아무렇게나 적어도 컴파일에 영향을 미치지 못하고 무시된다.

  나는 클래스를 설계할 때 몇가지 규칙을 가지고 멤버 변수나 멤버 함수를 배치하는데 이 때 비슷한 분류의 멤버 변수나 멤버 함수를 묶어두고 싶을때 이 명령어를 주로 사용한다.


class Point
{
	int x;
	int y;

public:

	void Print() const  // const 함수: 함수 안에서 멤버 변수의 값을 변경할 수 없음을 의미 
	{
		//const 함수 내에서는 const 함수만 호출할 수 있다.
		cout << "(" << x << "," << y << ")";
	}


public:

#pragma region operator method //여기서부터 
	Point operator+ (Point arg)
	{
		//cout << "\noperator+ () 함수호출" << endl;

		Point ret;

		ret.x = this->x + arg.x;
		ret.y = this->y + arg.y;

		return ret;
	}

	//아래의 const는 리턴값을 바로 변경할 수 없다는 뜻이다.
	// (p3++) + p2 같은 식으로 쓸 수 없다는 뜻

	const Point operator-- () //전위 --연산자
	{
		--x; --y;
		return (*this);
	}

	//후위 연산자라고 자동으로 우리가 생각되는 방식으로 작동하진 않는다.
    //그러므로 후위연산자를 구현하기 위해서 원래 값을 따로 저장해뒀다가 그 값을 리턴해주자.

	const Point operator-- (int) //후위 --연산자
	{
		Point tmp = *this;
		--(*this);
		return tmp;
	}
#pragma endregion operator method //여기까지 접을 수 있다.

public:
	//생성자 정의
	Point(int _x = 0, int _y = 0) : x(_x), y(_y)	{	} 

	
};
반응형