프래그마
프래그마 (Pragmas)
프래그마(pragma)는 구현(implementation)에 특별한 정보를 전달하고 벤더 고유 확장을 추가할 수 있는 지시자예요. pragma(ident) 형태로 문(statement), 문 블록, 선언, 또는 선언 블록에 적용할 수 있어요. 이 문서는 사전 정의된 프래그마들(crt_constructor, inline, lib, mangle, msg, printf, scanf, startaddress 등)과 벤더 고유 프래그마를 다루어요.
출처: Pragmas
본문
프래그마는 구현에 특별한 정보를 전달하고 벤더 고유 확장을 추가할 수 있어요. 프래그마는 ;로 끝나 단독으로 사용될 수 있고, 문, 문 블록, 선언, 또는 선언 블록에 적용할 수 있어요.
프래그마는 PragmaDeclaration 또는 PragmaStatement가 될 수 있어요.
PragmaDeclaration:
Pragma ;
Pragma DeclarationBlock
PragmaStatement:
Pragma ;
Pragma NoScopeStatement
Pragma:
pragma ( Identifier )
pragma ( Identifier , ArgumentList )
pragma(ident); // just by itself
pragma(ident) declaration; // influence one declaration
pragma(ident): // influence subsequent declarations
declaration;
declaration;
pragma(ident) // influence block of declarations
{
declaration;
declaration;
}
pragma(ident) statement; // influence one statement
pragma(ident) // influence block of statements
{
statement;
statement;
}
프래그마의 종류는 Identifier에 의해 결정돼요. ArgumentList는 쉼표로 구분된 AssignExpression 목록이에요. AssignExpression들은 표현식으로 파싱될 수 있어야 하지만, 그 의미는 각각의 프래그마 의미론에 달려 있어요.
사전 정의된 프래그마 (Predefined Pragmas)
모든 구현은 다음 프래그마들을 지원해야 해요(그냥 무시하기만 해도 괜찮아요):
pragma crt_constructorpragma crt_destructorpragma inlinepragma libpragma linkerDirectivepragma manglepragma msgpragma printfpragma scanfpragma startaddress
Implementation Defined: (구현 정의 사항) 구현은 이 프래그마들을 무시할 수 있어요.
pragma crt_constructor
함수가 C 런타임 라이브러리 초기화 이후, D 런타임 라이브러리 초기화 이전에 실행되도록 표시해요.
그 함수는 반드시:
extern (C)여야 해요.- 매개변수를 가지지 않아야 해요.
- 비정적 멤버 함수(non-static member function)가 아니어야 해요.
- 선언(declaration)이 아니라 함수 정의(definition)여야 해요 (즉, 함수 본문을 가져야 해요).
- 소멸자를 가진 타입을 반환하면 안 돼요.
- 중첩 함수(nested function)이면 안 돼요.
__gshared int initCount;
pragma(crt_constructor)
extern(C) void initializer() { initCount += 1; }
프래그마에 인자는 허용되지 않아요. 어떤 함수는 pragma(crt_constructor)와 pragma(crt_destructor) 둘 다로 표시될 수 있어요. 함수 정의가 아닌 선언을 표시하는 것은 아무 효과가 없어요. struct나 class 정의를 표시해도 애그리게이트의 멤버에는 영향을 주지 않아요. pragma(crt_constructor)로 표시된 함수는 const 또는 immutable 변수를 초기화할 수 있어요.
Best Practices: (모범 사례) 시스템 프로그래밍과 C/C++ 연동에 사용하세요. 예를 들어 DSO를 로드할 때 런타임 초기화를 허용하거나,
betterC모드에서shared static this의 단순한 대체물로 사용할 수 있어요.Implementation Defined: (구현 정의 사항)
pragma(crt_constructor)로 표시된 함수들이 실행되는 순서.Best Practices: (모범 사례) 한 모듈 내에서 함수들이 호출되는 순서를 제어하려면, 원하는 순서로 호출하는 단일 함수를 작성하고 그 함수만 표시하세요.
Implementation Defined: (구현 정의 사항) 이는
main()이 호출되기 전에 코드를 실행하기 위해 C 컴파일러가 사용하는 메커니즘을 이용해요. C++ 컴파일러는 이를 사용해 정적 생성자와 소멸자를 실행해요. 예를 들어 GCC의__attribute__((constructor))가 동등해요. Digital Mars C는 crt_constructor와 crt_destructor 함수를 표시하기 위해_STI와_STD식별자 접두사를 사용해요.Implementation Defined: (구현 정의 사항) 표시된 함수에 대한 참조는 Elf 시스템에서
.init_array섹션, Win32 OMF 시스템에서XI섹션, Windows MSCOFF 시스템에서.CRT$XCU섹션, OSX 시스템에서__mod_init_func섹션에 삽입돼요.
Note: crt_constructor와 crt_destructor는 v2.078.0 (2018-01-01)에서 구현됐어요. 그 전에는 일부 컴파일러가 비표준적인 컴파일러 고유 메커니즘을 노출했어요.
pragma crt_destructor
pragma(crt_destructor)는 다음을 제외하고 pragma(crt_constructor)와 동일하게 동작해요:
- 함수가 D 런타임 라이브러리 종료 이후, C 런타임 라이브러리 종료 이전에 실행되도록 표시해요. C의
exit()함수를 호출해도 표시된 함수들이 실행돼요. - 표시된 함수들이 실행되는 순서는
pragma(crt_constructor)로 표시된 함수들의 역순이에요.
Implementation Defined: (구현 정의 사항) 이는
main()이 반환되거나exit()가 호출된 후 코드를 실행하기 위해 C 컴파일러가 사용하는 메커니즘을 이용해요. C++ 컴파일러는 이를 사용해 정적 소멸자를 실행해요. 예를 들어 GCC의__attribute__((destructor))가 동등해요. Digital Mars C는_STI와_STD식별자 접두사를 사용해요.Implementation Defined: (구현 정의 사항) 표시된 함수에 대한 참조는 Elf 시스템에서
.fini_array섹션, Win32 OMF 시스템에서XC섹션, Windows MSCOFF 시스템에서.CRT$XPU섹션, OSX 시스템에서__mod_term_func섹션에 삽입돼요.
__gshared int initCount;
pragma(crt_constructor)
extern(C) void initialize() { initCount += 1; }
pragma(crt_destructor)
extern(C) void deinitialize() { initCount -= 1; }
pragma(crt_constructor)
pragma(crt_destructor)
extern(C) void innuendo() { printf("Inside a constructor... Or destructor?\n"); }
pragma inline
함수가 인라인되는지 여부에 영향을 줘요. 선언 수준이면 제어하는 블록 안에 선언된 함수들에 영향을 주고, 함수 안이면 자신이 둘러싼 함수에 영향을 줘요.
두 가지 형태가 있어요:
pragma(inline)— 동작을 구현의 기본 동작과 일치하도록 설정해요.pragma(inline, AssignExpression)— AssignExpression이 평가되어야 하며 boolean으로 변환될 수 있는 타입이어야 해요. 결과가false면 함수들은 절대 인라인되지 않고, 그렇지 않으면 항상 인라인돼요.
AssignExpression은 하나보다 많아서는 안 돼요. 함수 안에 여러 pragma inline이 있으면 어휘적으로 마지막 것(last)이 효력을 가져요.
pragma(inline):
int foo(int x) // foo() is never inlined
{
pragma(inline, true);
++x;
pragma(inline, false); // supercedes the others
return x + 3;
}
Implementation Defined: (구현 정의 사항) 기본 인라인 동작은 일반적으로
-inline같은 컴파일러 스위치로 선택할 수 있어요. 특정 함수가 인라인될 수 있는지 여부, 그리고 함수를 인라인할 수 없을 때pragma(inline, true)에서 일어나는 일(전형적으로 오류 메시지)은 구현 정의예요.
pragma lib
AssignExpression이 하나 있어야 하고, 컴파일 타임에 문자열 리터럴로 평가되어야 해요.
pragma(lib, "foo.lib");
Implementation Defined: (구현 정의 사항) 문자열 리터럴은 라이브러리 파일의 파일 이름을 지정해요. 이 이름은 생성된 객체 파일에 삽입되거나 링커로 전달되어, 링커가 해당 라이브러리를 자동으로 링크하게 돼요.
pragma linkerDirective
AssignExpression이 하나 있어야 하고, 컴파일 타임에 문자열 리터럴로 평가되어야 해요.
pragma(linkerDirective, "/FAILIFMISMATCH:_ITERATOR_DEBUG_LEVEL=2");
Implementation Defined: (구현 정의 사항) 문자열 리터럴은 생성된 객체 파일에 임베드할 링커 지시어(linker directive)를 지정해요. 링커 지시어는 MS-COFF 출력에서만 지원돼요.
pragma mangle
심볼의 기본 망글링(default mangling)을 덮어써요. 변수와 함수에는 AssignExpression이 하나 있어야 하며 컴파일 타임에 문자열 리터럴로 평가되어야 해요. 애그리게이트에는 AssignExpression이 하나 또는 두 개 있을 수 있는데, 하나는 컴파일 타임에 문자열 리터럴로 평가되어야 하고 하나는 심볼로 평가되어야 해요. 그 심볼이 TemplateInstance라면 애그리게이트는 그 TemplateInstance의 시그니처와 인자를 가진 템플릿으로 취급돼요. 문자열이 제공되지 않으면 심볼의 식별자가 사용돼요. 애그리게이트 이름이 D 키워드일 때 두 인자 모두 사용될 수 있어요.
이는 함수와 변수 심볼에만 적용돼요. 다른 심볼은 무시돼요.
Implementation Defined: (구현 정의 사항) macOS와 Win32에서, 2.079부터 문자열 앞에 추가 밑줄(
_)이 붙어요. 이는 C/C++ 툴체인이 하는 방식이에요. 이를 통해 호환되는 모든 플랫폼(한쪽은 POSIX, 다른 쪽은 win64)에서 특수 처리 없이 같은pragma(mangle)을 사용할 수 있어요.Implementation Defined: (구현 정의 사항) macOS에서 2.113부터, 심볼 이름이
section$로 시작하면 선행 밑줄(_)이 자동으로 생성되지 않아요. 이 이름은 macOS 링커가 섹션을 위해 생성하는 특별한 이름이며 선행 밑줄로 시작하지 않아요.Rationale: (근거) D가 표현할 수 없는 심볼 이름에 연결(link)할 수 있게 해줘요. Identifier는 키워드가 될 수 없으므로, D 키워드인 심볼에 연결할 수 있게 해줘요.
pragma(mangle, "body")
extern(C) void body_func();
pragma(mangle, "function")
extern(C++) struct _function {}
template ScopeClass(C)
{
pragma(mangle, C)
struct ScopeClass { align(__traits(classInstanceAlignment, C)) void[__traits(classInstanceSize, C)] buffer; }
}
extern(C++)
{
class MyClassA(T) {}
void func(ref ScopeClass!(MyClassA!int)); // mangles as MyClass<int>&
}
pragma msg
각 AssignExpression은 컴파일 타임에 평가된 뒤 모두 합쳐져 하나의 메시지가 돼요.
pragma(msg, "compiling...", 6, 1.0); // prints "compiling...61.0" at compile time
Implementation Defined: (구현 정의 사항) 메시지가 취하는 형태와 사용자에게 제시되는 방식. 한 가지 방법은 표준 오류 스트림에 출력하는 것이에요.
Rationale: (근거)
writeln()이 런타임 동안 정보 메시지를 쓰는 역할을 하듯이,pragma(msg)는 컴파일 타임에 그와 동등한 역할을 해요. 예를 들어:
static if (kilroy)
pragma(msg, "Kilroy was here");
else
pragma(msg, "Kilroy got lost");
pragma printf
pragma(printf)는 함수 선언이 printf류 함수임을 지정해요. 즉, C99 표준 7.19.6.1을 따르는 0으로 끝나는 char 문자열 포인터를 받는 format 매개변수를 가지며, 그 바로 뒤에 ... 가변 인자 목록이나 마지막 매개변수로 va_list 타입 매개변수가 오는 extern (C) 또는 extern (C++) 함수예요.
format 인자가 문자열 리터럴이면 C99 표준에 따라 유효한 format 문자열인지 검증돼요. format 매개변수 뒤에 ...가 오면 가변 인자의 개수와 타입이 format 문자열에 대해 검사돼요.
진단되는 비호환성은 다음과 같아요:
- 인자 정렬 불일치를 일으킬 수 있는 호환되지 않는 크기
- 포인터가 아닌 인자를 역참조(dereference)하는 것
- 인자 수가 부족한 것
- struct 인자
- 배열 및 슬라이스 인자
s지정자에 대한 포인터가 아닌 인자- 비표준 형식(non-standard formats)
- C99 표준에 따른 미정의 동작(undefined behavior)
C99 표준에 따라, 여분의 인자는 무시돼요. 무시되는 불일치는 다음과 같아요:
%uformat으로int를 출력하는 것 같은 부호(sign) 불일치- format이
int나uint보다 작은 정수 타입을 지정하는 정수 승격(integral promotion) 불일치. 예를 들어%hd대신%dformat으로short를 출력하는 것
printf("%k\n", value); // error: non-Standard format k
printf("%d\n"); // error: not enough arguments
printf("%d\n", 1, 2); // ok, extra arguments ignored
Best Practices: (모범 사례) 비표준 printf/scanf format을 사용하려면 간단한 해결책은 다음과 같아요:
const format = "%k\n";
printf(format.ptr, value); // no error
Best Practices: (모범 사례) 검출되는 오류 대부분은 이식성(portability) 문제예요. 예를 들어:
string s;
printf("%.*s\n", s.length, s.ptr);
printf("%d\n", s.sizeof);
ulong u;
scanf("%lld%*c\n", &u);
는 다음과 같이 바꿔야 해요:
string s;
printf("%.*s\n", cast(int) s.length, s.ptr);
printf("%zd\n", s.sizeof);
ulong u;
scanf("%llu%*c\n", &u);
함수가 아닌 선언에 적용된 pragma(printf)는 무시돼요. 특히 포인터 to 함수 타입의 선언에는 아무 효과가 없어요.
pragma scanf
pragma(scanf)는 함수 선언이 scanf류 함수임을 지정해요. 즉, C99 표준 7.19.6.2를 따르는 0으로 끝나는 char 문자열 포인터를 받는 format 매개변수를 가지며, 그 바로 뒤에 ... 가변 인자 목록이나 마지막 매개변수로 va_list 타입 매개변수가 오는 extern (C) 또는 extern (C++) 함수예요.
format 인자가 문자열 리터럴이면 C99 표준에 따라 유효한 format 문자열인지 검증돼요. format 매개변수 뒤에 ...가 오면 가변 인자의 개수와 타입이 format 문자열에 대해 검사돼요.
진단되는 비호환성은 다음과 같아요:
- format 지정 타입에 대한 포인터가 아닌 인자
- 인자 수가 부족한 것
- 비표준 형식(non-standard formats)
- C99 표준에 따른 미정의 동작(undefined behavior)
C99 표준에 따라, 여분의 인자는 무시돼요. 함수가 아닌 선언에 적용된 pragma(scanf)는 무시돼요. 특히 포인터 to 함수 타입의 선언에는 아무 효과가 없어요.
pragma startaddress
AssignExpression이 하나 있어야 하고, 컴파일 타임에 함수 심볼로 평가되어야 해요.
Implementation Defined: (구현 정의 사항) 함수 심볼은 프로그램의 시작 주소(start address)를 지정해요. 그 심볼은 객체 파일에 삽입되거나 시작 주소를 설정하기 위해 링커에 제시돼요. 이는 일반적으로 애플리케이션 수준 프로그래밍에 사용되지 않고, 전문 시스템 작업에 사용돼요. 애플리케이션 코드에서는 시작 주소를 런타임 라이브러리가 처리해요.
void foo() { ... }
pragma(startaddress, foo);
벤더 고유 프래그마 (Vendor Specific Pragmas)
벤더 고유 프래그마 식별자는 버전 식별자와 유사한 방식으로 벤더의 상표화된 이름이 접두사로 붙으면 정의될 수 있어요:
pragma(DigitalMars_extension) { ... }
구현은 인식할 수 없는 프래그마에 대해, 벤더 고유 프래그마라도 오류를 진단해야 해요.
Implementation Defined: (구현 정의 사항) 벤더 고유 프래그마.
Best Practices: (모범 사례) 벤더 고유 프래그마는 version 문 안에 감싸야 해요.
version (DigitalMars)
{
pragma(DigitalMars_extension)
{ ... }
}