Linux Kernel의 Generic List, Tree 등은 노드 내에 struct list_head 와 같이 양방향으로 연결하는 포인터만 가진구조체를 삽입하여 구현하고 있다. 그렇다면 이러한 멤버 변수만을 통해서 부모 구조체로 변환할 수 있을까? 커널 개발자들은 이 문제의 해결책을 영리하게도 C언어의 트릭에서 찾았다! 바로 구조체 안의 멤버들 간의 오프셋은 컴파일 시기에 고정된다는 점을 이용한 것이다.
아래 container_of() 매크로는 이 원리를 이용해 부모 구조체의 주소를 쉽게 찾아준다.
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member));})
typeof() : ()안의 변수의 Type을 리턴해주는 Compiler 내에 내장된 매크로 함수
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
https://acanel.xyz/public/langC/1 참조원리 : 부모 구조체에서
struct list_head의 offset을 구한 후, list_head의 포인터에서 빼주면 부모 구조체 포인터가 나온다. 위 코드가 잘 읽히지 않는다면 실제 값으로 대입하여 확인해보면 이해하기 쉬워진다. 예시의 코드를 적용해보자. 인자로 들어가는 세 값은 다음과 같다.
ptr : 멤버의 실제 포인터. 여기서는 struct list_head 의 포인터
type : ptr 을 포함하고 있는 부모 구조체의 타입. 여기서는 struct album_struct
member : 부모 구조체에 포함된 struct list_head 멤버명. 여기서는 list이 값들을 대입을 해보면 아래와 같이 변경된다.
container_of(ptr, struct album_struct, list)
==>
(struct album_struct *)((char *)ptr) - ((size_t) &((struct album_struct *)0)->list)
즉 실제 메모리를 가리키고 있을
struct list_head 포인터 타입의 ptr 에서 부모 구조체의 주소까지의 오프셋을 구하여 빼면 실제 부모 구조체의 주소를 도출할 수 있다.