C++/문법 및 메소드(STL)
[C++][Build] 전처리 단계 - #pragma 지시문
pledge24
2025. 4. 14. 16:59
#pragma 지시문
- #pragma 지시문은 C/C++ 표준 키워드가 아닌 컴파일러에 종속된 키워드들로, 컴파일러의 종류에 따라 기능이나 문법이 다르다.
- #pragma는 #include나 #define처럼 전처리기에 의해 처리되지만, 전처리기는 이를 단순히 해석하여 컴파일러에게 전달하고, 실제 동작은 컴파일러가 수행한다.
- 이러한 #pragma를 활용하면 코드의 경고 제어, 최적화 설정, 메모리 정렬, 헤더 중복 방지 등 다양한 작업을 수행할 수 있다.
대표적인 #pragma 지시문들
- #pragma once
- #pragma pack
- #pragma region
- #pragma comment
#pragma once
#pragma once
- #pragma once는 헤더 파일이 동일한 번역 단위(translation unit, TU) 내에서 여러 번 포함되는 경우 한 번만 포함되도록 하는 지시문이다.
- 즉, #pragma once는 #ifndef가 했던 “헤더 가드”의 역할을 한다. (헤더 가드에 대한 설명은 여기를 참고)
- #pragma once는 올바른 헤더 가드의 역할을 하기 위해 헤더 파일 최상단에 위치해야 한다.
// header.h
#pragma once
// Code placed here is included only once per translation unit
#ifndef와 #prgama once의 헤더 가드 비교
- 아래 코드처럼 #prgama once의 헤더 가드는 사용하기 간편하기 때문에 헤더 가드로써의 활용 빈도가 #ifndef보다 더 높다.
// =====#ifndef 방식의 헤더가드=====
#ifndef A_H // A_H는 a.h 헤더 파일을 의미
#define A_H
class A {};
#endif
// =====#pragma once 방식의 헤더가드=====
#pragma once
class A {};
#pragam pack()
#pragma pack(N)
#pragma pack(push, N)
#pragma pack(pop, N)
- #pragma pack()은 구조체 멤버의 메모리 정렬(alignment) 방식을 조절할 때 사용한다.
- #pragma pack()은 인자로 무엇을 넣느냐에 따라 두 가지 방식으로 사용할 수 있다. 첫 번째 방법으론 #pragma pack(N)이 있고, 두 번째 방법으론 #pragma pack(push/pop)이 있다.
메모리 정렬(Memory Alignment)이란?
- 메모리 정렬은 CPU가 메모리에 더 빠르게 접근하기 위해 일정한 바이트 단위로 데이터를 정렬하는 것을 의미한다.
- 메모리 정렬을 할 경우 데이터들은 정렬 경계(alignment boundary)에 맞추어 저장된다. 이때 메모리 정렬에 의해 데이터가 맞춰져 저장된 모습을 “데이터가 경계에 놓여있다”라고 표현한다.
- 예를 들어, 데이터가 4의 배수에 맞추어 정렬되었다면, 이를 “더블 워드 경계에 놓여있다”라고 한다. (더블 워드는 4바이트를 의미한다.)
- 컴퓨터의 메모리 주소 체계(32bits, 64bits)에 맞게 정렬해야 메모리에 접근이 빠르기 때문에, 메모리 정렬의 기본값은 4바이트(32bits) 또는 8바이트(64bits)이다.
- 연속된 데이터를 저장해야 하는 경우, CPU는 메모리 정렬을 유지하기 위해 데이터 사이에 빈 메모리 공간을 추가한다. 이 빈 공간을 "패딩(padding)"이라고 부르며, 같은 데이터라도 배치에 따라 패딩의 크기가 달라진다.
패딩(padding) 예제
// 현재 사용하는 메모리 주소 체계는 64bits(8Byte)
struct S1
{
long long a = 0x24; // 8바이트
char c = 'a'; // 1바이트
int x = 0x11; // 4바이트
};
=> S1 구조체 크기: 16바이트, 패딩: 3바이트
[24 00 00 00 00 00 00 00] // long long a(8Byte)
[61 cc cc cc 11 00 00 00] // char c(1Byte) + "padding"(3Byte) + int x(4Byte)
struct S2
{
char c = 'a'; // 1바이트
long long a = 0x24; // 8바이트
int x = 0x11; // 4바이트
};
=> S2 구조체 크기: 24바이트, 패딩: 11바이트
[61 cc cc cc cc cc cc cc] // char c(1Byte) + "padding"(7Byte)
[24 00 00 00 00 00 00 00] // long long a(8Byte)
[11 00 00 00 cc cc cc cc] // int x(4Byte) + "padding"(4Byte)
#pragma pack()의 첫 번째 사용 방법: #pragma pack(N)
- 첫 번째 사용 방법인 #pragma pack(N)은 N바이트 단위로 메모리를 정렬하라는 뜻으로, 기존에 4바이트 또는 8바이트로 정렬하던 시스템을 무시하고 N바이트로 정렬하도록 지정한다. (유효한 N은 1, 2, 4, 8, 16이다.)
- 이 방식을 사용할 때 한 가지 주의할 점이 있는데, 한 번 적용하면 계속 유지되기 때문에 이후 모든 구조체에 적용된다는 것이다. 따라서, 특정 구조체에만 적용하고 싶다면 적용 후 다시 원상태(#pragma pack(8))로 되돌려놓아야 한다.
#pragma pack(1) // 이후의 구조체들은 1바이트 정렬됨
struct A {
char a;
int b; // 평소엔 8바이트 경계에 맞추지만 지금은 1바이트 기준
};
// #pragma pack(8) // 이후 구조체들에는 기존 정렬 방식을 사용하고 싶을때 활성화
#pragma pack()의 두 번째 사용 방법: #pragma pack(push/pop)
- 두 번째 사용 방법인 #pragma pack(push/pop)은 정렬 스택을 사용하는 방법으로, 정렬 스택 top에 저장된 정렬 방식으로 메모리 정렬을 설정한다.
- 이 방식은 일시적으로 정렬 방식 바꾸고 원상 복구하고 싶을 때 유용하다.
- 사용 방식은 다음과 같다.
- #pragma pack(push, N) : N바이트 메모리 정렬로 설정하고, N값을 스택에 push 한다.
- N을 지정하지 않고 비워두면, 현재 정렬 값을 push 한다.
- #pragma pop(pop) : 스택에서 한 번 pop 하고, pop 한 값으로 메모리 정렬을 설정한다.
- #pragma pop(pop, N) : 스택에서 한 번 pop 하고, N값으로 메모리 정렬을 설정한다. (이 기능은 Visual Studio와 같은 MSVC 전용 기능이다.)
- #pragma pack(push, N) : N바이트 메모리 정렬로 설정하고, N값을 스택에 push 한다.
#pragma pack(push, 1) // 현재 정렬 설정 저장(push)하고 1바이트 정렬로 설정
struct A {
char a;
int b;
};
#pragma pack(pop) // 정렬 설정을 이전 값으로 복원
#pragma region
#pragma region 지역_이름
#pragma endregion [주석]
- #pragma region은 MSVC 확장 기능으로, 추가적인 기능은 없고 그저 코드 블록을 접거나 펼 수 있도록 하는 기능이다.
- #pragma region “이름”으로 코드 블록의 시작을 표시하고, #pragma endregion으로 코드 블록의 끝을 표시한다.
- #pragma endregion 뒤에 주석을 적으면 접었을 때 주석이 표시된다.
#pragma region X
void func1() { /*...*/ }
void func2() { /*...*/ }
void func3() { /*...*/ }
#pragma endregion X 지역을 설명하는 주석
- 아래 사진과 같이 #pragma region으로 지역을 설정하면 왼쪽에 접는 화살표가 생기며, 접었을 때 적어놓은 주석이 표시되는 걸 볼 수 있다.
#pragma comment()
#pragma comment( comment_유형 [ , "comment-문자열" ])
- #pragma comment는 MSVC 확장 기능으로, 링커 옵션을 소스 코드에 직접 명시할 때 사용한다.
- comment_유형에는 compiler, lib, linker 등의 미리 정의된 식별자가 들어가며, comment-문자열에는 "/include:__mySymbol”와 같이 링커 옵션이 포함된 문자열이나 "user32.lib”와 같이 라이브러리 문자열이 들어간다.
주요 용도: 라이브러리 자동 연결
- #pragma comment를 사용하는 주된 이유는 라이브러리를 자동으로 연결하기 위해서다.
- #pragma comment의 comment_유형을 lib로 설정한 다음, 추가할 라이브러리 이름을 comment-문자열 위치에 적어주면, Visual Studio 프로젝트 설정에서 별도의 추가 없이 해당 라이브러리가 자동으로 링킹 된다.
- 주로 헤더 파일에 적어서 사용한다.
라이브러리 연결 예제: Window Socket
// someHeaderFile.h
#pragma comment(lib, "ws2_32.lib") // 윈도우 소켓 라이브러리 자동 연결
이외에도 많은 #pragma 지시문들이 있다.
- #pragma에서 제공하는 지시문들은 굉장히 많다. 다른 지시문에 대한 자세한 내용은 아래 링크를 확인하면 된다.
Pragma 지시문 및 __pragma 및 _Pragma 키워드
pragma MsVC(Microsoft Visual C 및 C++)에서 사용할 수 있는 지시문에 대해 설명합니다.
learn.microsoft.com