2017-11-26 5 views
6

C++ 17 (expr.add/4)을 말한다 :실제로 char 배열을 가리 키지 않을 때 "char *"포인터 UB에 추가 중입니까?

일체형을 갖는식이에 첨가하거나 포인터로부터 감산

결과 포인터 피연산자의 형태를 갖는다. 표현식 P가 n 요소가있는 배열 객체 x의 요소 x [i]를 가리키는 경우 P + J 및 J + P (J는 값 j를 가짐) 표현식은 요소 x를 가리 킵니다. [i + j] 0≤i + j≤n 인 경우; 그렇지 않으면 동작이 정의되지 않습니다. 마찬가지로 P - J 이라는 표현식은 0≤i-j≤n 인 경우 (아마도 가상의) 요소 x [i-j]를 가리 킵니다. 그렇지 않으면 동작이 정의되지 않습니다.

struct Foo { 
    float x, y, z; 
}; 

Foo f; 
char *p = reinterpret_cast<char*>(&f) + offsetof(Foo, z); // (*) 
*reinterpret_cast<float*>(p) = 42.0f; 

(*)가 UB로 표시된 선을 했습니까? reinterpret_cast<char*>(&f)은 char 배열을 가리 키지 않고 float로 가리 키므로 인용 단락에 따라 UB이어야합니다. 그러나 그것이 UB라면 offsetof의 유용성은 제한 될 것입니다.

UB입니까? 그렇지 않다면 왜 안 되겠습니까?

+0

[\ [basic.lval \]/8] (https://timsong-cpp.github.io/cppwp/n4659/basic.lval#8) – StoryTeller

+0

@StoryTeller : line (*)은 액세스하지 않습니다. , 그것은 단지 포인터 조작입니다. – geza

+0

UB가 아닙니다. 변수의 주소를 가져 와서 'char *'로 캐스팅 한 다음 원래 유형으로 되돌립니다. 유효한 객체 ('z'의 주소)를 가리 킵니다. – Brandon

답변

1

이 추가 사항은 유효하기위한 것이지만 표준이 충분히 명확하게 말할 수 있다고는 생각하지 않습니다. N4140 (대략 C++ 14) 인용 :

3.9 유형 [basic.types]

2 개체 들어 사소 복사 가능한 유형 T의 (a베이스 클래스 서브 오브젝트 이외)인지 객체가 T 유형의 유효한 값을 보유하지 않은 경우 객체를 구성하는 기본 바이트 (1.7)는 또는 unsigned char의 배열로 복사 할 수 있습니다. 예를 들어 사용하여 42...]

42), 라이브러리 함수 (17.6.1.2) std::memcpy 또는 std::memmove.

std::memcpystd::memmove가 기본이되는 바이트를 복사 할 수하기위한 것입니다있는 유일한 방법이 아니기 때문에 그것은 "예를 들어,"말한다. 수동으로 바이트 단위로 복사하는 간단한 for 루프도 유효합니다.

오브젝트를 구성하는 원시 바이트에 대한 포인터에 대해 덧셈을 정의해야하며 표현식의 정의 방식이 작동하면 덧셈의 정의는 이후에 그 가산 결과가 사용되는지 여부에 달려 있지 않습니다 바이트를 배열에 복사한다.

이것은 해당 바이트가 이미 배열을 형성했는지 또는 이것이 연산자 설명에서 어떤 식 으로든 생략 된 + 연산자의 일반 규칙에 대한 특별한 예외인지 여부에 관계없이 분명하지 않습니다 (전자는 의심 스럽지만). 어느 쪽이든 당신이 당신의 코드에서 수행하고있는 추가를 유효하게 만들 것입니다.

+0

귀하의 의견은 귀하의 의견이 달리 언급하는 것처럼 보이지만 귀하의 말씨가 현재의 문구에도 유효 함을 암시하는 것으로 보입니다. 당신은 명확히 할 수 있습니까? –

+0

@PasserBy StoryTeller에 대한 응답으로 질문에 대한 내 의견을 말씀 하시겠습니까? 그 중 StoryTeller의 논리에는 결함이 있다고 믿는다 고 말하고 있지만, 결론은 아무 것도 말하지 않습니다. 결함이있는 논리는 올바르지 않은 결론뿐만 아니라 올바른 결론을 초래할 수 있습니다. – hvd

0

내가 아는 한, 귀하의 코드는 유효합니다. 객체를 char 배열로 앨리어싱하는 것은 §   3.10 ¶  에 따라 명시 적으로 허용됩니다.8 :

  • [...]
  • char을 :

    프로그램 동작은 정의 된 유형 중 하나 이외의 glvalue 통해 피사체의 저장된 값에 액세스하려고 시도하는 경우

    또는 unsigned char 유형. float*로 다시 char* 포인터를 캐스팅하고 유효을 통해 할당 여부를

다른 질문입니다. Foo이 POD 유형이므로 괜찮습니다. POD 멤버의 주소를 계산할 수 있습니다 (계산 자체가 UB가 아닌 경우). 그러면 해당 주소를 통해 멤버에 액세스 할 수 있습니다. 예를 들어 POD가 아닌 객체의 구성원 인 private에 대한 액세스 권한을 악용해서는 안됩니다. 또한 int*으로 전송하거나 float 유형의 개체가없는 주소에 글을 쓸 경우 UB가됩니다. 이것에 대한 추론은 위에 인용 된 절에서 찾을 수 있습니다.

+1

그냥'char'라고 말하면서'char' 배열이 아닙니다. 그것은 차이를 만듭니다. – geza

+0

'char [N]'과 하위 객체가 다릅니다. 'struct {char a0, a1; }'패딩이없는 경우에도 'int16_t'의 별명을 지정할 수 있습니다. –

2

offsetof의 의도 된 사용이 잘못해야 허용하지 않습니다 모든 해석 :

#include <stddef.h> 
struct S { float a, b, c; }; 

void fn(S *sp) 
{ 
    return *(float *)(((char *)sp) + offsetof(S, c)); 
} 

... 그러나, 하나는 잘못된해야 명시 적으로 선언 배열의 끝을지나 단계를 가능하게하는 모든 해석 : C 및 C++ 표준 모두에있는 표현은, 그이 두 번째 경우를 허용하도록 구성 되었기 때문에

#include <assert.h> 
#include <stddef.h> 
struct S { float a[2]; float b[2]; }; 

static_assert(offsetof(S, b) == sizeof(float)*2, 
    "padding between S.a and S.b -- should be impossible"); 

float fn(S *sp) 
{ 
    return S->a[2]; // undefined behavior, reading past end of array 
} 

는 그리고 우리는 아마도 첫 번째 경우를 허용하지, 딜레마의 뿔에 지금있다.

일반적으로 "개체 란 무엇입니까?" 문제. C 및 C++위원회의 위원을 비롯한 사람들은 1990 년대 이래이 문제와 관련 문제에 대해 논쟁을 벌여 왔으며 그 말을 고치려는 시도가 여러 번 있었으며 지식의 최대 한 부분도 성공하지 못했습니다. 기존의 "합리적인"코드가 명확하게 준수되고 기존의 모든 "합리적인"최적화가 여전히 허용됩니다.