오프셋

offsetof

C의 offsetof() 매크로는 stdef.h에 있는 ANSI C 라이브러리 기능입니다.구조체 또는 유니언 유형 내 특정 멤버의 오프셋(바이트 단위), 즉 size_t 유형의 식에 따라 평가됩니다.offsetof()매크로에는 두 가지 파라미터가 있습니다.첫 번째 파라미터는 구조체 이름이고 두 번째 파라미터는 구조체 내 멤버 이름입니다.C [1]프로토타입으로 설명할 수 없습니다.

실행

매크로의 "기존" 구현은 주소 0에서 시작하는 가상 구조를 지정함으로써 구성원의 오프셋을 얻는 컴파일러에 의존했습니다.

#오피스 오프셋(st, m) \ ((size_t)&((st *)0)->m) 

이것은 type structure st의 늘포인터를 가져와서 해당 구조 내의 member 주소를 얻는 것으로 이해할 수 있다.이 구현에 정확하게 많은 컴파일러에서 일하고 있기 때문(비록 표준에, 6.6콩스탕 Expressions, 제9, 개체의 값accesse지 않다 따라 null의 포인터를 역 참조하다 포함하는 것처럼 비친다면 Cstandard,[2]에 따르면 이 막연한 행동에 대해 일부 논쟁을 촉발하고 있다.d조작에 의해서).또한 인수 중 하나의 [citation needed]철자가 틀리면 컴파일러 진단을 혼란스럽게 하는 경향이 있습니다.

다른 방법은 다음과 같습니다.

#오피스 오프셋(st, m) \ ((size_t)((char *)&(st *)0)->m - (char *)0) 

표준에서는 늘 포인터의 내부 표현이 주소 0에 있다고 명시되어 있지 않기 때문에 이 방법으로 지정될 수 있습니다.따라서 멤버 주소와 베이스 주소의 차이를 만들어야 합니다.이것도 상수 표현식이므로 반드시 런타임에 계산되는 것이 아니라 컴파일 시 계산될 수 있습니다.

일부 최신 컴파일러(GCC 등)는 특수한 형식(언어 확장자)을 사용하여 매크로를 정의합니다.[3]

#오피스 오프셋(st, m) \ __builtin_filt of(st, m) 

이 빌트인은 커스텀 단항 연산자 [4]&을 선언하는 C++ 클래스 또는 구조에서 특히 유용합니다.

사용.

C에서 범용 데이터 구조를 구현할 때 유용합니다.예를 들어 Linux 커널은 offsetof()사용하여 container_of()를 구현합니다.이것에 의해, mixin 타입과 같은 타입이 그것을 [5]포함한 구조를 검색할 수 있습니다.

#container_of(ptr, type, member)({\) const type of ( ( type * ) 0 ) - > member )*_mptr = ( ptr ) ; \ (타입 * ( ( char * ) _ mptr - offset of ( type , member ) ; ) 

매크로는 my_struct 객체의 링크된 목록의 반복과 같이 포인터에서 중첩된 요소로 둘러싸인 구조를 가져오기 위해 사용됩니다.

구조 my_my_my_my_my_my_my {     컨스턴트  *이름.;     구조 list_node 목록.; };  외부 구조 list_node * 리스트_다음(구조 list_node *);  구조 list_node *현재의 = /* ... */ 하는 동안에 (현재의 != 특수한 순서) {     구조 my_my_my_my_my_my_my *요소 = 컨테이너_/(현재의, 구조 my_my_my_my_my_my_my, 목록.);     인쇄물(%s\n", 요소->이름.);     현재의 = 리스트_다음(&요소->목록.); } 

container_of의 Linux 커널 구현에서는 스테이트먼트 [6]식이라고 불리는 GNU C 확장자를 사용합니다.문장의 표현이 타입의 안전성을 확보하고 우발적인 버그를 제거하기 위해 사용되었을 가능성이 있습니다.단, 스테이트먼트 표현을 사용하지 않고 동일한 동작을 구현하면서 타입의 안전을 확보할 수 있는 방법이 있습니다.

#container_of(ptr, type, member) ((type * (1? (ptr) : &(type *)0)-> member) - offset of(type, member)))) 

언뜻 보면 이 구현은 필요 이상으로 복잡해 보일 수 있으며, 조건부 연산자의 비정상적인 사용은 적절하지 않은 것으로 보일 수 있습니다.보다 심플한 실장이 가능합니다.

#container_of(ptr, type, member) ((type *)(char *(ptr) - offsetof(type, member)))) 

이 구현도 같은 목적을 달성하지만 원래 Linux 커널 구현에 대한 근본적인 누락이 있습니다.ptr의 타입은 멤버의 타입과 대조되지 않습니다.이것은 Linux 커널 실장에서는 검출할 수 있는 것입니다.

상기 타입 체크 실장에서는 조건 연산자의 비정상적인 사용에 의해 체크가 이루어진다.조건부 연산자의 제약조건은 조건부 연산자에 대한 오퍼랜드가 모두 하나의 타입에 대한 포인터인 경우, 양쪽 모두 호환되는 타입에 대한 포인터여야 한다는 것을 지정합니다.이 경우 조건식의 세 번째 피연산자 값이 절대 사용되지 않음에도 불구하고 컴파일러는 다음 사항을 확인하기 위해 검사를 수행해야 합니다.(ptr)그리고.&((type *)0)->member는 둘 다 호환되는 포인터 유형입니다.

제한 사항

사용방법offsetof는, C++98 의 POD 타입, C++[7]11 의 표준 레이아웃 클래스로 한정되어 있습니다.또, [8]C++17 에서 조건부로 서포트되고 있는 케이스도 많아지고 있습니다.그렇지 않으면 정의되지 않은 동작이 발생합니다.대부분의 컴파일러는 표준을 따르지 않는 경우에도 올바른 결과를 생성하지만, offsetof가 잘못된 값을 생성하거나 컴파일 시 경고 또는 오류를 생성하거나 프로그램을 완전히 크래시하는 경우가 있습니다.이는 특히 [9]가상 상속의 경우입니다.다음 프로그램은 amd64 아키텍처에서 gcc 4.7.3을 사용하여 컴파일하면 몇 가지 경고를 생성하고 의심스러운 결과를 출력합니다.

#실패하다 <stdef.h> #실패하다 <stdio.h>  구조 A {     인트  a;     가상 무효 모조의() {} };  구조 B: 일반의 가상 A {     인트  b; };  인트 주된() {     인쇄물("오프셋(A, a): %zu\n", 오프셋(A, a));     인쇄물("오프셋(B, b): %zu\n", 오프셋(B, b));     돌아가다 0; } 

출력:

offset of(A, a): 8 offset of(B, b) : 8

레퍼런스

  1. ^ "offsetof reference". MSDN. Retrieved 2010-09-19.
  2. ^ "Does &((struct name *)NULL -> b) cause undefined behaviour in C11?". Retrieved 2015-02-07.
  3. ^ "GCC offsetof reference". Free Software Foundation. Retrieved 2010-09-19.
  4. ^ "what is the purpose and return type of the __builtin_offsetof operator?". Retrieved 2012-10-20.
  5. ^ Greg Kroah-Hartman (June 2003). "container_of()". Linux Journal. Retrieved 2010-09-19.
  6. ^ "Statements and Declarations in Expressions". Free Software Foundation. Retrieved 2016-01-01.
  7. ^ "offsetof reference". cplusplus.com. Retrieved 2016-04-01.
  8. ^ "offsetof reference". cppreference.com. Retrieved 2020-07-20.
  9. ^ Steve Jessop (July 2009). "Why can't you use offsetof on non-POD structures in C++?". Stack Overflow. Retrieved 2016-04-01.