리눅스 커널 코드에서 많이 활용되는 매크로 함수를 소개 해보려고 합니다.
처음에 봤을때 좀 난해한 느낌이 들기도 했습니다. 그런만큼 꼭 포스팅 해보고 싶은 녀석이기도 했습니다.
바로 offsetof와 container_of 매크로 함수 입니다.


1. offsetof 매크로

특정 구조체에서 해당 변수가 메모리 상에서 어느지점에 위치해 있는지 알 수 있다.

#ifndef offsetof
#define offsetof(s, m) (size_t)&(((s *)0)->m)
#endif

s는 구조체, m는 멤버 변수이다.
(size_t)&(((s*)0)->m) 에서 ((s*)0)은 0번지 주소에 s 구조체가 있다고 가정하는 것이다.
이 구조체는 멤버변수 m을 가리키고 있다.

이함수는 어떻게 작동이 가능할까?
컴퓨터는 메모리에 접근할 때 기본 주소에서 할당 멤버의 offset(일정 거리)를 더해서 나머지 값을 더한다.

예를 들어, m이 어디에 있는지 고정된 값으로 정하지 않는다.
대신 기본주소 (1000)에서 m의 거리인 offset(4)를 더하는 것이다.
m의 주소는 1000+4로 1004가 될 것이다.

다시 함수의 원형을 본다면, (0)->m0+(m의 위치)가 된다. 0+4는 4가 된다.
(m은 4크기를 가진 멤버 변수 라고 가정)

다음의 코드를 살펴보자.

typedef struct TEST
    int a;
    int b;
    int c;
} Test;

Test t;

위와 같이 test 라는 이름의 Test 구조체를 선언했다. 이때 메모리의 구조는 다음과 같다.

순서 메모리 주소 이름
1 1000 번지 구조체 t
2 1000 번지 멤버 변수 a
3 1004 번지 멤버 변수 b
4 1008 번지 멤버 변수 c

구조체 t의 주소와 t의 멤버 변수인 a의 메모리 주소는 같다.
구조체에서 제일 첫번쨰 변수와 그 구조체의 인스턴스의 주소는 항상 같다. (배열, 공용체도 마찬가지)
아래표의 코드에서 세가지 코드는 모두 같은 주소를 가리키고 있다.

순서 메모리 주소 의미 코드
1 1000 번지 구조체 t의 주소 &t
2 1000 번지 멤버 변수 a의 주소 &(t.a)
3 1000 번지 멤버 변수 a의 주소 (char*)&t + offsetof(test, a)

2. container_of 매크로

특정 구조체에서 멤버변수의 주소를 가지고 있을 경우 그 본체의 주소를 반환한다.

#ifndef container_of
#define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *_mptr = (ptr); \
    (type *)( (char *)_mptr - offsetof(type, member) ); })
#endif

구조체의 주소 = (구조체 멤버의 주소 값 - offsetof()를 통한 구조체 멤버의 오프셋)

우선 이함수를 이해하기 위해서는 앞서 offsetof()의 이해를 필요로 합니다.
앞서 설명한 offsetof() 함수가 이해되지 않는다면, 꼭 이해하고 보시기 바랍니다.

container_of 함수는 다음과 같이 세가지 파라미터를 필요로 합니다.

1. 멤버변수의 주소값
2. 구조체 타입
3. 멤버변수 이름

쉽게 설명 하기 위해 예를 들어 보겠습니다. 다음과 Block 이라는 구조체가 있습니다.

멤버 변수 크기
A 4
B 4
C 4

우리가 가지고 있는 정보는 현재 현재위치구조체멤버변수 입니다.
3가지값을 통해 우리가 알고싶은 정보는 구조체의 위치 입니다.

앞서 우리는 이함수가 3가지 파라미터를 필요로 한다고 했습니다.
여기서 우리가 가지고 있는 값들과 파라미터를 매칭 시켜 보겠습니다.

파라미터 화살표 의미
멤버변수의 주소값 ➡️ 현재 멤버변수의 위치
구조체 타입 ➡️ 구조체의 정보
멤버변수 이름 ➡️ 구조체의 멤버변수

정리해보면 위의 표와 같습니다.

이제 구조체멤버변수offsetof()함수를 잘 이용해 봅시다.
우리는 구조체로 부터 해당 멤버 변수가 얼마만큼 떨어져있는지를 바로 offsetof()함수를 통해서 알 수 있습니다.
구조체 Block과 멤버변수 B를 통해서 우리는 BBlock으로부터 4만큼 떨어져 있는 것을 알 수 있습니다.

그럼 현재의 멤버변수의 위치인 B의 위치 에서 방금 구한 B가 구조체 Block으로부터 떨어져 있는 거리를 빼면?
B라는 멤버변수를 가지는 구조체의 위치를 알 수 있습니다.