문제의 그 코드

길이가 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

+ Recent posts