포인터 캐스팅 시 얼라인먼트에 대해 걱정해야 합니까?
제 프로젝트에는 다음과 같은 코드가 있습니다.
// 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*
일반적으로 정렬 요건이 더 엄격합니다.하지만 테크니컬 리더에 따르면 대부분의 컴파일러는 캐스팅 후에도 포인터 값을 그대로 유지하며 코드를 이렇게 작성하면 된다고 합니다.
솔직히 말해서, 난 정말 확신이 안 선다.조사한 결과, 위와 같은 포인터 캐스팅을 사용하는 것에 반대하는 사람들이 있습니다. 예를 들어, 여기와 여기 등입니다.
질문하겠습니다.
- 실제 프로젝트에서 캐스팅한 후 포인터를 참조 해제하는 것이 정말 안전한가요?
- 캐스팅과 C-스타일 ?
reinterpret_cast
- 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();
}
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가지 질문에 대해서:
- 아니요, 앨리어싱과 정렬 때문에 일반적으로 캐스팅 포인터를 참조하는 것은 안전하지 않습니다.
- 에서는 C++의 C-는 C-는 C-는 C-는 C-는 C++로.
reinterpret_cast
. - 아니요, 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));
기본 유형 배열에는 문제가 없습니다.정렬 문제는 구조화된 데이터의 어레이를 처리할 때 발생합니다.struct
C) 어레이의 원래 프라이머리 타입이 캐스트된 타입보다 클 경우 위의 업데이트를 참조하십시오.
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
'sourcecode' 카테고리의 다른 글
Linux에서 C에 디렉토리를 반복적으로 나열하는 방법 (0) | 2022.08.31 |
---|---|
"메인 클래스를 찾을 수 없거나 로드할 수 없습니다"는 무슨 의미입니까? (0) | 2022.08.31 |
어레이의 주소가 C의 값과 같은 이유는 무엇입니까? (0) | 2022.08.30 |
"thread"가 없는 "while(true)"가 되는 이유는 무엇입니까?sleep'이 원인이 되어 Linux에서는 CPU 사용률이 100%이지만 Windows에서는 그렇지 않으면 Windows에서는 CPU 사용률이 100%가 되지 않습니다. (0) | 2022.08.30 |
VUE2js: 소품 변경 후 컴포넌트를 재렌더하는 방법 (0) | 2022.08.30 |