sourcecode

포인터 캐스팅 시 얼라인먼트에 대해 걱정해야 합니까?

copyscript 2022. 8. 30. 22:26
반응형

포인터 캐스팅 시 얼라인먼트에 대해 걱정해야 합니까?

제 프로젝트에는 다음과 같은 코드가 있습니다.

// raw data consists of 4 ints
unsigned char data[16];
int i1, i2, i3, i4;
i1 = *((int*)data);
i2 = *((int*)(data + 4));
i3 = *((int*)(data + 8));
i4 = *((int*)(data + 12));

에게 이 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★unsigned char* a까지int*일반적으로 정렬 요건이 더 엄격합니다.하지만 테크니컬 리더에 따르면 대부분의 컴파일러는 캐스팅 후에도 포인터 값을 그대로 유지하며 코드를 이렇게 작성하면 된다고 합니다.

솔직히 말해서, 난 정말 확신이 안 선다.조사한 결과, 위와 같은 포인터 캐스팅을 사용하는 것에 반대하는 사람들이 있습니다. 예를 들어, 여기와 여기 입니다.

질문하겠습니다.

  1. 실제 프로젝트에서 캐스팅한 후 포인터를 참조 해제하는 것이 정말 안전한가요?
  2. 캐스팅과 C-스타일 ?reinterpret_cast
  3. C와 C++ 사이에 차이가 있나요?

1. 실제 프로젝트에서 캐스팅 후 포인터를 참조 해제하는 것이 정말 안전한가?

포인터가 올바르게 정렬되지 않으면 문제가 발생할 수 있습니다.를 실제로, 이 오류 때문에 를 제가 직접 . 주조로 인한 생산 코드입니다.char*더 엄격하게 정렬된 유형으로.명백한 오류가 발생하지 않더라도 성능 저하와 같은 덜 명백한 문제가 발생할 수 있습니다.UB를 피하기 위해 표준을 엄격히 따르는 것은 문제가 즉시 발견되지 않더라도 좋은 생각입니다.(그리고 코드가 깨지는 한 가지 규칙은 엄격한 에일리어스 규칙, § 3.10/10*)

더 나은 대안은 다음을 사용하는 것입니다.std::memcpy() ★★★★★★★★★★★★★★★★★」std::memmove버퍼가 겹치는 경우(또는 더 좋은 경우)

unsigned char data[16];
int i1, i2, i3, i4;
std::memcpy(&i1, data     , sizeof(int));
std::memcpy(&i2, data +  4, sizeof(int));
std::memcpy(&i3, data +  8, sizeof(int));
std::memcpy(&i4, data + 12, sizeof(int));

일부 컴파일러는 다른 컴파일러보다 char 배열이 필요 이상으로 엄밀하게 정렬되도록 합니다.이것은 프로그래머가 자주 잘못 알고 있기 때문입니다.

#include <cstdint>
#include <typeinfo>
#include <iostream>

template<typename T> void check_aligned(void *p) {
    std::cout << p << " is " <<
      (0==(reinterpret_cast<std::intptr_t>(p) % alignof(T))?"":"NOT ") <<
      "aligned for the type " << typeid(T).name() << '\n';
}

void foo1() {
    char a;
    char b[sizeof (int)];
    check_aligned<int>(b); // unaligned in clang
}

struct S {
    char a;
    char b[sizeof(int)];
};

void foo2() {
    S s;
    check_aligned<int>(s.b); // unaligned in clang and msvc
}

S s;

void foo3() {
    check_aligned<int>(s.b); // unaligned in clang, msvc, and gcc
}

int main() {
    foo1();
    foo2();
    foo3();
}

http://ideone.com/FFWCjf

2. C스타일의 캐스팅과 refret_cast는 어떤 차이가 있습니까?

사정에 따라 다르겠지.C스타일의 출연진은 관련된 유형에 따라 다른 일을 한다.포인터 유형 간의 C 스타일 캐스팅은 rexterret_cast와 같은 결과를 낳는다. § 5.4 명시적 유형 변환(캐스트 표기법) 및 § 5.2.9-11 참조.

3. C와 C++는 어떤 차이가 있습니까?

C에서 합법적인 유형을 다루는 한 그런 일은 없어야 합니다.


* 또 다른 문제는 C++가 하나의 포인터 타입에서 보다 엄격한 얼라인먼트 요건을 가진 타입으로의 주조 결과를 규정하지 않는다는 것입니다.이는 정렬되지 않은 포인터를 나타낼 수 없는 플랫폼을 지원하기 위한 것입니다.그러나 오늘날의 일반적인 플랫폼은 정렬되지 않은 포인터를 나타낼 수 있으며 컴파일러는 이러한 캐스트의 결과를 예상대로 지정합니다.따라서 이 문제는 에일리어스 위반의 부차적인 문제입니다.[expr.reinterpret.cast]/7 참조.

그건 괜찮지 않아, 정말로.정렬이 잘못되어 코드가 엄밀한 에일리어스를 위반할 수 있습니다.당신은 그것을 명시적으로 풀어야 합니다.

i1 = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;

이것은 확실히 명확한 동작이며, 포인터 캐스트와는 달리 엔디안니스에 의존하지 않습니다.

이 예에서는 첫 번째 문자 포인터가 올바르게 정렬되어 있으면 최신의 거의 모든 CPU에서 작업을 수행할 때 안전합니다.일반적으로 이것은 안전하지 않고 작동한다고 보장되지 않습니다.

첫 번째 문자 포인터가 올바르게 정렬되지 않으면 x86 및 x86_64에서는 동작하지만 다른 아키텍처에서는 실패할 수 있습니다.운이 좋으면 추락사고만 날 뿐이고 코드를 고칠 수 있어요.운이 나쁘면, 비정렬의 액세스는, operating system의 트랩 핸들러에 의해서 수정되어 왜 이렇게 느린지에 대한 명확한 피드백이 없는 상태가 됩니다(일부 코드에 대해서는 빙하적으로 느린 것이 20년 전 알파에서는 큰 문제였습니다).

x86 & co에서도 얼라인먼트되지 않은 접근은 느려집니다.

라면, ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」memcpy 컴파일러에 최적화가 되어 있을 입니다.memcpy 일을 옳은 일을 해라.memcpy그 자체에는 얼라인먼트 검출 기능이 있어 가장 빠른 처리를 할 수 있습니다.

또한 당신의 예는 한 가지 점에서 틀렸습니다: size of(int)가 항상 4는 아닙니다.

char는 "Buffered data"를 사용합니다.memcpy:

unsigned char data[4 * sizeof(int)];
int i1, i2, i3, i4;
memcpy(&i1, data, sizeof(int));
memcpy(&i2, data + sizeof(int), sizeof(int));
memcpy(&i3, data + 2 * sizeof(int), sizeof(int));
memcpy(&i4, data + 3 * sizeof(int), sizeof(int));

캐스팅은 에일리어싱을 위반합니다.즉, 컴파일러와 옵티마이저는 소스 오브젝트를 초기화되지 않은 것으로 취급할 수 있습니다.

3가지 질문에 대해서:

  1. 아니요, 앨리어싱과 정렬 때문에 일반적으로 캐스팅 포인터를 참조하는 것은 안전하지 않습니다.
  2. 에서는 C++의 C-는 C-는 C-는 C-는 C-는 C++로.reinterpret_cast.
  3. 아니요, C와 C++는 캐스트 베이스의 에일리어스에 동의합니다.유니언 베이스 에일리어스의 처리에는 차이가 있습니다(경우에 따라서는 C가 허가하지만 C++는 허가하지 않습니다).

업데이트: 저는 당신의 예시와 같이 작은 타입이 큰 타입에 비해 상대적으로 정렬되지 않을 수 있다는 사실을 간과했습니다.를 int의의 배열로 .어레이를 int의 어레이로 선언하고 다음 주소로 캐스트합니다.char *그런 식으로 접근해야 할 때 말이죠.

// raw data consists of 4 ints
int data[4];

// here's the char * to the original data
char *cdata = (char *)data;
// now we can recast it safely to int *
i1 = *((int*)cdata);
i2 = *((int*)(cdata + sizeof(int)));
i3 = *((int*)(cdata + sizeof(int) * 2));
i4 = *((int*)(cdata + sizeof(int) * 3));

기본 유형 배열에는 문제가 없습니다.정렬 문제는 구조화된 데이터의 어레이를 처리할 때 발생합니다.structC) 어레이의 원래 프라이머리 타입이 캐스트된 타입보다 클 경우 위의 업데이트를 참조하십시오.

int의 배열에 char 배열을 캐스트하는 것도 문제 없습니다.단, 오프셋 4를 다음과 같이 치환하면 됩니다.sizeof(int)플랫폼 상의 int 사이즈에 일치시키기 위해 코드를 실행합니다.

// raw data consists of 4 ints
unsigned char data[4 * sizeof(int)];
int i1, i2, i3, i4;
i1 = *((int*)data);
i2 = *((int*)(data + sizeof(int)));
i3 = *((int*)(data + sizeof(int) * 2));
i4 = *((int*)(data + sizeof(int) * 3));

바이트 순서가 다른 플랫폼 간에 데이터를 공유하는 경우에만 엔디안니스 문제가 발생한다는 점에 유의하십시오.그렇지 않으면, 완벽하게 괜찮을 거야.

컴파일러 버전에 따라 상황이 어떻게 달라질 수 있는지 보여주는 것이 좋습니다.

얼라인먼트와는 별개로 두 번째 문제가 있습니다: 표준으로 인해,int*로로 합니다.char*그 그 반대는 아니다.char*는 원래 래래 an an an 에서 캐스팅되었다.int*자세한 것은, 투고를 참조해 주세요.

정렬에 대해 걱정할 필요가 있는지 여부는 포인터가 생성된 객체의 정렬에 따라 달라집니다.

정렬 요건이 엄격한 유형으로 주조하면 휴대할 수 없습니다.

char예시와더 필요가 없습니다.char

는 ", "로 할 수 .char *정렬에 관계없이 뒤로 이동합니다.char *포인터는 원본의 더 강한 정렬을 유지합니다.

결합을 사용하여 보다 강하게 정렬된 문자 배열을 만들 수 있습니다.

union u {
    long dummy; /* not used */
    char a[sizeof(long)];
};

유니언의 모든 구성원은 같은 주소로 시작합니다.처음에는 패딩이 없습니다.따라서 스토리지에서 유니언 객체를 정의할 때는 가장 엄격하게 정렬된 부재에 적합한 정렬이 있어야 합니다.

★★★★★★★★★★★★★★★★★★union u는 는 of of of of of above above 、 of 、 of 、 above 、 above 、 above of of of of of of 。long.

얼라인먼트 제한을 위반하면 프로그램이 일부 아키텍처로 이식될 때 크래쉬할 수 있습니다.또는 잘못 정렬된 메모리 액세스가 하드웨어(일부 추가 사이클 비용) 또는 소프트웨어(소프트웨어가 액세스를 에뮬레이트하는 커널에 대한 트랩 비용) 중 어느 쪽에 구현되느냐에 따라 약간 또는 심각한 성능에 영향을 미칠 수 있습니다.

언급URL : https://stackoverflow.com/questions/13881487/should-i-worry-about-the-alignment-during-pointer-casting

반응형