배열 초기화

배열 초기화 (Array initialization)

배열을 선언하면서 어떤 값으로 채울지 한 번에 정하고 싶을 때가 많아요. 배열 타입의 객체를 초기화하는 방법에는 문자열 리터럴로 채우는 방법과, 중괄호로 감싼 초기화자 목록으로 채우는 방법이 있어요. 이 페이지에서 그 규칙을 정리해 볼게요.

출처: cppreference

본문

배열 타입의 객체를 초기화할 때, 초기화자는 문자열 리터럴(선택적으로 중괄호로 감쌀 수 있음)이거나 배열 멤버들을 위한 초기화자의 중괄호 목록이어야 해요.

= string-literal (1)
= { expression , ... } (2) (until C99)
= { designator(optional) expression , ... } (2) (since C99)
= { } (3) (since C23)
  1. 문자 및 와이드 문자 배열용 문자열 리터럴 초기화자
  2. 배열 요소의 초기화자인 상수(until C99)표현식의 쉼표 구분 목록. 선택적으로 [ constant-expression ] = 형태의 배열 지정자(designator)를 쓸 수 있음 (since C99)
  3. 빈 초기화자(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의 배열 초기화 원문을 참고할 수 있어요.