이진 리소스 포함
이진 리소스 포함 (#embed)
이미지 파일이나 바이트 스트림 같은 이진 데이터를 소스 코드에 그대로 넣고 싶을 때가 있어요. 예전엔 그 데이터를 수동으로 바이트 배열로 변환해서 코드에 붙여 넣는 수밖에 없었는데, C23부터는 #embed 지시자가 이 작업을 전처리 단계에서 처리해 줘요. 파일을 열어 읽는 런타임 없이, 데이터가 그대로 배열 안의 정수 목록으로 확장돼요.
출처: cppreference
본문
문법 (Syntax)
#embed < h-char-sequence > embed-parameter-sequence(optional) new-line // (1)
#embed " q-char-sequence " embed-parameter-sequence(optional) new-line // (2)
#embed pp-tokens new-line // (3)
__has_embed ( " q-char-sequence " embed-parameter-sequence(optional) ) // (4)
__has_embed ( < h-char-sequence > embed-parameter-sequence(optional) )
__has_embed ( string-literal pp-balanced-token-sequence(optional) ) // (5)
__has_embed ( < h-pp-tokens > pp-balanced-token-sequence(optional) )
h-char-sequence가 식별하는 리소스를 찾아, 리소스 데이터에 해당하는 정수들의 쉼표로 구분된 목록으로 지시자를 대체해요.q-char-sequence가 식별하는 리소스를 찾아, 그 데이터에 해당하는 정수 목록으로 지시자를 대체해요. (1)로 대체해도 돼요.- (1)이나 (2)가 매치되지 않으면
pp-tokens는 매크로 대체를 거쳐요. 대체 후의 지시자를 다시 (1) 또는 (2)와 대조해 볼 수 있어요. - 리소스가 임베딩에 사용 가능한지, 비어 있는지 아닌지, 전달된 매개변수가 구현에 의해 지원되는지 검사해요.
- (4)가 매치되지 않으면
h-pp-tokens와pp-balanced-token-sequence가 매크로 대체를 거쳐요. 대체 후의 지시자를 다시 (4)와 대조해요.
문법 요소:
h-char-sequence— 하나 이상의 h-char 시퀀스. 그중 다음 중 하나가 나타나면 동작이 정의되지 않아요:',",\,//,/*.h-char— 개행과>를 제외한 소스 문자 집합의 임의 멤버.q-char-sequence— 하나 이상의 q-char 시퀀스. 그중',\,//,/*가 나타나면 동작이 정의되지 않아요.q-char— 개행과"를 제외한 소스 문자 집합의 임의 멤버.pp-tokens— 하나 이상의 전처리 토큰 시퀀스.string-literal— 문자열 리터럴.h-pp-tokens—>를 제외한 하나 이상의 전처리 토큰 시퀀스.embed-parameter-sequence— 하나 이상의 pp-parameter 시퀀스. 속성 목록과 달리 쉼표로 구분되지 않아요.pp-parameter— 속성에서 보듯 attribute-token이되, 토큰 대신 전처리 토큰으로 구성된 것.pp-balanced-token-sequence— 속성에서 보듯 balanced-token-sequence이되, 전처리 토큰으로 구성된 것.
설명 (Explanation)
h-char-sequence가 식별하는 리소스를 구현 정의 방식으로 검색해요.q-char-sequence가 식별하는 리소스를 구현 정의 방식으로 검색해요. (1,2)에서 구현은 보통 소스 파일 포함에 쓰는 검색 경로와 비슷하지만 구별되는 메커니즘을 사용해요. 표준의 예제 중__has_embed(__FILE__ ...형태가 등장하는 걸 보면, 적어도 (2)의 경우 현재 파일이 있는 디렉터리가 검색된다는 걸 암시해요.- 지시자에서
embed뒤의 전처리 토큰은 일반 텍스트에서처럼 처리돼요(즉 현재 매크로 이름으로 정의된 각 식별자는 그 대체 전처리 토큰 목록으로 치환돼요). 모든 대체 후의 지시자는 앞의 두 형태 중 하나와 일치해야 해요.<와>전처리 토큰 쌍 사이, 또는"쌍 사이의 전처리 토큰 시퀀스를 단일 헤더 이름 전처리 토큰으로 합치는 방법은 구현 정의예요. h-char-sequence/q-char-sequence가 식별하는 리소스를, 그 전처리 토큰 시퀀스가 문법 (3)의 pp-tokens인 것처럼 검색해요. 단 추가 매크로 확장은 수행되지 않아요. 그런 지시자가#embed지시자의 문법 요구를 만족하지 못하면 프로그램은 ill-formed예요.__has_embed표현식은 검색이 성공하고 리소스가 비어 있지 않으며 모든 매개변수가 지원되면__STDC_EMBED_FOUND__, 리소스가 비어 있고 모든 매개변수가 지원되면__STDC_EMBED_EMPTY__, 검색이 실패하거나 매개변수 중 하나가 지원되지 않으면__STDC_EMBED_NOT_FOUND__로 평가돼요.- 이 형태는 문법 (4)가 매치되지 않을 때만 고려되며, 그때 전처리 토큰은 일반 텍스트처럼 처리돼요.
리소스를 찾지 못하거나 매개변수 중 하나가 구현에 지원되지 않으면 프로그램은 ill-formed예요.
__has_embed는 #if와 #elif의 표현식에서 확장될 수 있어요. #ifdef, #ifndef, #elifdef, #elifndef, 그리고 defined에 의해 정의된 매크로처럼 취급되지만, 그 밖의 다른 곳에서는 사용할 수 없어요.
리소스에는 구현 정의 비트 크기인 implementation resource width 가 있어요. 그 resource width 는 limit 매개변수로 수정되지 않는 한 implementation resource width와 같아요. resource width가 0이면 리소스는 비어 있다고 간주돼요. embed element width 는 구현 정의 매개변수로 수정되지 않는 한 CHAR_BIT와 같아요. resource width는 embed element width로 나누어 떨어져야 해요.
#embed 지시자의 확장 결과는 아래 설명된 정수 상수 표현식 목록으로 이루어진 토큰 시퀀스예요. 목록에서 각 정수 상수 표현식의 토큰 묶음은 이전 정수 상수 표현식의 토큰 묶음과 쉼표로 구분돼요. 시퀀스는 쉼표로 시작하거나 끝나지 않아요. 정수 상수 표현식 목록이 비어 있으면 토큰 시퀀스도 비어요. 지시자는 이 확장으로 대체되며, 특정 embed 매개변수가 있으면 추가 또는 대체 토큰 시퀀스가 함께 붙어요.
확장된 시퀀스의 정수 상수 표현식 값은 리소스 데이터의 구현 정의 매핑으로 결정돼요. 각 정수 상수 표현식의 값은 [0, 2embed element width) 범위예요. 다음 두 조건을 모두 만족하면,
- 정수 상수 표현식 목록이
unsigned char와 호환되는 타입(또는char가 음수 값을 담지 못한다면char)의 배열 초기화에 쓰이고, - embed element width가
CHAR_BIT와 같다면,
초기화된 배열 요소의 내용은 리소스의 이진 데이터가 변환 시간에 그 배열로 fread된 것과 같아요.
구현은 변환 시간의 비트·바이트 순서와 실행 시간의 비트·바이트 순서를 모두 고려해 리소스의 이진 데이터를 더 적절히 표현하도록 권장돼요. 이렇게 하면 변환 시간에 #embed 지시자로 참조한 리소스가 실행 시간 수단으로 접근한 것과 같은 리소스라면, 예컨대 fread 등으로 연속 저장소에 읽어 넣은 데이터가 #embed 확장 결과로 초기화된 문자 타입 배열과 비트 단위로 똑같이 비교될 가능성을 높여요.
매개변수 (Parameters)
표준은 limit, prefix, suffix, if_empty 매개변수를 정의해요. 지시자에 나타나는 그 외의 매개변수는 반드시 구현 정의여야 하며, 그렇지 않으면 프로그램은 ill-formed예요. 구현 정의 embed 매개변수는 지시자의 의미를 바꿀 수 있어요.
limit
limit( constant-expression ) // (1)
__limit__( constant-expression ) // (2)
limit embed 매개변수는 embed 매개변수 시퀀스에 최대 한 번 나타날 수 있어요. 인자를 반드시 가져야 하며, 그 인자는 음이 아닌 수로 평가되고 defined 토큰을 포함하지 않는 정수(전처리) 상수 표현식이어야 해요. resource width는 그 정수 상수 표현식에 embed element width를 곱한 값과 implementation resource width 중 작은 값으로 설정돼요.
suffix
suffix( pp-balanced-token-sequence(optional) ) // (1)
__suffix__( pp-balanced-token-sequence(optional) ) // (2)
suffix embed 매개변수는 최대 한 번 나타날 수 있어요. (비어 있을 수도 있는) 전처리 인자 절을 가져야 해요. 리소스가 비어 있지 않으면, 매개변수 절의 내용이 지시자 확장 직후에 놓여요. 그렇지 않으면 아무 효과가 없어요.
prefix
prefix( pp-balanced-token-sequence(optional) ) // (1)
__prefix__( pp-balanced-token-sequence(optional) ) // (2)
prefix embed 매개변수는 최대 한 번 나타날 수 있어요. (비어 있을 수도 있는) 전처리 인자 절을 가져야 해요. 리소스가 비어 있지 않으면, 매개변수 절의 내용이 지시자 확장 직전에 놓여요. 그렇지 않으면 아무 효과가 없어요.
if_empty
if_empty( pp-balanced-token-sequence(optional) ) // (1)
__if_empty__( pp-balanced-token-sequence(optional) ) // (2)
if_empty embed 매개변수는 최대 한 번 나타날 수 있어요. (비어 있을 수도 있는) 전처리 인자 절을 가져야 해요. 리소스가 비어 있으면, 매개변수 절의 내용이 지시자를 대체해요. 그렇지 않으면 아무 효과가 없어요.
예제 (Example)
PNG 파일을 #embed로 배열에 넣고, 텍스트 파일은 비어 있으면 특정 바이트로 채우는(if_empty) 예시예요.
#include <stdint.h>
#include <stdio.h>
const uint8_t image_data[] =
{
#embed "image.png"
};
const char message[] =
{
#embed "message.txt" if_empty('M', 'i', 's', 's', 'i', 'n', 'g', '\n')
,'\0' // null terminator
};
void dump(const uint8_t arr[], size_t size)
{
for (size_t i = 0; i != size; ++i)
printf("%02X%c", arr[i], (i + 1) % 16 ? ' ' : '\n');
puts("");
}
int main()
{
puts("image_data[]:");
dump(image_data, sizeof image_data);
puts("message[]:");
dump((const uint8_t*)message, sizeof message);
}
가능한 출력:
image_data[]:
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
00 00 00 01 00 00 00 01 01 03 00 00 00 25 DB 56
...
message[]:
4D 69 73 73 69 6E 67 0A 00
같이 보기 (See also)
- 조건부 포함 —
__has_embed를 쓸 수 있는#if/#elif지시자