Array declaration
배열은 특정 요소 타입 을 가진 객체들의 연속적으로 할당된 비어 있지 않은 시퀀스로 구성된 타입입니다. 이러한 객체들의 개수(배열 크기)는 배열 수명 동안 절대 변경되지 않습니다.
목차 |
구문
배열 선언의 선언 문법 에서, type-specifier 시퀀스는 element type (완전한 객체 타입이어야 함)을 지정하며, declarator 는 다음 형태를 가집니다:
[
static
(선택 사항)
한정자
(선택 사항)
표현식
(선택 사항)
]
속성-명세-시퀀스
(선택 사항)
|
(1) | ||||||||
[
한정자
(선택 사항)
static
(선택 사항)
표현식
(선택 사항)
]
속성-명세-시퀀스
(선택 사항)
|
(2) | ||||||||
[
한정자
(선택 사항)
*
]
속성-명세-시퀀스
(선택 사항)
|
(3) | ||||||||
| expression | - | 쉼표 연산자 를 제외한 모든 표현식, 배열의 요소 수를 지정함 |
| qualifiers | - |
const
,
restrict
, 또는
volatile
한정자의 모든 조합, 함수 매개변수 목록에서만 허용됨; 이는 이 배열 매개변수가 변환되는 포인터 타입을 한정함
|
| attr-spec-seq | - | (C23) 속성 의 선택적 목록, 선언된 배열에 적용됨 |
float fa[11], *afp[17]; // fa는 11개의 float로 이루어진 배열입니다 // afp는 float에 대한 17개의 포인터 배열입니다
설명
배열 타입에는 여러 가지 변형이 있습니다: 알려진 상수 크기의 배열, 가변 길이 배열, 그리고 크기를 알 수 없는 배열이 있습니다.
상수로 알려진 크기의 배열
만약 expression 이 배열 선언자에서 정수 상수 표현식 이고 값이 0보다 큰 경우 그리고 요소 유형이 알려진 상수 크기를 가진 유형인 경우(즉, 요소가 VLA가 아님) (C99부터) , 해당 선언자는 상수로 알려진 크기의 배열을 선언합니다:
int n[10]; // 정수 상수는 상수 표현식입니다 char o[sizeof(double)]; // sizeof는 상수 표현식입니다 enum { MAX_SZ=100 }; int n[MAX_SZ]; // enum 상수는 상수 표현식입니다
상수로 알려진 크기의 배열은 배열 초기화 를 사용하여 초기값을 제공할 수 있습니다:
int a[5] = {1,2,3}; // int[5]을 선언하고 1,2,3,0,0으로 초기화 char str[] = "abc"; // char[4]을 선언하고 'a','b','c','\0'으로 초기화
|
함수 매개변수 목록에서, 배열 선언자 내에 추가적인 구문 요소들이 허용됩니다:
각
함수 호출
에서 배열 매개변수가
void fadd(double a[static 10], const double b[static 10]) { for (int i = 0; i < 10; i++) { if (a[i] < 0.0) return; a[i] += b[i]; } } // fadd 호출은 컴파일 타임 경계 검사를 수행할 수 있으며 // 10개의 double을 미리 가져오는 등의 최적화도 허용합니다 int main(void) { double a[10] = {0}, b[20] = {0}; fadd(a, b); // 정상 double x[5] = {0}; fadd(x, b); // 정의되지 않은 동작: 배열 인수가 너무 작음 } 만약 qualifiers 가 존재한다면, 이들은 배열 매개변수 타입이 변환되는 포인터 타입을 한정합니다: int f(const int a[20]) { // 이 함수에서 a는 const int* 타입을 가집니다 (const int에 대한 포인터) } int g(const int a[const 20]) { // 이 함수에서 a는 const int* const 타입을 가집니다 (const int에 대한 const 포인터) }
이는 일반적으로
void fadd(double a[static restrict 10], const double b[static restrict 10]) { for (int i = 0; i < 10; i++) // 루프는 언롤링되고 재정렬될 수 있음 { if (a[i] < 0.0) break; a[i] += b[i]; } } 가변 길이 배열만약 expression 이 integer constant expression 이 아니라면, 선언자는 가변 크기 배열을 위한 것입니다. 제어 흐름이 선언을 지날 때마다, expression 이 평가되며(항상 0보다 큰 값으로 평가되어야 함), 배열이 할당됩니다(이에 상응하여 VLA의 lifetime 은 선언이 범위를 벗어날 때 종료됩니다). 각 VLA 인스턴스의 크기는 수명 동안 변경되지 않지만, 동일한 코드를 다시 지날 때는 다른 크기로 할당될 수 있습니다.
이 코드 실행
#include <stdio.h> int main(void) { int n = 1; label:; int a[n]; // 10번 재할당되며, 매번 다른 크기를 가짐 printf("The array has %zu elements\n", sizeof a / sizeof *a); if (n++ < 10) goto label; // VLA의 범위를 벗어나면 수명이 종료됨 }
크기가
가변 길이 배열과 그것들로부터 파생된 타입(이에 대한 포인터 등)은 일반적으로 "가변 수정 타입"(VM)으로 알려져 있습니다. 모든 가변 수정 타입의 객체는 블록 범위나 함수 프로토타입 범위에서만 선언될 수 있습니다. extern int n; int A[n]; // 오류: 파일 범위 VLA extern int (*p2)[n]; // 오류: 파일 범위 VM int B[100]; // OK: 상수 크기를 아는 파일 범위 배열 void fvla(int m, int C[m][m]); // OK: 프로토타입 범위 VLA VLA는 자동 또는 할당된 저장 기간을 가져야 합니다. VLA에 대한 포인터는 정적 저장 기간을 가질 수 있지만, VLA 자체는 그럴 수 없습니다. 어떤 VM 유형도 링크를 가질 수 없습니다. void fvla(int m, int C[m][m]) // OK: 블록 범위/자동 지속 시간 VLA 포인터 { typedef int VLA[m][m]; // OK: 블록 범위 VLA int D[m]; // OK: 블록 범위/자동 지속 시간 VLA // static int E[m]; // 오류: 정적 지속 시간 VLA // extern int F[m]; // 오류: 링크를 가지는 VLA int (*s)[m]; // OK: 블록 범위/자동 지속 시간 VM s = malloc(m * sizeof(int)); // OK: s는 할당된 저장 공간의 VLA를 가리킴 // extern int (*r)[m]; // 오류: 링크를 가지는 VM static int (*q)[m] = &B; // OK: 블록 범위/정적 지속 시간 VM} } 가변 수정 타입은 구조체나 공용체의 멤버가 될 수 없습니다. struct tag { int z[n]; // 오류: VLA 구조체 멤버 int (*y)[n]; // 오류: VM 구조체 멤버 }; |
(C99 이후) |
|
컴파일러가 매크로 상수 __STDC_NO_VLA__ 를 정수 상수 1 로 정의하는 경우, VLA 및 VM 타입은 지원되지 않습니다. |
(C11부터)
(C23까지) |
|
컴파일러가 매크로 상수 __STDC_NO_VLA__ 를 정수 상수 1 로 정의하는 경우, 자동 저장 기간을 가진 VLA 객체는 지원되지 않습니다. 할당된 저장 기간을 가진 VM 타입과 VLA에 대한 지원은 필수입니다. |
(C23부터) |
크기를 알 수 없는 배열
만약
expression
이 배열 선언자에서 생략되면, 이는 크기를 알 수 없는 배열을 선언합니다. 함수 매개변수 목록(이러한 배열이 포인터로 변환되는 경우)과
initializer
가 사용 가능한 경우를 제외하고, 이러한 타입은
incomplete type
입니다
(크기가 지정되지 않은 VLA는
*
를 크기로 선언되며 완전한 타입임을 유의하세요)
(C99부터)
:
extern int x[]; // x의 타입은 "크기를 알 수 없는 int 배열"입니다 int a[] = {1,2,3}; // a의 타입은 "3개의 int 배열"입니다
|
struct 정의 내에서, 크기가 알려지지 않은 배열은 (다른 이름 있는 멤버가 최소 하나 이상 존재하는 경우) 마지막 멤버로 나타날 수 있으며, 이 경우 flexible array member 로 알려진 특별한 경우입니다. 자세한 내용은 struct 를 참조하십시오: struct s { int n; double d[]; }; // s.d is a flexible array member struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // as if d was double d[8]
|
(C99부터) |
한정자
|
배열 타입이
|
(C23 이전) |
|
배열 타입과 그 요소 타입은 항상 동일하게 한정된 것으로 간주되지만, 배열 타입은 절대
|
(C23부터) |
typedef int A[2][3]; const A a = {{4, 5, 6}, {7, 8, 9}}; // const int 배열의 배열 int* pi = a[0]; // 오류: a[0]의 타입은 const int* void* unqual_ptr = a; // C23까지는 OK; C23 이후부터는 오류 // 참고: clang은 C89-C17 모드에서도 C++/C23 규칙을 적용함
|
typedef int A[2]; // _Atomic A a0 = {0}; // 오류 // _Atomic(A) a1 = {0}; // 오류 _Atomic int a2[2] = {0}; // OK _Atomic(int) a3[2] = {0}; // OK |
(C11부터) |
할당
배열 타입의 객체는 수정 가능한 lvalue 가 아니며, 비록 그 주소를 취할 수는 있지만 대입 연산자의 좌변에 나타날 수 없습니다. 그러나 배열 멤버를 가진 구조체는 수정 가능한 lvalue이며 대입될 수 있습니다:
int a[3] = {1,2,3}, b[3] = {4,5,6}; int (*p)[3] = &a; // 정상, a의 주소를 취할 수 있음 // a = b; // 오류, a는 배열임 struct { int c[3]; } s1, s2 = {3,4,5}; s1 = s2; // 정상: 배열 멤버를 포함하는 구조체는 대입 가능
배열에서 포인터로의 변환
배열 타입의 모든 lvalue 표현식 은 다음을 제외한 모든 컨텍스트에서 사용될 때
- 주소 연산자 의 피연산자로 사용될 때
-
sizeof의 피연산자로 사용될 때 -
typeof및typeof_unqual의 피연산자로 사용될 때 (C23부터) - 배열 초기화 에 사용되는 문자열 리터럴로 사용될 때
| (C11부터) |
첫 번째 요소에 대한 포인터로 암시적 변환 을 수행합니다. 결과는 lvalue가 아닙니다.
배열이
register
로 선언된 경우, 이러한 변환을 시도하는 프로그램의 동작은 정의되지 않습니다.
배열 타입이 함수 매개변수 목록에서 사용될 때, 해당 포인터 타입으로 변환됩니다: int f ( int a [ 2 ] ) 와 int f ( int * a ) 는 동일한 함수를 선언합니다. 함수의 실제 매개변수 타입이 포인터 타입이므로, 배열 인자를 사용한 함수 호출은 배열-포인터 변환을 수행합니다; 인자 배열의 크기는 호출된 함수에서 사용할 수 없으며 명시적으로 전달되어야 합니다:
#include <stdio.h> void f(int a[], int sz) // 실제로는 void f(int* a, int sz)를 선언함 { for (int i = 0; i < sz; ++i) printf("%d\n", a[i]); } void g(int (*a)[10]) // 배열에 대한 포인터 매개변수는 변환되지 않음 { for (int i = 0; i < 10; ++i) printf("%d\n", (*a)[i]); } int main(void) { int a[10] = {0}; f(a, 10); // a를 int*로 변환하고 포인터를 전달함 g(&a); // 배열에 대한 포인터를 전달함 (크기를 전달할 필요 없음) }
다차원 배열
배열의 원소 타입이 다른 배열일 때, 그 배열을 다차원 배열이라고 합니다:
// 2개의 배열로 이루어진 배열, 각 배열은 3개의 int를 가짐 int a[2][3] = {{1,2,3}, // 2x3 행렬로 볼 수 있음 {4,5,6}}; // 행 우선 배치 방식
배열-포인터 변환이 적용될 때, 다차원 배열은 첫 번째 요소에 대한 포인터로 변환된다는 점에 유의하십시오. 예를 들어, 첫 번째 행에 대한 포인터로 변환됩니다:
int a[2][3]; // 2x3 행렬 int (*p1)[3] = a; // 첫 번째 3-요소 행에 대한 포인터 int b[3][3][3]; // 3x3x3 큐브 int (*p2)[3][3] = b; // 첫 번째 3x3 평면에 대한 포인터
|
다차원 배열은 모든 차원에서 가변적으로 수정될 수 있습니다 (VLA가 지원되는 경우) (C11부터) : int n = 10; int a[n][2*n]; |
(C99부터) |
참고 사항
길이가 0인 배열 선언은 허용되지 않으며, 일부 컴파일러가 확장 기능으로 제공하기도 합니다(일반적으로 C99 이전의 flexible array members 구현으로).
VLA의 크기 expression 에 부작용이 있는 경우, sizeof 표현식의 결과가 그것에 의존하지 않는 경우를 제외하고 부작용이 발생하는 것이 보장됩니다:
int n = 5, m = 5; size_t sz = sizeof(int (*[n++])[m++]); // n은 증가하지만, m은 증가할 수도 있고 증가하지 않을 수도 있음
참고문헌
- C23 표준 (ISO/IEC 9899:2024):
-
- 6.7.6.2 배열 선언자 (p: TBD)
- C17 표준 (ISO/IEC 9899:2018):
-
- 6.7.6.2 배열 선언자 (p: 94-96)
- C11 표준 (ISO/IEC 9899:2011):
-
- 6.7.6.2 배열 선언자 (p: 130-132)
- C99 표준 (ISO/IEC 9899:1999):
-
- 6.7.5.2 배열 선언자 (p: 116-118)
- C89/C90 표준 (ISO/IEC 9899:1990):
-
- 3.5.4.2 배열 선언자
참고 항목
|
C++ 문서
참조:
배열 선언
|