리눅스 커널 코드에서 많이 활용되는 매크로 함수를 소개 해보려고 합니다.
처음에 봤을때 좀 난해한 느낌이 들기도 했습니다. 그런만큼 꼭 포스팅 해보고 싶은 녀석이기도 했습니다.
바로 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)->m은 0+(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를 통해서 우리는 B가 Block으로부터 4만큼 떨어져 있는 것을 알 수 있습니다.
그럼 현재의 멤버변수의 위치인 B의 위치 에서 방금 구한 B가 구조체 Block으로부터 떨어져 있는 거리를 빼면?
B라는 멤버변수를 가지는 구조체의 위치를 알 수 있습니다.