유형 punning 테마의 변형 : 적절한 사소한 구성


9

나는 이것이 매우 일반적인 주제라는 것을 알고 있지만 일반적인 UB를 쉽게 찾을 수있는 한 지금 까지이 변형을 찾지 못했습니다.

따라서 실제 데이터 사본을 피하면서 Pixel 객체를 공식적으로 소개하려고합니다.

이것이 유효합니까?

struct Pixel {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
};

static_assert(std::is_trivial_v<Pixel>);

Pixel* promote(std::byte* data, std::size_t count)
{
    Pixel * const result = reinterpret_cast<Pixel*>(data);
    while (count-- > 0) {
        new (data) Pixel{
            std::to_integer<uint8_t>(data[0]),
            std::to_integer<uint8_t>(data[1]),
            std::to_integer<uint8_t>(data[2]),
            std::to_integer<uint8_t>(data[3])
        };
        data += sizeof(Pixel);
    }
    return result; // throw in a std::launder? I believe it is not mandatory here.
}

예상되는 사용 패턴, 크게 단순화 :

std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data

더 구체적으로:

  • 이 코드에는 잘 정의 된 동작이 있습니까?
  • 그렇다면 반환 된 포인터를 사용하는 것이 안전합니까?
  • 그렇다면 다른 Pixel유형으로 확장 할 수 있습니까? (3 개의 구성 요소 만있는 is_trivial 제한? 픽셀을 완화합니까?).

clang과 gcc는 전체 루프를 무용지물로 최적화합니다. 이제 이것이 이것이 일부 C ++ 규칙을 위반하는지 여부를 알고 싶습니다.

Godbolt 와 함께 놀고 싶다면 링크 하십시오.

(참고 : std::byte질문 에도 불구하고 c ++ 17 태그를 지정하지 않았습니다. char)


2
그러나 연속 Pixel된 새 항목은 여전히의 배열이 아닙니다 Pixel.
Jarod42

1
@spectras 그것은 배열을 만들지 않습니다. 서로 옆에 많은 Pixel 객체가 있습니다. 그것은 배열과 다릅니다.
NathanOliver

1
그래서 당신은 어디에 어떻게하지 않는다 pixels[some_index]또는 *(pixels + something)? 그것은 UB입니다.
NathanOliver

1
관련 섹션이 여기에 있으며 핵심 문구는 P가 배열 객체 x의 배열 요소 i를 가리키는 경우 입니다. 여기서 pixels(P)는 배열 객체에 대한 포인터가 아니라 단일에 대한 포인터 Pixel입니다. 즉, pixels[0]법적으로 만 액세스 할 수 있습니다 .
NathanOliver

3
wg21.link/P0593 을 읽으 려고 합니다.
ecatmur

답변:


3

결과를 promote배열로 사용하는 것은 정의되지 않은 동작 입니다. 우리가 보면 [expr.add] /4.2 우리는이

그렇지 않은 경우, P배열 요소 포인트 i배열 개체xn요소 ([dcl.array]) 식 P + JJ + P(여기서 J의 값을 갖는다 j)을 (아마도-가설) 배열 요소 점 i+jx경우 0≤i+j≤n와 식 P - J받는 점 ( 가능성 - 가설) 배열 요소 i−jx경우 0≤i−j≤n.

포인터가 실제로 배열 객체를 가리켜 야한다는 것을 알 수 있습니다. 실제로 배열 객체는 없습니다. 인접한 메모리에서 Pixel다른 것을 Pixels따라가는 싱글에 대한 포인터가 있습니다. 즉, 실제로 액세스 할 수있는 유일한 요소는 첫 번째 요소입니다. 포인터의 유효한 도메인 끝을 지났으므로 다른 것에 액세스하려고하면 정의되지 않은 동작이됩니다.


빨리 찾아 주셔서 감사합니다. 대신 이터레이터를 만들 것입니다. 참고로, 이것은 또한 &somevector[0] + 1UB 임을 의미합니다 (결과 포인터를 사용하는 것이 좋습니다).
스펙트럼

@spectras 실제로 괜찮습니다. 항상 객체를 지나서 포인터를 얻을 수 있습니다. 유효한 객체가 있어도 해당 포인터를 역 참조 할 수 없습니다.
NathanOliver

예, 의견을 명확하게하기 위해 주석을 편집했습니다. 결과 포인터를 참조 해제하는 것을 의미했습니다.
스펙트럼

@spectras 문제 없습니다. C ++의이 부분은 매우 어려울 수 있습니다. 하드웨어가 원하는 작업을 수행하더라도 실제로는 코딩 작업이 아닙니다. 우리는 C ++ 추상 머신으로 코딩하고 있으며 persnickety machine입니다.) P0593이 채택되어 훨씬 쉬워 질 것입니다.
NathanOliver

1
@spectras No, std 벡터는 배열을 포함하는 것으로 정의되므로 배열 요소간에 포인터 산술을 수행 할 수 있습니다. 슬프게도 UB를 실행하지 않고 C ++ 자체에서 std 벡터를 구현할 수있는 방법이 없습니다.
Yakk-Adam Nevraumont

1

반환 된 포인터의 제한된 사용에 대한 답변이 이미 있지만 std::launder첫 번째에도 액세스 할 수 있다고 생각합니다.Pixel .

reinterpret_cast어떤 전에 완료 Pixel객체가 생성 (당신이 그렇게하지 가정 getSomeImageData). 따라서 reinterpret_cast포인터 값이 변경되지 않습니다. 결과 포인터는 여전히 첫 번째 요소를 가리 킵니다.std::byte 함수에 전달 배열 .

Pixel객체 를 만들면 객체 가 어레이 내에 중첩std::byte 되고 std::byte어레이가 스토리지제공 합니다.Pixel 객체에 합니다.

저장 영역을 재사용하면 이전 오브젝트에 대한 포인터가 자동으로 새 오브젝트를 가리키게되는 경우가 있습니다. 그러나 이것은 여기서 일어나는 일이 아니므로 result여전히 std::byte대상이 아닌 대상을 가리킬 것 Pixel입니다. 마치 Pixel객체를 가리키는 것처럼 기술적으로 정의되지 않은 동작 이되는 것처럼 사용합니다 .

객체를 reinterpret_cast생성 한 후 Pixel객체를 생성 한 후에도 객체를 저장 하는 Pixel객체 std::byte포인터 상호 변환 이 불가능하기 때문에 여전히 유지한다고 생각합니다 . 따라서 포인터가 객체가 std::byte아닌 을 계속 가리 킵니다 Pixel.

새로운 배치 중 하나의 결과에서 리턴 할 포인터를 얻은 경우 해당 특정 Pixel오브젝트에 대한 액세스와 관련하여 모든 것이 정상이어야합니다 .


또한 std::byte포인터가 적절하게 정렬 Pixel되고 배열이 실제로 충분히 큰지 확인해야합니다 . 내가 기억하는 한, 표준은 실제로 Pixel정렬과 같 std::byte거나 패딩이없는 것을 요구하지 않습니다.


또한 이것 중 어느 것도 Pixel사소하거나 실제로 다른 재산에 의존하지 않습니다 . std::byte배열의 크기가 충분하고 Pixel객체에 적합하게 정렬되는 한 모든 것이 동일한 방식으로 작동 합니다.


나는 그것이 맞다고 믿는다. 배열 것 (의 unimplementability는해도 std::vector) 문제가 아니었다, 당신은 여전히 필요 것 std::launder대해 게재 위치의 액세스하기 전에 결과 new에디션 Pixel들. std::launder인접한 Pixels가 세탁 된 포인터 에서 접근 할 수 있기 때문에 지금 여기에 UB 가 있습니다 .
Fureeish

@Fureeish 나는 돌아 오기 전에 std::launderUB가 적용되는 이유를 잘 모르겠습니다 result. eel.is/c++draft/ptr.launder#4에 대한 이해로 세탁 포인터를 통해 인접 항목 Pixel에 " 도달 할 수 없습니다" . 그리고 심지어 전체 원래 배열이 원래 포인터에서 도달 할 수 있기 때문에 그것이 어떻게 UB인지 알지 못했습니다 . std::byte
호두

그러나 다음 Pixelstd::byte포인터에서 도달 할 수 없지만 laundered 포인터 에서 온 것입니다 . 나는 이것이 관련 있다고 생각 합니다 . 그래도 정정되어 기쁘다.
Fureeish

@Fureeish 주어진 예제 중 어느 것도 여기에서 적용 할 수 없으며 요구 사항의 정의도 표준과 동일하다고 말할 수 있습니다. 도달 가능성은 객체가 아니라 저장 바이트 수로 정의됩니다. 다음에 차지하는 바이트 Pixel는 원래 포인터에서 나에게 도달 할 수있는 것처럼 보입니다. 원래 포인터 는 "를 만들기 std::byte위한 저장 장치를 구성하는 바이트를 포함하는 배열 의 요소를 가리 키 거나 Z가 요소 "조건 (여기서 적용 이다 , 즉, 요소 자체). PixelZYstd::byte
호두

Pixel지적한 Pixel객체가 배열 객체의 요소가 아니며 다른 관련 객체와 포인터를 상호 변환 할 수 없기 때문에 다음 점유 하는 스토리지 바이트 는 세탁 포인터를 통해 도달 할 수 없다고 생각 합니다. 그러나 나는 또한 std::launder그 깊이에서 처음 으로이 세부 사항에 대해 생각하고 있습니다. 나는 이것에 대해 100 % 확신하지 못한다.
호두
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.