문제의 그 코드
길이가 0인 배열이 있다. 어? 길이가 0이라...그게 말이 되나? 그 문제의 코드는 다음과 같다.
typedef struct{
int len;
uint8_t data[0];
}data_t;
int main()
{
int data_len = 100;
data_t *d = (data_t *)malloc(sizeof(data_t) + data_len);
if(d != NULL)
{
d->data = data_len;
}
for(int i = 0; i < d->len; i++)
{
d->data[i] = i;
}
return 0;
}
가만 보자..
일단 구조체의 멤버변수 data가 도통 이해가 되질 않는다. 내가 아는 배열 선언도 아니고..
그렇다고 포인터 선언도 아닌데.. 뭐, 굳이 어디다 끼워 맞추자면 포인터에 가깝지 않나 싶기도 하다.
스승님의 말씀에는 위 구조체의 멤버변수를 "유연한 배열 멤버" 라고 한다.
배열로 선언했지만, 마치 동적으로 할당하는 것처럼 사용할 수 있어 "유연한" 이란 단어를 사용하는 것 같다.
실제로 그렇다. 미리 길이를 정하지 않았다. 흔히 우리는 배열의 길이를 미리 정하고 사용한다.
애초에 그렇게 사용하지 않으면 안된다. 컴파일 오류가 발생하기 때문이다.
하지만 위 방법은 배열의 길이가 있다. 0이지만.. 저 0은 단순히 숫자 0이 아닌 것 같다.
뭐랄까.. 空 이라고나 할까. 비어있다. 비어있는 것이지 없는게 아니다. 내가 채워 넣는것이 곧 길이이다.
데니스 리치가 불교 신자는 아니겠지?
여튼, 위 코드에서 메모리 할당하는 라인을 살펴보자.
data_t *d = (data_t *)malloc(sizeof(data_t) + data_len);
이 코드를 보면 감이 좀 온다. 아, 구조체 사이즈만큼 할당하고, 그 뒤로 1byte의 data_len 길이를 추가로 할당하는구나.
그럼 메모리의 모양은 대충 이런 느낌이겠구나.
[------------------- 할당된 메모리 블록 (총 4 + 10 = 14 바이트) --------------------]
[ len (4 바이트) ] [ data[0] ] [ data[1] ] [ data[2] ] ... [ data[9] ]
[ uint32_t ] [ uint8_t ] [ uint8_t ] [ uint8_t ] ... [ uint8_t ]
상당히 메모리스럽게 접근해야 비로소 조금 마음에 와닿는다. 정말 신기하고도 재밌다.
이렇게 사용하면, 구조체 단위로 데이터를 생성하고 처리할 수 있다. 안에 배열의 데이터뿐만 아니라,
길이, 이름, 속성등 여러 attributes를 포함할 수 있어 뭔가 더 유용하게 사용할 수 있을 것 같다.
스승님 말씀
- 길이가 0인 배열은 유연한 배열 멤버 또는 구조체 해킹 기법의 일부입니다.
- 구조체 자체에는
data
를 위한 메모리 공간이 할당되지 않습니다. - 주로 가변 길이 데이터를 구조체 헤더와 연속적인 메모리 블록으로 관리하기 위해 사용됩니다.
malloc()
등을 사용하여 구조체를 할당할 때, 필요한 데이터 크기만큼 추가적인 메모리 공간을 함께 할당하여data
배열처럼 접근합니다.sizeof(os_msg_header)
는data
배열의 크기를 포함하지 않습니다.
uint8_t data[0]
vs uint8_t *data
차이점
특징 | uint8_t data[0] |
uint8_t *data |
---|---|---|
메모리 할당 | 구조체 자체에는 데이터 공간 없음 | 포인터 저장 공간 할당, 데이터 공간은 별도 할당 필요 |
데이터 위치 | 구조체 뒤에 연속적인 메모리 블록으로 관리 | 별도의 메모리 블록에 위치 가능 |
할당 방식 | 구조체와 데이터 공간을 한 번에 malloc() |
구조체와 데이터 공간을 각각 malloc() |
메모리 관리 | 하나의 free() 호출로 전체 메모리 해제 가능 |
구조체와 데이터 공간을 각각 free() 해야 함 |
sizeof |
데이터 크기 미포함 | 포인터 크기 포함 |
사용 목적 | 헤더와 가변 길이 데이터를 밀접하게 결합 | 가변 길이 데이터를 동적으로 할당 및 관리 |
uint8_t data[0]
를 사용한 가변 길이 데이터 처리 방식
malloc(sizeof(structure) + data_length)
를 사용하여 구조체 헤더와 가변 길이 데이터를 하나의 연속적인 메모리 블록으로 할당합니다.- 구조체 포인터를 통해 멤버에 접근하고,
data
멤버는 구조체 헤더 바로 다음의 메모리 주소를 기준으로 배열처럼 접근하는 트릭을 사용합니다. vd->data[i]
는vd
포인터에서 구조체 크기만큼 offset된 주소에서i
번째 바이트에 접근하는 방식으로 동작합니다.
결론: uint8_t data[0]
는 구조체와 가변 길이 데이터를 효율적으로 결합하여 관리하기 위한 C 언어의 유용한 (하지만 약간은 트릭에 가까운) 기법이며, uint8_t *data
는 별도의 메모리 할당을 통해 더 유연하게 가변 길이 데이터를 처리하는 표준적인 방식입니다.
'C' 카테고리의 다른 글
나만의 assert 만들기 (0) | 2025.04.14 |
---|---|
Object file.. (0) | 2025.01.14 |
세그먼트 (0) | 2023.04.13 |
Why is the number of array elements smaller than minus value? (0) | 2023.04.06 |
array length as parameter (0) | 2022.11.09 |