예전(2.4.x) 리눅스 커널의 double linked list 관련 소스를 보다보면 약간 당황스런 코드를 접하게 된다.

/**
 * list_entry - get the struct for this entry
 * @ptr:        the &struct list_head pointer.
 * @type:       the type of the struct this is embedded in.
 * @member:     the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member)     ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

이 매크로는 어떤 의미일까??  매크로를 보기 편하게 분해 해 보면.

(  (type *)   ( (char *)(ptr) - (unsigned long)(&((type *)0)->member))  )

빨간색에서 파란색을 뺀 값을 type의 포인터 형으로 형변환하는것을 알 수 있다. 좀 더 자세히 풀면,
char 포인터형인 ptr에서  시작주소가 0인 type의 맴버변수인 member의 주소를 unsigned long형태로 빼고 있다.

그림으로 살펴보면 아래와 같은데.


주소가 0인 type 구조체의 맴버변수인  member의 주소는 결국 type구조체에서 member의 offset을 의미한다.
종합해 보면 ptr은 type구조체의 member를 가리키고 있는데 여기에서 type 구조체내의 member의 offset을 뺀것을 의미한다.

결국 원하는 것은 member의 주소를 가지고 type구조체의 시작주소를 얻고 싶은것이다.
list_entry 구조체를 사용하면 member list에 엮여 있는 type구조체의 시작주소를 얻을 수 있고, 시작주소를 얻는다는 것은 type구조체의 모든 맴버변수에 접근할 수 있다는 것이다.

지금까지 어렵게 list_entry 매크로를 언급한 이유는 offetof 매크로를 설명하기 위함인데..

offsetof 는 표준 라이브러리 <stddef.h> 에서 제공하고 있는 매크로로 어떠한 맴버변수가 구조체안에서 어떤 위치에 있는 지 알려준다. offsetof와 sizeof연산자를 활용하면 어떤 구조체가 메모리에 배치되는 모양을 완벽하게 그려낼 수 있다.

사용법은 offsetof(구조체형, 맴버명) 을 입력하면 size_t형의 정수로 맴버의 구조체 상의 배치되어 있는 offset을 알려준다.

struct foo 
{
	char c;
	short int si;
	double d;
};


int main()
{
	printf("%lu\n", (unsigned long)sizeof(struct foo));
	printf("%lu\n", (unsigned long)sizeof(short int));
	printf("%lu\n", (unsigned long)offsetof(struct foo, si));
	printf("%lu\n", (unsigned long)sizeof(double));
	printf("%lu\n", (unsigned long)offsetof(struct foo, d));
	
	return 0;
}

위 예제의 결과는 임플리멘테이션에 따라 다르게 나올 것이다.

다시 list_struct로 돌아와서 offsetof연산자를 이용하면 위 매크로는 좀더 단순하게 바꿀 수 있다.

((type *)((char *)(ptr)-(unsigned long)(offsetof(struct type, member))))

ps. 실제로 offsetof는 아래와 같이 정의되어 있다.

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



저작자 표시 비영리 변경 금지
신고
Write your message and submit
« PREV : 1 : 2 : 3 : 4 : 5 : 6 : ··· : 14 : NEXT »