[Q/A] container_of() 매크로

QnA 2010. 2. 27. 05:47

커널 소스를 보다가 list_entry() 매크로를 자세히 보게되었다.

#define list_entry(ptr,type,member) container_of(ptr,type,member)

이하. containger_of() 매크로를 보던중, 관련 스레드이다.


 

embedding list를 구현할때 사용하는 기법(?)입니다.(BSD의 sys/queue.h에서 처음 등장했고요, Linux는 몇가지 다른 버전이 있더군요)

흔히 보통의 자료구조 교재에서 접하는 linked list등의 구현 방법은, 구조체에 다음 노드의 "시작"점을 포인팅하게 구현하죠.
이 방법은 직관적이고 이해하기 쉽지만, 일반 타입에 대해서 만들려면 노가다가 필요합니다.(c++ 등은 템플릿이 있어서 상관없고요)

그래서 나온 또 다른 방법이, link 정보를 구조체 안에 있는 변수에 넣어두고(위에 예제에선 member), 이 변수들이 다른 구조체에 접근할 수 있도록 하는 것입니다. 이렇게 하면.. 실제로 리스트에서는 다음 노드의 링크 변수에만 접근할 수 있습니다.(member)

그러나 우리가 원하는 것은 링크 변수가 아닌, 링크 변수를 가진 구조체의 시작점이죠. 이 구조체의 시작점을 계산하는 매크로가 list_entry입니다. gcc extension에서는 offsetof라는 연산자를 지원하기도 하는데, 그렇지 않을 경우에는 가상의 구조체 만든 후(0을 구조체 타입으로 캐스팅했죠) 링크 변수의 주소에서 링크 변수의 구조체 시작점에서의 오프셋을 뺍니다. 이렇게 계산하면 실제 구조체의 시작점 주소가 반환되겠죠.




[Q]

보통
#define MAX(a,b) ( (a>b) ? (a) : (b) )

이런식으로 매크로 함수를 정의합니다.
하지만 약간 복잡한 형태의 함수의 return값을 명시적으로 주고자 했을때 어떻게 해야합니까?

#define func(a) {\

//bla~ bla~ bla; \

/*return*/ result;
}

이렇게 하게되면 {}로 둘러쌓인 부분이 result로 평가 받게 되나요?

리눅스 커널에 있는 container_of 라는 매크로를 풀어 보니 대략 이런 류의 코드가 되네요.

대략 어떻게 되는거다 라는건 알겠는데..

이거 원래 C 문법에 맞는 코드 인가요?

어떤 식의 처리가 되는거죠?



[A]

====================================================================================

#define func(a) ({\ 

//bla~ bla~ bla; \

/*return*/ result; \
})

요런식으로 하면 result가 ({ blahblah...; result; }) 표현의 평가값이 되니까

ret = func(something);

식으로 사용할 수는 있을 것 같습니다만, 아무래도 이건 좀...-.-a

====================================================================================

http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html

GCC extension이라는 걸로 봐서 표준은 아닌 듯 합니다. -pedantic 옵션으로 컴파일 하니까 뭐라뭐라 그러네요. 하지만 다음과 같이 정의하는 건 표준 C 문법에 부합합니다.

#define func(a) ( \
tmp1 = another_func1(a), \
tmp2 = another_func2(a), \
result = tmp1 + tmp2, \
/* return */ result )

...
int tmp1, tmp2, result; // for use by macro func()
...
ret = func(a);

즉, 선언'문' 없이, '식'으로만 이뤄져 있으면 GCC뿐 아니라 모든 ANSI C 컴파일러에서 문제가 없게 됩니다... 만, 원하시는 것에는 이프로 부족하지 않을까 싶네요-

====================================================================================

매크로는 확장(expansion)될 뿐 그 이상도 이하도 아닙니다.
소스에 그대로 대치되는 것입니다.
실제로 전처리기가 매크로를 다 풀어헤친(?) 상태의 소스를 컴파일하게 됩니다.
매크로의 리턴 값이라고 하면 말이 앞뒤가 안맞는 느낌이 드네요.

====================================================================================

Submitted by 익명 사용자 on 금, 2005/10/14 - 1:06am.

동의합니다. 8)

매크로에서의 리턴값이란거부터가 이미 말이 안되는거죠.
스스로 pre-processor입장이 되어서 매크로가 삽입되는 과정을
시뮬레이션 해보시면 잘 이해할 수 있을겁니다.

그럼 매크로에서 return 문을 쓰게 되었을 때 매크로가 값을
리턴하는게 아니라 매크로가 삽입된 함수가 값을 리턴하며
끝나게 된다
는것도 이해가 가실줄로 압니다.

====================================================================================

몇가지 정리하면 다음과 같습니다.

첫째, 표준에 따라, statement를 expression이라고 할 수 없습니다. 물론 statement의 정의에 따라, expression-statement가 될 수 있기는 하지만, 모든 statement가 expression이라고 할 수 없습니다. 따라서

인용:

과연 { ~~~~~; ~~~~; result;}의 평가값이 result가 되는냐는 것이죠

는, 표준에 따라 잘못된 질문입니다.

둘째, 복잡한? 매크로를 만들고 싶다면 inline function을 쓰기 바랍니다. inline function을 쓰는 것이 여러모로 편리하고, 안전합니다. 다음과 같이 쓰면 좋습니다:

static inline void
foo(void)
{
...
}

세째, gcc C 언어 확장 기능으로, (즉, 표준이 아니며, 다른 컴파일러에서 지원하지 않는..) compound statement (중괄호로 둘러싼 여러 statement)를 expression으로 해석하는 기능이 있습니다. 예를 들면:

({ int y = foo(); int z;
if (y > 0) z = y;
else z = - y;
z; })

로 쓴 것처럼, foo() 리턴 값의 절대값을 얻는 코드를 위와 같이 작성할 수 있습니다. 이 것은 C99 표준에서 inline function이 나오기 전에 매우 유용하게 썼던 기능입니다. expression에서 switch, for 등의 statement를 쓸 수 있는 등의 장점을 얻을 수 있었습니다. 실제로 GNU C library에 포함되어 있는 obstack에 관계된 대부분의 함수가 이 compound-statement expression을 써서 macro로 구현되어 있습니다. 물론 맨 마지막 statement의 값이, 이 compound statement의 값이 됩니다.

그런데, 이 기능은 현재 C++에서 쓸 경우, 임시 변수의 destructor가 불리는 순서가 바뀌는 등의 단점이 있기 때문에, C++로 쓸 우려가 있는 코드에서는 이 compound statement expression을 쓰지 말라고 권하고 있습니다.

개인적으로 간단한 수식으로 나올 수 있는 것이 아니라면 do { ... } while (0)을 써서 macro로 만들고, 어떤 값을 리턴할 필요가 있는 macro라면 inline function을 쓰는 것이 좋다고 생각합니다.


====================================================================================

매크로 함수 기능은 대단히 주의깊게 이용되어야만 합니다.

http://groups.google.co.kr/group/comp.os.linux/browse_frm/thread/9690464...

#define toupper(c) (_ctmp=c,islower(_ctmp)?_ctmp-('a'-'A'):_ctmp)

아무 문제 없을것 같은 코드지만

-------------------bug.c-------------------------
#include <linux/ctype.h>
main()
{
printf("%d\n", toupper('x') == toupper('y'));
printf("%d\n", toupper('x') == toupper('X'));
}
-------------------------------------------------

he program below prints:
1
1
but should print:
0
1

====================================================================================

매크로와 함수는 각각 할 일이 있는 거라고 봅니다. 함수 호출의 오버헤드가 아주 중요한 요인으로 작용하는 경우가 아니라면, 함수를 쓰는 것이 맞다고 생각하구요. (그리고 그런 경우는 별로 없더군요..)

말씀하신 상황에서 굳이 매크로를 쓰고 싶으시다면 SOME_MACRO(input, output) 처럼 매크로를 정의해서 argument 에 바로 값을 넣도록 만드는 것이 옳은 방법이라고 생각합니다.




출처 : http://kldp.org/node/58409


WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,