복합 리터럴(Compound literal) — 이름 없는 객체 만들기
복합 리터럴(Compound literal) — 이름 없는 객체 만들기
함수에 구조체를 넘길 때마다 일일이 변수를 선언하고 초기화하기 번거로울 때가 있어요. 그때 쓰는 것이 **복합 리터럴(compound literal)**이에요. 타입을 괄호로 감싸고 그 뒤에 초기화 목록을 붙이는 (struct point){.x=1, .y=2} 같은 형태로, 이름 없는 객체를 그 자리에서 만들고 초기화해요. 캐스트와 겉모습이 비슷하지만, 캐스트가 비-좌측값인 데 비해 복합 리터럴은 **좌측값(lvalue)**이라는 점이 큰 차이예요.
출처: cppreference
본문
지정된 타입(구조체, 공용체, 심지어 배열 타입일 수도 있어요)의 이름 없는 객체를 그 자리( in-place)에서 생성해요.
문법(Syntax)
( storage-class-specifiers(optional, since C23) type ) { initializer-list } (1) (since C99)
( storage-class-specifiers(optional, since C23) type ) { initializer-list , } (2) (since C99)
( storage-class-specifiers(optional) type ) { } (3) (since C23)
storage-class-specifiers— (C23부터)constexpr,static,register,thread_local만 담을 수 있는 저장 클래스 지정자 목록.type— 어떤 완전 객체 타입(type name)이거나 크기를 알 수 없는 배열이지만, VLA는 아니어야 해요.initializer-list— 해당 타입의 객체 초기화에 적합한 초기화자 목록.
설명(Explanation)
복합 리터럴 표현식은 type이 지정하는 타입의 이름 없는 객체를 만들고, initializer-list가 지정하는 대로 초기화해요. 지정 초기화자(designated initializer)는 허용돼요.
복합 리터럴의 타입은 type이에요(크기를 알 수 없는 배열인 경우는 예외 — 그때는 배열 초기화에서처럼 크기가 initializer-list로부터 추론돼요).
복합 리터럴의 값 범주는 **좌측값(lvalue)**이에요(그 주소를 취할 수 있어요).
C23 이전에는, 복합 리터럴이 파일 스코프에서 발생하면 그 이름 없는 객체는 **정적 저장 기간(static storage duration)**을, 블록 스코프에서 발생하면 **자동 저장 기간(automatic storage duration)**을 가졌어요(후자의 경우 객체의 수명은 바깥 블록이 끝날 때 끝나요).
C23부터는, 복합 리터럴이 함수 본문 밖과 매개변수 목록 밖에서 평가되면 파일 스코프와 연관되고, 그 외에는 바깥 블록과 연관돼요. 이 연관에 따라 저장 클래스 지정자(비어 있을 수 있음), 타입 이름, 초기화 목록은 파일 스코프 또는 블록 스코프에서 다음 형식의 객체 정의에 유효한 지정자여야 해요.
storage-class-specifiers typeof( type ) ID = { initializer-list } ;
여기서 ID는 전체 프로그램에 대해 고유한 식별자예요. 복합 리터럴은 값·타입·저장 기간·그 외 속성이 위 정의 문법에 따른 것과 같은 이름 없는 객체를 제공해요. 저장 기간이 자동이면 이름 없는 객체 인스턴스의 수명은 바깥 블록의 현재 실행 동안이에요. 저장 클래스 지정자가 constexpr, static, register, thread_local 외의 다른 지정자를 담으면 동작은 미정의예요.
참고(Notes)
const 한정 타입의 복합 리터럴은 저장소를 공유할 수 있어요. const 한정 문자 또는 와이드 문자 배열 타입의 복합 리터럴은 문자열 리터럴과 저장소를 공유할 수 있어요.
&(const int){7} == &(const int){7} // might be 1 or 0, unspecified
(const char []){"abc"} == "abc" // might be 1 or 0, unspecified
각 복합 리터럴은 자신의 스코프에서 단일 객체만 만들어요.
#include <assert.h>
int main(void)
{
struct S
{
int i;
}
*p = 0, *q;
int j = 0;
again:
q = p,
p = &((struct S){ j++ }); // creates an unnamed object of type S,
// initializes it to the value formerly
// held in j, then assigns the address
// of this unnamed object to the pointer p
if (j < 2)
goto again; // note: if a loop were used, it would end scope here,
// which would terminate the lifetime of the compound
// literal leaving p as a dangling pointer
assert(p == q && q->i == 1);
}
복합 리터럴은 이름이 없으므로 자기 자신을 참조할 수 없어요(이름 있는 구조체는 자기 자신을 가리키는 포인터를 포함할 수 있어요).
복합 리터럴의 문법이 캐스트와 비슷하지만, 중요한 차이는 캐스트는 비-좌측값 표현식인 데 반해 복합 리터럴은 좌측값이라는 점이에요.
예제(Example)
#include <stdio.h>
int *p = (int[]){2, 4}; // creates an unnamed static array of type int[2]
// initializes the array to the values {2, 4}
// creates pointer p to point at the first element of
// the array
const float *pc = (const float []){1e0, 1e1, 1e2}; // read-only compound literal
struct point {double x,y;};
int main(void)
{
int n = 2, *p = &n;
p = (int [2]){*p}; // creates an unnamed automatic array of type int[2]
// initializes the first element to the value formerly
// held in *p
// initializes the second element to zero
// stores the address of the first element in p
void drawline1(struct point from, struct point to);
void drawline2(struct point *from, struct point *to);
drawline1(
(struct point){.x=1, .y=1}, // creates two structs with block scope and
(struct point){.x=3, .y=4}); // calls drawline1, passing them by value
drawline2(
&(struct point){.x=1, .y=1}, // creates two structs with block scope and
&(struct point){.x=3, .y=4}); // calls drawline2, passing their addresses
}
void drawline1(struct point from, struct point to)
{
printf("drawline1: `from` @ %p {%.2f, %.2f}, `to` @ %p {%.2f, %.2f}\n",
(void*)&from, from.x, from.y, (void*)&to, to.x, to.y);
}
void drawline2(struct point *from, struct point *to)
{
printf("drawline2: `from` @ %p {%.2f, %.2f}, `to` @ %p {%.2f, %.2f}\n",
(void*)from, from->x, from->y, (void*)to, to->x, to->y);
}
가능한 출력:
drawline1: `from` @ 0x7ffd24facea0 {1.00, 1.00}, `to` @ 0x7ffd24face90 {3.00, 4.00}
drawline2: `from` @ 0x7ffd24facec0 {1.00, 1.00}, `to` @ 0x7ffd24faced0 {3.00, 4.00}
파일 스코프의 (int[]){2, 4}는 정적 배열을 만들고, 블록 스코프의 (int [2]){*p}는 자동 배열을 만들어요. drawline1은 복합 리터럴을 값으로, drawline2는 &로 주소를 얻어 포인터로 넘겨요. 두 방식 모두 좌측값이기 때문에 주소를 취할 수 있어요.
더 알아보기
- 캐스트에 대한 Cast operators 문서에서 복합 리터럴과의 차이(좌측값 여부)를 확인해요.
- 초기화 규칙에 대한 Initialization 문서를 참고해요.