가변 배열 멤버(Flexible Array Member)란?
C 언어에서 가변 배열 멤버란 구조체(structure) 내부에 배열의 크기를 지정하지 않은 채 선언하는 배열을 말합니다. 이렇게 선언된 배열은 컴파일 시점에 크기가 고정되지 않고, 실행 중 필요한 만큼 메모리를 할당해 사용할 수 있어 크기가 '유연한(flexible)' 성격을 가집니다.
단, 가변 배열 멤버는 반드시 구조체의 마지막 멤버로 선언되어야 합니다. 이는 컴파일러가 배열 시작 위치 뒤쪽에 필요한 만큼의 연속된 메모리를 붙여 할당할 수 있도록 하기 위한 필수 조건입니다.
사용 규칙
- 가변 배열 멤버는 구조체(클래스)의 마지막 멤버여야 합니다.
- 구조체에는 가변 배열 멤버 외에 최소 하나 이상의 다른 명명된 멤버가 존재해야 합니다.
- 실제 메모리는 일반적으로
sizeof(구조체) + 배열 크기만큼malloc()으로 동적 할당합니다.
예제 코드
아래 예제는 직원 정보를 저장하는 구조체에 가변 배열 멤버를 활용해, 이름 길이에 맞춰 메모리를 동적으로 할당하는 방법을 보여줍니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// employee 타입의 구조체.
// 가변 배열 멤버 외에 최소 하나 이상의 명명된 멤버를 포함해야 합니다.
struct employee
{
int emp_id;
int name_len;
int emp_size; // 'emp_size' 변수는 가변 문자 배열 emp_name[]의 크기를 저장하는 데 사용됩니다.
char emp_name[]; // 가변 배열 멤버 emp_name[]은 반드시 구조체의 마지막 멤버여야 합니다.
};
struct employee *createEmployee(struct employee *e, int id, char a[])
{
// 구조체 크기 + 이름 길이만큼 메모리 할당
e = (struct employee *)malloc(sizeof(*e) + sizeof(char) * strlen(a));
e->emp_id = id;
e->name_len = strlen(a);
// 사용자가 전달한 배열 a[]의 크기에 맞게 emp_name에 복사
strcpy(e->emp_name, a);
return e;
}
void printEmployee(struct employee *e) // 직원 정보를 출력하는 함수
{
printf("Employee_id : %d\n" "Employee Name : %s\n" "Name Length: %d\n\n", e->emp_id, e->emp_name, e->name_len);
}
int main()
{
struct employee *e1 = createEmployee(e1, 26, "Ram");
struct employee *e2 = createEmployee(e2, 53, "Madhu");
printEmployee(e1);
printEmployee(e2);
printf("Size of structure Employee: %lu\n", sizeof(struct employee));
return 0;
}
실행 결과
Employee_id : 26
Employee Name : Ram
Name Length: 3
Employee_id : 53
Employee Name : Madhu
Name Length: 5
Size of structure Employee: 12
결과 분석
실행 결과에서 주목할 부분은 구조체 자체의 크기가 12바이트라는 점입니다. 이는 int형 멤버 세 개(emp_id, name_len, emp_size)의 크기(4바이트 × 3)에 해당하며, 가변 배열 멤버 emp_name[]은 구조체 크기 계산에 포함되지 않습니다.
대신 createEmployee() 함수에서 malloc()을 호출할 때 sizeof(*e) + sizeof(char) * strlen(a)를 사용해 구조체 크기에 이름 길이만큼의 추가 공간을 함께 할당합니다. 덕분에 "Ram"(3글자)과 "Madhu"(5글자)처럼 길이가 서로 다른 문자열도 하나의 구조체 정의만으로 손쉽게 처리할 수 있습니다.
가변 배열 멤버의 장점
- 메모리 효율성: 데이터 크기에 딱 맞게 할당하므로 공간 낭비가 없습니다.
- 데이터 지역성: 구조체 헤더와 배열 데이터가 연속된 메모리에 위치해 캐시 효율이 좋습니다.
- 관리 편의성: 별도의 포인터와 추가 할당 없이 한 번의
malloc()호출로 처리할 수 있습니다.