offsetof 매크로에 대해

icanfly의 이미지

얼마전에 offsetof 매크로란게 있다는걸 알았습니다.

class또는 struct 내부에 있는 멤버와 그 class,struct와의 메모리 오프셋

을 구할때 쓴다는 식으로 막연하게 밖에 이해가 안되는데요. 소스를 한번 열

어 봤더니 VC++, MinGW에서 아래와 같이 구현되 있더군요.

VC++ 6.0

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

MinGW 3.x

#ifndef __cplusplus
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#else /* C++ */
/* The reference cast is necessary to thwart an operator& that might
   be applicable to MEMBER's type.  See DR 273 for details.  */
#define offsetof(TYPE, MEMBER) (reinterpret_cast <size_t> \
    (&reinterpret_cast <char &>(static_cast <TYPE *> (0)->MEMBER)))

어째 상용 컴파일러가 더 허접하게 정의 된듯합니다만....

중요한건 도무지 저 코드들이 뭘 뜻하는지 모르겠다는 것입니다.

좀 자세하게 부연 설명을 해주시면 정말 감사하겠습니다.

그럼...

codebank의 이미지

코드 그대로 아닐까요?

offsetof (int, 100)이 된다면
&((int *)0)->100
이 되겠죠?
메모리 0번지 부터 100바이트를 뛰어서의 번지를 나타내는 것이 아닌지요?
즉, 위 코드대로라면 100번지를 나타내는 것이라고 생각합니다.
(역시 C언어는 암호문이 맞는것 같네요. :oops: )

------------------------------
좋은 하루 되세요.

raymundo의 이미지

icanfly wrote:
얼마전에 offsetof 매크로란게 있다는걸 알았습니다.

class또는 struct 내부에 있는 멤버와 그 class,struct와의 메모리 오프셋

을 구할때 쓴다는 식으로 막연하게 밖에 이해가 안되는데요. 소스를 한번 열

어 봤더니 VC++, MinGW에서 아래와 같이 구현되 있더군요.

VC++ 6.0

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

MinGW 3.x

#ifndef __cplusplus
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#else /* C++ */
/* The reference cast is necessary to thwart an operator& that might
   be applicable to MEMBER's type.  See DR 273 for details.  */
#define offsetof(TYPE, MEMBER) (reinterpret_cast <size_t> \
    (&reinterpret_cast <char &>(static_cast <TYPE *> (0)->MEMBER)))

어째 상용 컴파일러가 더 허접하게 정의 된듯합니다만....

중요한건 도무지 저 코드들이 뭘 뜻하는지 모르겠다는 것입니다.

좀 자세하게 부연 설명을 해주시면 정말 감사하겠습니다.

그럼...

예를 들어서 다음의 구조체를 생각해 보면...

struct s_a {
   short b;
   int c;
} a;

내가 s_a 인 구조체 a 의 주소를 알고 있을 때 (&a 겠죠) 그 안에 있는 멤버 c 의 주소가 a 의 주소로부터 얼마나 떨어져 있느냐(즉 offset) 를 알고 싶을 때가 있겠죠.

답이 2일 수도 있고 4일 수도 있겠죠. short b 바로 뒤에 이어서 c 가 저장된다면 전자가 되겠고 아키텍처가 4바이트 단위로 정렬을 하는 경우 후자가 되겠죠.

그러면 이 오프셋을 알고 싶을 때 매번 아키텍처가 뭔지를 판단하면서 계산을 해야 하느냐..의 답이 저 코드인거죠.

캐스팅 뒤에 0 이 있어서 혼란스럽지만 그 자리에 포인터 변수가 있다고 상상하면 간단해 집니다.

stuct s_a * p_a = &a;
void * p_void;
p_void = p_a;

이 때,

((struct s_a *) p_void)            이것은 캐스팅
((struct s_a *) p_void)->c       이것은 a.c 
&(((struct s_a *) p_void)->c)  이것은 a.c 의 주소

이 값은 아키텍처에 따라서 &a , 즉 p_void 보다 2가 클 수도, 4가 클 수도 있겠죠? 이 때 저 p_void 의 값이 0 이었다면? 2 아니면 4가 반환되는 겁니다. 그게 위 매크로인 거죠.

사용법은
offsetof(struct s_a, c)
쯤 되겠네요. 반환값은 이 경우 아키텍처에 따라 2 아니면 4.

좋은 하루 되세요!

icanfly의 이미지

명쾌한 답변 정말 감사 드립니다.

그건 그렇고 답변을 보고 그냥 심심해서 다음과 같은 생각을 해봤는데요.

0번지에서 부터 포인터 연산을 하지 않고, 있는 그대로 다음과 같이 표현해도

원하는 결과를 얻을 수 있을거 같습니다.

 (struct s_a*) p_void - &((struct s_a*) p_void)->c)

위 표현이 적법하다면

&(((struct s_a*)0)->c) == (struct s_a*) p_void - &((struct s_a*) p_void)->c)

가 된다는 말이고, 이를 전개해서 증명할 수 있지 않을까 하는 이상한

생각을 해보았습니다.

위 식에서 우변은

&(*(struct s_a*)p_void) - &(((struct s_a*)p_void)->c) 

와 같으므로, &()를 밖으로 뽑아내면
    &( *(struc s_a*)p_void - (((struct s_a*)p_void)->c) )

가 되고.....-_-;

다음은..모르겠네요..

그냥 잡담이었습니다.

akbar의 이미지

raymundo wrote:

캐스팅 뒤에 0 이 있어서 혼란스럽지만 그 자리에 포인터 변수가 있다고 상상하면 간단해 집니다.

stuct s_a * p_a = &a;
void * p_void;
p_void = p_a;

이 때,

((struct s_a *) p_void)            이것은 캐스팅
((struct s_a *) p_void)->c       이것은 a.c 
&(((struct s_a *) p_void)->c)  이것은 a.c 의 주소

이 값은 아키텍처에 따라서 &a , 즉 p_void 보다 2가 클 수도, 4가 클 수도 있겠죠? 이 때 저 p_void 의 값이 0 이었다면? 2 아니면 4가 반환되는 겁니다. 그게 위 매크로인 거죠.

사용법은
offsetof(struct s_a, c)
쯤 되겠네요. 반환값은 이 경우 아키텍처에 따라 2 아니면 4.

라고 하셨는데 정확히 말하면

((struct s_a *) p_void)            이것은 캐스팅
((struct s_a *) p_void)->c       이것은 a.c 
&(((struct s_a *) p_void)->c)  이것은 a.c 의 주소

는 아니고

((struct s_a *) p_void)  이것은 캐스팅
((struct s_a *) p_void)->c       이것은 p_void 주소부터 c 가 객체에서 차지하는 바이트만큼 떨어져(?) 있는 곳의 있는 자료
&(((struct s_a *) p_void)->c)  이것은 p_void 주소부터 c 가 객체에서 차지하는 바이트만큼 떨어진 곳의 자료의 주소

가 됩니다. 따라서
&(((struct s_a *) 0)->c) 의 값이 4 였다면
&(((struct s_a *) 1)->c) 은 5(4+1) 가 출력되고
&(((struct s_a *) 2)->c) 은 6(4+2) 가 출력됩니다.
단 (((struct s_a *)0)->c) 이렇게 c 멤버값을 알아낼려고 하면 안되겠죠

raymundo의 이미지

akbar wrote:

단 (((struct s_a *)0)->c) 이렇게 c 멤버값을 알아낼려고 하면 안되겠죠

^^; 맞는 말씀입니다. 그래서 그 위에

p_void = p_a;

라고 했지요. 그러니 멤버값을 알아내는 데는 문제가 없겠죠. :-)

((type *) 0)
이란 형태에 혼란스러워 하는 경우가 있어서 (사실은 바로 얼마전에도 이 코드 얘기를 다른 사람들과 했었거든요) 흔히 쓰는 형태의 예를 먼저 든 겁니다.

좋은 하루 되세요!

raymundo의 이미지

icanfly wrote:
명쾌한 답변 정말 감사 드립니다.

그건 그렇고 답변을 보고 그냥 심심해서 다음과 같은 생각을 해봤는데요.

0번지에서 부터 포인터 연산을 하지 않고, 있는 그대로 다음과 같이 표현해도

원하는 결과를 얻을 수 있을거 같습니다.

 (struct s_a*) p_void - &((struct s_a*) p_void)->c)

위 표현이 적법하다면

포인터의 뺄셈은 정수의 뺄셈처럼은 안 되죠? :-) 게다가 위의 경우는 왼쪽은 s_a 구조체의 포인터고 오른쪽은 int 의 주소이니까 컴파일 자체가 안 됩니다. gcc 로 해 봐도 저 상태는 컴파일 에러가 나네요.

포인터의 뺄셈은 같은 형의 포인터끼리만 가능하고 그 결과도 정수의 차이가 아니라 (정수의 차이/해당 타입의 사이즈) 값이 나오니까... 양쪽을 struct s_a * 로 캐스트하면 0이 나오고 (2가 되든 4가 되든 어쨌거나 struct s_a 의 크기보다 작으니까) 양쪽을 int * 로 캐스트하면 나올 수 있는 값은 0 아니면 1인데, 그나마도 우측의 주소값이 더 크니까 -1 이 나오는군요.

아하, 방금 생각났는데, 양쪽을 (char *) 로 캐스트하면 되겠군요. ^^; 뺄셈의 순서도 바꿔서

(char *) &(((struct s_a *)p_void)->c) - (char *)p_void

요러면 4가 나오는군요. 근데 이러느니... ^^;;

좋은 하루 되세요!

azalei의 이미지

include/linux/list.h에서 리눅스가 리스트를 처리하는 방식입니다.

181 /**
182  * list_entry - get the struct for this entry
183  * @ptr:        the &struct list_head pointer.
184  * @type:       the type of the struct this is embedded in.
185  * @member:     the name of the list_struct within the struct.
186  */
187 #define list_entry(ptr, type, member) \
188         ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
189 
icanfly의 이미지

좀 더 살펴보았더니 offsetof 는 POD 객체에 대해서만 유효하다고 나오는군요. class에 대해서 offsetof를 적용했을때, 이와 같은 워닝이 나서..그런데..POD 가 뭔지 몰라 인터넷을 뒤졌더니 이렇게 나와있네요.

Quote:

C++ distinguishes between two types of objects: POD (plain old data) and non–POD objects. A POD object has one of the following datatypes: a built-in type, pointer, union, struct, array, or class with a trivial constructor. For example:

int n;
class C
{
public:
int j,k;
};
C c; // c is a POD object
struct S
{
int j,k;
};
S s;
char *pc;
bool barr[10];
Conversely, a non–POD object is one for which a nontrivial constructor exists. Note that this distinction between POD and non–POD doesn't exist in pure object-oriented languages; it's an artifact of the hybrid nature of C++. It's used to differentiate between traditional C datatypes and real C++ objects. A POD object begins its lifetime when it obtains storage with the proper alignment and size for its type, and its lifetime ends when the storage for the object is either reused or deallocated. A non–POD object begins its lifetime after its constructor has executed; its lifetime ends when its destructor has started.

실제로 명시적인 생성자가 있을때 컴파일러는 워닝을 내고. 명시적 생성자는
는 없고, 멤버 함수를 가지고 있을때는 워닝을 내지 않고 깨끗하게 컴파일 되었
습니다.
단, VC++ 6.0에 있는 CL컴파일러에서는 두경우다 아무런 워닝을 내지 않았습니다. 워닝레벨을 높여도 offsetof에 대한 워닝은 내지 않더군요.

역시 지식은 여러 사람들과 공유하면서 깊어지고 넓어지나봅니다.
그럼...

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.