배열 초기화
배열 초기화 (Array initialization)
배열을 선언하면서 어떤 값으로 채울지 한 번에 정하고 싶을 때가 많아요. 배열 타입의 객체를 초기화하는 방법에는 문자열 리터럴로 채우는 방법과, 중괄호로 감싼 초기화자 목록으로 채우는 방법이 있어요. 이 페이지에서 그 규칙을 정리해 볼게요.
출처: cppreference
본문
배열 타입의 객체를 초기화할 때, 초기화자는 문자열 리터럴(선택적으로 중괄호로 감쌀 수 있음)이거나 배열 멤버들을 위한 초기화자의 중괄호 목록이어야 해요.
= string-literal |
(1) | |
= { expression , ... } |
(2) | (until C99) |
= { designator(optional) expression , ... } |
(2) | (since C99) |
= { } |
(3) | (since C23) |
- 문자 및 와이드 문자 배열용 문자열 리터럴 초기화자
- 배열 요소의 초기화자인 상수(until C99)표현식의 쉼표 구분 목록. 선택적으로
[ constant-expression ] =형태의 배열 지정자(designator)를 쓸 수 있음 (since C99) - 빈 초기화자(empty initializer)로 배열의 모든 요소를 빈 초기화(empty-initialize)함
크기가 알려진 배열과 알려지지 않은 배열은 초기화할 수 있지만, VLA는 초기화할 수 없어요 (since C99)(until C23). VLA는 빈 초기화만 할 수 있어요 (since C23).
명시적으로 초기화되지 않은 모든 배열 요소는 빈 초기화돼요.
문자열로부터의 초기화
일치하는 타입의 배열 초기화자로 문자열 리터럴(선택적으로 중괄호로 감쌀 수 있음)을 쓸 수 있어요.
- 보통 문자열 리터럴과 UTF-8 문자열 리터럴(since C11)은 모든 문자 타입(char, signed char, unsigned char)의 배열을 초기화할 수 있어요
L접두사 와이드 문자열 리터럴은 (cv 한정을 무시했을 때)wchar_t와 호환되는 모든 타입의 배열을 초기화할 수 있어요u접두사 와이드 문자열 리터럴은 (cv 한정을 무시했을 때)char16_t와 호환되는 모든 타입의 배열을 초기화할 수 있어요U접두사 와이드 문자열 리터럴은 (cv 한정을 무시했을 때)char32_t와 호환되는 모든 타입의 배열을 초기화할 수 있어요 (since C11)
문자열 리터럴의 연속된 바이트 또는 와이드 문자열 리터럴의 와이드 문자(끝을 맺는 널 바이트/문자 포함)가 배열의 요소를 초기화해요.
char str[] = "abc"; // str has type char[4] and holds 'a', 'b', 'c', '\0'
wchar_t wstr[4] = L"猫"; // str has type wchar_t[4] and holds L'猫', '\0', '\0', '\0'
배열의 크기가 알려져 있다면, 문자열 리터럴 크기보다 하나 작아도 되는데, 그 경우 끝을 맺는 널 문자가 무시돼요.
char str[3] = "abc"; // str has type char[3] and holds 'a', 'b', 'c'
이런 배열의 내용은 char* str = "abc";처럼 문자열 리터럴을 직접 접근하는 것과 달리 수정 가능하다는 점에 주의하세요.
중괄호 목록으로부터의 초기화
배열을 중괄호로 감싼 초기화자 목록으로 초기화할 때, 목록의 첫 번째 초기화자는 인덱스 0의 배열 요소를 초기화하고(지정자가 지정되지 않았다면)(since C99), 지정자 없는(since C99) 각 후속 초기화자는 이전 초기화자가 초기화한 요소보다 하나 큰 인덱스의 배열 요소를 초기화해요.
int x[] = {1,2,3}; // x has type int[3] and holds 1,2,3
int y[5] = {1,2,3}; // y has type int[5] and holds 1,2,3,0,0
int z[4] = {1}; // z has type int[4] and holds 1,0,0,0
int w[3] = {0}; // w has type int[3] and holds all zeroes
크기가 알려진 배열을 초기화할 때 요소보다 초기화자를 더 많이 제공하면 오류예요 (문자 배열을 문자열 리터럴로 초기화하는 경우 제외).
int n[5] = {[4]=5,[0]=1,2,3,4}; // holds 1,2,3,4,5
int a[MAX] = { // starts initializing a[0] = 1, a[1] = 3, ...
1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0
};
// for MAX=6, array holds 1,8,6,4,2,0
// for MAX=13, array holds 1,3,5,7,9,0,0,0,8,6,4,2,0 ("sparse array")
지정자(designator)는 그 뒤의 초기화자가 지정자가 서술하는 배열 요소를 초기화하게 만들어요. 그러면 초기화는 지정자가 서술한 요소의 다음 요소부터 순서대로 앞으로 계속돼요 (since C99).
크기가 알려지지 않은 배열을 초기화할 때, 초기화자가 지정된 가장 큰 첨자가 선언되는 배열의 크기를 결정해요.
중첩 배열
배열의 요소가 배열, struct, 또는 union이라면, 중괄호 목록의 대응하는 초기화자는 그 멤버들에 유효한 아무 초기화자인데, 단 그 중괄호는 다음과 같이 생략될 수 있어요.
중첩 초기화자가 여는 중괄호로 시작하면, 닫는 중괄호까지의 전체 중첩 초기화자가 대응하는 배열 요소를 초기화해요.
int y[4][3] = { // array of 4 arrays of 3 ints each (4x3 matrix)
{ 1 }, // row 0 initialized to {1, 0, 0}
{ 0, 1 }, // row 1 initialized to {0, 1, 0}
{ [2]=1 }, // row 2 initialized to {0, 0, 1}
}; // row 3 initialized to {0, 0, 0}
중첩 초기화자가 여는 중괄호로 시작하지 않으면, 목록에서 하위 배열·struct·union의 요소나 멤버를 채우기에 충분한 만큼만 초기화자가 사용되고, 남은 초기화자는 다음 배열 요소를 채우는 데 쓰여요.
int y[4][3] = { // array of 4 arrays of 3 ints each (4x3 matrix)
1, 3, 5, 2, 4, 6, 3, 5, 7 // row 0 initialized to {1, 3, 5}
}; // row 1 initialized to {2, 4, 6}
// row 2 initialized to {3, 5, 7}
// row 3 initialized to {0, 0, 0}
struct { int a[3], b; } w[] = { { 1 }, 2 }; // array of structs
// { 1 } is taken to be a fully-braced initializer for element #0 of the array
// that element is initialized to { {1, 0, 0}, 0}
// 2 is taken to be the first initialized for element #1 of the array
// that element is initialized { {2, 0, 0}, 0}
배열 지정자는 중첩될 수 있어요. 중첩 배열의 대괄호 상수 표현식은 바깥 배열의 대괄호 상수 표현식 뒤를 따라와요 (since C99).
int y[4][3] = {[0][0]=1, [1][1]=1, [2][0]=1}; // row 0 initialized to {1, 0, 0}
// row 1 initialized to {0, 1, 0}
// row 2 initialized to {1, 0, 0}
// row 3 initialized to {0, 0, 0}
주의
C에서 배열 초기화자의 부분표현식 평가 순서는 불확정 시퀀스(indeterminately sequenced)예요 (C++11부터 C++에서는 아님).
int n = 1;
int a[2] = {n++, n++}; // unspecified, but well-defined behavior,
// n is incremented twice (in arbitrary order)
// a initialized to {1, 2} and to {2, 1} are both valid
puts((char[4]){'0'+n} + n++); // undefined behavior:
// increment and read from n are unsequenced
C에서는 초기화자의 중괄호 목록이 비어 있을 수 없어요. C++은 빈 목록을 허용해요. (until C23)
빈 초기화자(empty initializer)를 써서 배열을 초기화할 수 있어요. (since C23)
int a[3] = {0}; // valid C and C++ way to zero-out a block-scope array
int a[3] = {}; // valid C++ way to zero-out a block-scope array; valid in C since C23
다른 모든 초기화와 마찬가지로, 정적 또는 스레드-로컬 저장 기간의 배열을 초기화할 때 초기화자 목록의 모든 표현식은 상수 표현식이어야 해요.
static char* p[2] = {malloc(1), malloc(2)}; // error
예시
int main(void)
{
// The following four array declarations are the same
short q1[4][3][2] = {
{ 1 },
{ 2, 3 },
{ 4, 5, 6 }
};
short q2[4][3][2] = {1, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 4, 5, 6};
short q3[4][3][2] = {
{
{ 1 },
},
{
{ 2, 3 },
},
{
{ 4, 5 },
{ 6 },
}
};
short q4[4][3][2] = {1, [1]=2, 3, [2]=4, 5, 6};
// Character names can be associated with enumeration constants
// using arrays with designators:
enum { RED, GREEN, BLUE };
const char *nm[] = {
[RED] = "red",
[GREEN] = "green",
[BLUE] = "blue",
};
}
q1, q2, q3, q4 네 선언이 모두 같은 배열을 만든다는 점이 이 예시의 핵심이에요. 중괄호를 완전히 붙인 형태, 괄호를 생략한 형태, 부분 지정자를 섞은 형태가 결국 같은 값을 만들어내죠.
더 알아보기
- 배열 타입 자체의 선언과 다양한 종류(고정 크기, VLA, 미지수 크기)는 배열 선언 문서에서 볼 수 있어요.
- 스칼라, struct, union 초기화는 각각의 초기화 문서에서 다뤄요.
- cppreference의 배열 초기화 원문을 참고할 수 있어요.