2017-09-08 4 views
5

이 질문은 구조체 오프셋을 사용하여 포인터 산술을 사용하여 파생 된 포인터에 대한 것입니다.C++의 구조체 오프셋 및 포인터 안전성

다음 프로그램을 고려 :

#include <cstddef> 
#include <iostream> 
#include <new> 

struct A { 
    float a; 
    double b; 
    int c; 
}; 

static constexpr auto off_c = offsetof(A, c); 

int main() { 
    A * a = new A{0.0f, 0.0, 5}; 
    char * a_storage = reinterpret_cast<char *>(a); 
    int * c = reinterpret_cast<int *>(a_storage + off_c)); 

    std::cout << *c << std::endl; 

    delete a; 
} 

이 프로그램은 기본 설정 및 C++ 11 표준을 사용하여, 일을하고 내가 테스트를 컴파일러에 예상되는 결과를 보인다.

(우리가 대신 char *void *static_cast 대신 reinterpret_cast를 사용하여 밀접하게 관련 프로그램, 보편적. gcc 5.4 문제 무효 포인터 포인터 연산에 대한 경고를 받아 들여 clang 6.0void *와 포인터 연산이라고 말한다되지 않는다 오류입니다.)

이 프로그램은 C++ 표준에 따라 잘 정의 된 동작을합니까?

구현이 완화되었거나 엄격한 포인터 안전성 ([basic.stc.dynamic.safety])을 사용하는지 여부에 따라 대답이 달라 집니까?

+0

이 과제는 무엇입니까? –

+2

@ GarrGodfrey : 놀랍습니다. –

+2

C++의 멤버 기능에 대한 포인터를 사용하지 않는 이유는 무엇입니까? 그것은 "offsetof"에 대한 "깨끗한"대안인가? –

답변

8

코드에 근본적인 오류가 없습니다.

A이 일반적인 오래된 데이터가 아닌 경우 위의 것은 UB (C++ 17 이전)이며 조건부 지원 (C++ 17 이후)입니다.

char*int*auto*으로 바꿀 수도 있지만 그게 스타일입니다.

멤버에 대한 포인터가 똑같은 방식으로 형식을 안전하게 사용한다는 점에 유의하십시오. 대부분의 컴파일러는 member ...에 대한 포인터를 해당 유형의 멤버의 오프셋으로 구현합니다. 그러나 그들은 비 포드 (non-pod) 구조에서도 모든 곳에서 일합니다.

제외 : offsetof이 표준에서 constexpr임을 보장하지 않습니다.)

어쨌든 바꾸

static constexpr auto off_c = offsetof(A, c); 

static constexpr auto off_c = &A::c; 

auto* a_storage = static_cast<char *>(a); 
    auto* c = reinterpret_cast<int *>(a_storage + off_c)); 

으로

auto* c = &(a->*off_c); 
으로

C++ 방식으로 할 수 있습니다.

+0

POD가 아닌 유형에 offsetof를 사용하면 Clang이 소리를 지르므로 올바른 수정 작업을 수행하는 것이 좋습니다. https://godbolt.org/g/6jLnM6 - on on on 비표준 레이아웃 타입 'A'[-Winvalid-offsetof] – xaxxon

+1

3.7.4.3 [basic.stc.dynamic.safety]에서 포인터는 안전하게 (조건)과 엄격한 포인터 안전성은 그런 장소에서 오지 않으면 포인터가 유효하지 않습니다. 5.7 포인터 산술에서, 내가 배열 내에서 일반적인 산술을 할 수 있지만 내가 구조체 오프셋 산술 괜찮아요 말하고있는 아무 것도 볼 말합니다.내가 생각하는 방식과 관련이 있는지 알아 내려고하는 중이시겠습니까 아니면 상쇄 된 산술 연산이 가상의 "엄격한"impls에서 좋지 않거나 5.7을 잘못 읽은 경우 (n4296) –

+0

기본적으로 질문은 느슨한 impls에서 ok이고 엄격한 impl에서 정의되지 않은 동작을합니다. 포인터를 인덱스에 추가 할 때 배열에 대한 포인터를 추가 할 때 자동으로 엄격한 impl에서 안전하지 않으며 결과가 안전하게 파생 된 ptr이 아닐 것이라고 생각합니다. 또한 잘못된 ptr을 역 참조하는 것이 ub라고 말합니다. 나는 이것이 어쨌든 제도 오류가되어야한다고 생각한다. 하지만 난 완전히 잘못 될 수 있습니다 :) –

4

특정 예제에서는 구조체가 표준 레이아웃이기 때문에 안전합니다. std::is_standard_layout<>을 사용하여이를 다시 확인할 수 있습니다.

struct A { 
    float a; 
    double b; 
    int c; 
    std::string str; 
}; 

문자열이 관련이 구조체의 일부 과거의 경우에도 불법 같습니다과 같은 구조체에 이것을 적용하려고

.

편집 있습니다 ABT에 관한 어떤 메신저

을 Heres : 3.7.4.3에서 [basic.stc.dynamic.safety]은 포인터가 안전하게 파생 말합니다 경우에만 (조건) 우리가있는 경우 엄격한 포인터 안전 그런 장소에서 오지 않으면 포인터가 유효하지 않습니다. 5.7 포인터 산술에서, 내가 배열 내에서 일반적인 산술을 할 수 있지만 내가 구조체 오프셋 산술 괜찮아요 말하고있는 아무 것도 볼 말합니다. 임 내가 그것을 생각하는 방식에 관련 이러면인지 알아 내려고, 또는 구조체 연산을 오프셋 (offset)를 가지는 경우는 가상의 "엄격한"impls에서 확인되지 않았거나 내가 잘못 5.7 (n4296)

을 읽으면 포인터 arithmatic을 수행하고 있는데, 의 배열에서 수행합니다. 크기는 적어도 sizeof(A)이므로 괜찮습니다. - 안전하게 파생 포인터의 잘 정의 된 포인터 변환 (4.10, 5.4)의 결과

: 두 번째 멤버로 다시 캐스팅 할 때 다음

, 당신은 (2.4)이 적용된다 값;

+0

좋아요, 포인터 메신저에 유형이'char * '이지만 5.7.4에서는 배열의 산술에 관한 블록이 있습니다. 포인터가 배열 객체의 요소를 가리키고 있습니다 ... "표준의 의미에서 배열 객체는 없습니다. 오직 A 만 존재합니다. Thats는 또한 엄격한 앨리어싱 문제와 관련이 있습니다. 객체는 언어에 의해 생성 될 때 존재하고 캐스트에 의해 존재하지 않습니다. 그래서 이것은 배열에서 안전한 포인터 조작으로 간주됩니다. 나는 표준 관점에서 볼 때 배열이 없다고 생각합니다. Idk 확실한 tho. –

1

가정 사항을 검사해야합니다.

가정 # 1) offsetof는 바이트 단위의 올바른 오프셋을 제공합니다. 이것은 클래스가 "표준 레이아웃"으로 간주되는 경우에만 보장되며 가상 메서드가없는 등 여러 가지 제한이 있으며 여러 상속을 피할 수 있습니다.이 경우 괜찮을 것이지만 일반적으로 확실히하다.

가정 # 2) char는 바이트와 동일한 크기입니다. C에서는 정의에 의한 것이므로 안전합니다.

가정 # 3) offsetof는 데이터의 시작이 아니라 클래스에 대한 포인터에서 올바른 오프셋을 제공합니다. 이것은 기본적으로 # 1과 동일하지만 vtable이 확실히 문제가 될 수 있습니다. 다시 말하지만 표준 레이아웃에서만 작동합니다.

+2

# 1에 대해 유형에 표준 레이아웃이 있는지 확인할 수 있습니다. http://en.cppreference.com/w/cpp/types/is_standard_layout –

+0

non-SL 유형에서 offsetof를 호출하면 clang도 경고를 표시합니다 – xaxxon