C 언어 배열
배열이란?
배열(array)은 동일한 타입의 데이터를 순차적으로 저장하는 자료 구조다
C 언어에서는 배열을 통해 여러 개의 값을 하나의 이름으로 관리할 수 있으며, 각 요소는 인덱스를 통해 접근할 수 있다
예를 들어 `int numbers[5];`라고 하면, 정수형 데이터를 5개 저장할 수 있는 메모리 공간이 확보되며, `numbers[0]`부터 `numbers[4]`까지 인덱스로 접근할 수 있다
C에서 배열은 다음과 같이 선언한다
<자료형> <배열명>[크기];
int scores[3]; // 정수형 배열 3개
float prices[10]; // 실수형 배열 10개
char name[20]; // 문자형 배열 20개 (문자열 저장 가능)
기본적으로 배열의 크기는 정수 상수로만 지정할 수 있으며, 컴파일 타임에 크기가 결정되어야 한다
이러한 배열은 정적 배열(static array) 또는 정적 크기의 배열이라고 부른다
이제 이러한 기본 개념을 바탕으로, C에서 배열이 어떻게 메모리에 할당되고 관리되는지 살펴보자
정적 배열 선언과 메모리 할당 방식
C에서는 배열을 선언할 때 new 키워드 없이 선언이 가능하다
배열을 선언할 때 new 없이 선언할 수 있는 이유는 컴파일 타임에 배열 크기가 결정되며, 자동으로 스택 메모리에 할당되기 때문이다
즉, 프로그램이 실행될 때 스택 영역에 공간이 확보되므로 별도의 동적 메모리 할당이 필요하지 않다
#include <stdio.h>
int main(void)
{
/* new 없이 정적 배열 선언 */
int numbers[5] = {1, 2, 3, 4, 5};
/* 배열 출력 */
for (size_t i = 0; i < 5; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
================ 실행 결과 ================
1 2 3 4 5
지역 변수로 선언된 배열은 자동으로 관리된다
위 코드에서 numbers[5] 배열은 지역 변수로 선언되었으므로, 함수가 실행되는 동안 스택 메모리에 할당된다
스택 메모리에 할당된 변수는 함수가 종료될 때 자동으로 해제되기 때문에, 별도의 메모리 해제 작업이 필요 없다
#include <stdio.h>
void create_array(void)
{
int numbers[3] = {10, 20, 30}; /* 지역 변수 배열 (스택 메모리 할당) */
}
int main(void)
{
create_array();
/* create_array()에서 선언된 numbers 배열은 함수가 종료되면서 자동 해제됨 */
return 0;
}
동적 메모리가 필요하면 malloc()을 사용한다
만약 동적으로 배열 크기를 실행 중에 조정해야 하거나, 함수가 종료되어도 데이터가 유지되어야 한다면 동적 메모리 할당을 사용해야 한다
C에서는 malloc()을 사용하여 힙 메모리에 배열을 할당할 수 있다. 할당하면서 동적으로 메모리 크기를 지정할 수 있다
또한 자동으로 해제되지 않으므로 사용 후 반드시 free()를 호출해야 한다
#include <stdio.h>
#include <stdlib.h> /* malloc(), free() 사용을 위한 헤더 */
int main(void)
{
/* 배열 크기를 동적으로 할당 */
size_t size = 5;
int *numbers = (int *)malloc(size * sizeof(int));
/* malloc()이 정상적으로 메모리를 할당했는지 확인 */
if (numbers == NULL) {
printf("메모리 할당 실패!\n");
return 1;
}
/* 배열 초기화 및 출력 */
for (size_t i = 0; i < size; i++) {
numbers[i] = (int)(i + 1); /* 1, 2, 3, 4, 5 */
printf("%d ", numbers[i]);
}
printf("\n");
/* 동적 메모리 해제 */
free(numbers);
return 0;
}
================ 실행 결과 ================
1 2 3 4 5
전역 변수로 선언된 배열의 메모리 저장 방식
C에서 배열을 전역 변수로 선언하면 프로세스의 데이터 영역에 저장된다. 즉, 프로그램이 실행되는 동안 할당된 메모리를 유지하며, 프로그램이 종료될 때까지 해제되지 않는다
그리고 이것에 대해서 생각하면서 처음 알게된 것이 있는데 BSS 영역이다. 초기화되지 않은 전역 변수는 이곳에 저장되며, 0으로 자동 초기화된다고 한다. 현재 내용에서 벗어나기 때문에 이런게 있구나, 넘어가고 필요할 때 학습하면 될 듯 하다
#include <stdio.h>
/* 전역 변수로 배열 선언 (초기화된 경우) → 데이터 영역 */
int numbers[5] = {1, 2, 3, 4, 5};
/* 전역 변수로 배열 선언 (초기화하지 않은 경우) → BSS 영역 */
int uninitialized_array[5];
void print_numbers(void)
{
for (size_t i = 0; i < 5; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
}
int main(void)
{
/* 전역 변수 사용 */
print_numbers();
/* 전역 배열 값 변경 */
numbers[0] = 100;
print_numbers();
return 0;
}
================ 실행 결과 ================
1 2 3 4 5
100 2 3 4 5
배열의 크기를 구하는 방법
C에서는 배열의 길이를 직접 알 수 있는 방법이 없으므로, 매크로를 이용해 배열의 요소를 구하는 방법을 자주 사용한다
#include <stdio.h>
/* 배열 크기를 구하는 매크로 */
#define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))
int main(void)
{
/* 정수형 배열 선언 */
int numbers[] = {10, 20, 30, 40, 50};
/* 배열의 크기 출력 */
printf("배열의 크기: %d\n", ARRAY_LENGTH(numbers));
return 0;
}
================ 실행 결과 ================
배열의 크기: 5
원리는 다음과 같다
- sizeof(arr)은 배열의 전체 크기를 반환한다 (4 * 5 = 20)
- sizeof(arr[0])은 요소 하나의 크기를 반환한다 (int = 4byte)
- 따라서 (sizeof(arr) / sizeof(arr[0]))은 배열 전체 크기를 개별 요소 크기로 나누어 계산한다
함수에 배열을 전달하는 경우
C에서 배열을 함수의 매개변수로 넘길 경우, 배열의 크기를 알 수 없다. 이유는 배열이 포인터로 변환되기 때문이다
#include <stdio.h>
#define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))
/* 배열을 함수로 전달하는 예제 */
void print_array(int arr[], int size)
{
/* sizeof(arr)를 사용하면 배열 크기를 알 수 없음 (포인터 크기만 반환) */
printf("sizeof(arr) in function: %zu\n", sizeof(arr));
/* 배열 요소 출력 */
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int numbers[] = {1, 2, 3, 4, 5};
printf("sizeof(numbers) in main: %zu\n", sizeof(numbers));
/* 배열 크기를 별도로 전달해야 함 */
print_array(numbers, ARRAY_LENGTH(numbers));
return 0;
}
================ 실행 결과 ================
sizeof(numbers) in main: 20
sizeof(arr) in function: 8
1 2 3 4 5
위와 같은 결과가 나온다. 따라서 C에서는 다음과 같이 배열의 크기를 함수에 매개변수로 전달하는 방법을 사용한다
#include <stdio.h>
#define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))
/* 배열을 함수로 전달할 때 크기도 함께 전달 (size_t 사용) */
void print_array(int arr[], size_t size)
{
printf("배열의 크기 (함수 내부에서 전달된 size 사용): %zu\n", size);
/* 배열 요소 출력 */
for (size_t i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int numbers[] = {1, 2, 3, 4, 5};
/* 배열 크기 계산 (size_t 타입 사용) */
size_t size = ARRAY_LENGTH(numbers);
printf("배열의 크기 (main에서 계산): %zu\n", size);
/* 배열과 배열 크기를 함수에 전달 */
print_array(numbers, size);
return 0;
}
Reference
Storage-class specifiers - cppreference.com
Array declaration - cppreference.com