sourcecode

실제로 포인트 캐스트가 올바른 경우는 언제입니까?

copyscript 2022. 7. 27. 23:52
반응형

실제로 포인트 캐스트가 올바른 경우는 언제입니까?

속설은 다음과 같다.

  • 유형 체계가 존재하는 데는 이유가 있다.정수와 포인터는 구별되는 유형이며, 이들 사이의 캐스팅은 대부분의 경우 잘못된 것으로 설계 오류를 나타낼 수 있으므로 피해야 합니다.

  • 이러한 캐스팅이 이루어지는 경우에도 정수 및 포인터의 크기에 대해 어떠한 가정도 해서는 안 된다(캐스팅void*로.intx64)에서 코드가 실패하도록 하는 가장 간단한 방법입니다.int사용해야 한다intptr_t또는uintptr_t부터stdint.h.

그렇다면 실제로 언제 이런 캐스팅을 하는 것이 도움이 될까요?

(주의: 휴대성 가격에 대해 조금 더 짧은 코드를 사용하는 것은 "실제로 유용한" 코드라고 간주되지 않습니다.)


내가 아는 한 가지 사례:

  • 일부 잠금 프리 멀티프로세서알고리즘은 2바이트 이상의 포인터에 용장성이 있다는 사실을 이용합니다.그런 다음 포인터의 가장 낮은 비트를 부울 플래그로 사용합니다.프로세서에 적절한 명령어 세트가 있으면 잠금 메커니즘이 필요하지 않을 수 있습니다(포인터와 부울 플래그가 분리된 경우 필요).
    (주의: 이 방법은 Java에서 java.util.concurrent.atomic을 통해 안전하게 실행할 수도 있습니다.Atomic Markable Reference)

더 할말 있나?

절반의 공간을 사용하여 이중으로 연결된 목록 저장

XOR Linked List는 다음 포인터와 사전 포인터를 동일한 크기의 단일 값으로 결합합니다.이것은 두 포인터를 함께 xoring함으로써 이루어지며, 이는 두 포인터를 정수처럼 취급해야 합니다.

나는 때때로 정수가 해시섬의 일부가 될 필요가 있을 때 포인터를 던진다.또, 포인터에 항상 1~2개의 스페어 비트가 남아 있는 것을 보증하는 특정의 설정에서는, 추가의 멤버를 가지는 대신에 왼쪽/오른쪽 포인터에 AVL 또는 RB 트리 정보를 부호화할 수 있도록, 그것들을 정수로 캐스트 합니다.그러나 이 모든 것은 구현에 따라 다르기 때문에 일반적인 솔루션이라고 생각하지 않는 것이 좋습니다.또한 해저드 포인터도 그러한 것으로 구현될 수 있다고 들었습니다.

경우에 따라서는 요구 ID로서 서버에 전달하는 오브젝트별 고유 ID가 필요합니다.메모리를 절약할 필요가 있는 상황에 따라서는, 가치가 있는 ID로서 오브젝트의 주소를 사용하고, 통상, 그것을 정수로 할 필요가 있습니다.

임베디드 시스템(캐논 카메라 등, chdk 참조)을 사용하는 경우, 매직주소가 있는 경우가 많기 때문에,(void*)0xFFBC5235혹은 비슷한 것이 종종 그곳에서도 발견된다.

편집:

그냥 (생각에) 걸려 넘어졌다pthread_self()보통 typedef인 pthread_t를 부호 없는 정수로 반환합니다.내부적으로는 문제의 스레드를 나타내는 일부 스레드 구조에 대한 포인터입니다.일반적으로 불투명한 손잡이로 다른 곳에서 사용할 수 있습니다.

임베디드 시스템에서는 레지스터가 메모리 맵의 고정 주소에 있는 메모리 맵 하드웨어 디바이스에 액세스하는 것이 매우 일반적입니다.C와 C의 하드웨어 모델링을 달리하는 경우가 많습니다.C++(C++에서는 클래스와 템플릿을 활용할 수 있지만, 일반적인 아이디어는 두 가지 모두에 사용할 수 있습니다.

간단한 예: 하드웨어에 타이머 페리페럴이 있고 32비트 레지스터가 2개 있다고 가정합니다.

  • 일정한 비율로 감소하는 자유실행형 "틱 카운트" 레지스터(예를 들어 마이크로초마다)

  • 제어 레지스터: 타이머 시작, 타이머 정지, 카운트를 0으로 줄일 때 타이머 인터럽트 활성화 등을 할 수 있습니다.

(실제 타이머 페리페럴은 보통 상당히 복잡합니다).

이들 레지스터는 각각 32비트 값이며 타이머 페리페럴의 "기본 주소"는 0xFFFF.0000입니다.다음과 같이 하드웨어를 모델링할 수 있습니다.

// Treat these HW regs as volatile
typedef uint32_t volatile hw_reg;

// C friendly, hence the typedef
typedef struct
{
  hw_reg TimerCount;
  hw_reg TimerControl;
} TIMER;

// Cast the integer 0xFFFF0000 as being the base address of a timer peripheral.
#define Timer1 ((TIMER *)0xFFFF0000)

// Read the current timer tick value.
// e.g. read the 32-bit value @ 0xFFFF.0000
uint32_t CurrentTicks = Timer1->TimerCount;

// Stop / reset the timer.
// e.g. write the value 0 to the 32-bit location @ 0xFFFF.0004
Timer1->TimerControl = 0;

이 접근법에는 100가지 종류가 있으며, 장점과 단점은 영원히 논의될 수 있지만, 여기서 포인터에 정수를 던지는 일반적인 용도를 보여주는 것에 불과합니다.이 코드는, 포터블이 아니고, 특정의 디바이스에 관련지어져 있는 것, 및 메모리 영역이 출입이 금지되어 있지 않은 것을 전제로 하고 있는 것 등에 주의해 주세요.

SIGBUS/SIGSEGV가 아닌 어설션으로 잘못 정렬된 메모리가 잡히도록 일반적으로 유형의 정렬을 확인할 때 유용합니다.

예:

#include <xmmintrin.h>
#include <assert.h>
#include <stdint.h>

int main() {
  void *ptr = malloc(sizeof(__m128));
  assert(!((intptr_t)ptr) % __alignof__(__m128));
  return 0;
}

(진짜 코드로 말하면 난 그냥 도박을 하지 않을 거야)malloc단, 요점을 나타내고 있습니다.)

제 생각에 가장 유용한 사례는 프로그램을 훨씬 더 효율적으로 만들 수 있는 가능성이 있는 경우입니다: 많은 표준 및 공통 라이브러리 인터페이스가 단일 라이브러리 인터페이스를 사용합니다.void *이 인수는 일종의 콜백 함수로 돌아갑니다.콜백에 대량의 데이터가 필요하지 않고 하나의 정수 인수만 필요하다고 가정합니다.

함수가 반환되기 전에 콜백이 발생할 경우 로컬(자동) 주소를 전달하기만 하면 됩니다.int모든 게 잘 되고 있어하지만 이 상황에 대한 가장 좋은 실제 예는pthread_create여기서 "콜백"은 병렬로 실행되며 그 전에 포인터를 통해 인수를 읽을 수 있다는 보장은 없습니다.pthread_create돌아온다.이 경우, 다음의 3개의 옵션이 있습니다.

  1. malloc독신자int새 스레드를 읽고free바로 그거에요.
  2. 를 포함하는 발신자 로컬 구조체에 포인터를 전달합니다.int동기 객체(세마포 또는 장벽 등)를 호출한 후 발신자가 대기하도록 합니다.pthread_create.
  3. 캐스트int로.void *가치로 넘길 수 있어요.

옵션 3은 다른 옵션보다 매우 효율적이며, 두 옵션 모두 추가 동기화 단계를 수반합니다(옵션 1의 경우 동기화는malloc/free스레드 할당과 해방은 동일하지 않기 때문에 비용이 드는 것은 거의 확실합니다).

예를 들면, Windows 의 경우,SendMessage()그리고.PostMessage()기능들.그들은 한 가지 방법을 취한다.HWnd(창에 대한 핸들), 메시지(통합형) 및 메시지에 대한 두 가지 파라미터, 즉WPARAM그리고LPARAM두 파라미터 유형은 모두 필수적이지만 보내는 메시지에 따라 포인터를 전달해야 할 수 있습니다.그런 다음 포인터를 던져야 합니다.LPARAM또는WPARAM.

는 보통 그것을 전염병처럼 피한다.포인터를 저장해야 하는 경우 가능한 경우 포인터 유형을 사용합니다.

컴파일러+플랫폼 조합의 동작을 완전히 알고 그것을 이용하고 싶은 경우를 제외하고, 이러한 캐스트를 실행하는 것은 결코 도움이 되지 않습니다(질문 시나리오는 그러한 예 중 하나입니다).

제가 이것이 결코 유용하지 않다고 말하는 이유는 일반적으로 컴파일러를 제어할 수 없고 컴파일러가 어떤 최적화를 선택할 수 있는지에 대한 완전한 지식이 없기 때문입니다.다른 말로 하자면, 생성될 기계 코드를 정확하게 제어할 수 없습니다.따라서 일반적으로 이러한 기술을 안전하게 구현할 수 없습니다.

내가 캐스팅한 유일한 시간은pointer에 대해서integer포인터를 저장하고 싶은데 사용할 수 있는 저장공간은 정수뿐입니다.

포인터를 ints에 저장하는 것이 올바른 시기는 언제입니까?있는 그대로 취급하는 것이 옳습니다.플랫폼 또는 컴파일러 고유의 동작 사용.

이 문제는 플랫폼/컴파일러 고유의 코드가 어플리케이션 전체에 혼재되어 있을 때에만 해당 코드를 다른 플랫폼으로 포팅해야 하는 경우입니다.이는 더 이상 해당되지 않는 가정을 했기 때문입니다.이 코드를 분리하여 기반이 되는 플랫폼에 대한 가정을 하지 않는 인터페이스 뒤에 숨김으로써 문제를 해소할 수 있습니다.

따라서 실장을 문서화하는 한 백그라운드에서 동작하는 방법에 의존하지 않는 핸들 등을 사용하여 플랫폼에 의존하지 않는 인터페이스 뒤에서 코드를 분리한 후 테스트 및 동작하는 플랫폼/컴파일러에서만 코드를 조건부로 컴파일합니다.그러면 어떤 종류의 voodoo m도 사용하지 않을 필요가 없습니다.agic을 발견하게 됩니다.필요에 따라 어셈블리 언어, 자체 API 호출 및 커널 시스템 호출의 큰 청크를 포함할 수도 있습니다.

즉, 만약 당신의 "휴대용" 인터페이스가 정수 핸들을 사용하고, 정수는 특정 플랫폼의 구현상의 포인터와 같은 크기이며, 구현은 내부적으로 포인터를 사용한다면, 왜 단순히 포인터를 정수 핸들로 사용하지 않는가?이 경우 핸들/포인트 룩업 테이블이 필요하지 않으므로 정수로 단순 캐스트하는 것이 좋습니다.

기존의 고정 주소의 메모리에 액세스 할 필요가 있는 경우가 있습니다.주소는 정수이며, 포인터에 할당해야 합니다.이는 임베디드 시스템에서 다소 일반적입니다.반대로 메모리 주소를 인쇄해야 하므로 정수로 캐스트해야 합니다.

아, 그리고 보통 0L의 포인터 캐스트인 NULL에 포인터를 할당하고 비교해야 한다는 것을 잊지 마십시오.

오브젝트의 네트워크 전체 ID에서 이러한 용도는 1가지입니다.이러한 ID는 머신의 ID(예: IP 주소), 프로세스 ID 및 객체의 주소를 결합합니다.소켓을 통해 전송하려면 이러한 ID의 포인터 부분이 앞뒤로 전송될 수 있도록 충분히 넓은 정수로 배치되어야 합니다.포인터 부분은 다른 기계나 다른 프로세스에서 서로 다른 개체를 구별하는 역할을 하는 상황에서만 포인터(= 포인터로 다시 캐스트)로 해석됩니다.

그 일을 하기 위해 필요한 것은 존재감이다.uintptr_t그리고.uint64_t(최대 64개의 주소를 가진 머신에서만 동작:)

x64에서 on은 포인터의 상위 비트를 태그 부착에 사용할 수 있습니다(실제 포인터에는 47비트만 사용되므로).런타임 코드 생성(LuaJ)과 같은 작업에 적합합니다.코멘트에 의하면, IT부문은, 낡은 기술인 이 기술을 사용해, 이 태그 붙이기 및 태그 체크를 실시해, 캐스트나 태그가 필요한지를 확인합니다.union그건 기본적으로 같은 것에 해당합니다.

포인터를 정수로 캐스팅하는 것은 비닝을 사용하는 메모리 관리 시스템에서도 매우 유용합니다.즉, 얼마 전에 썼던 잠금 없는 할당기에서 나온 예와 같은 계산을 통해 주소의 bin/page를 쉽게 찾을 수 있습니다.

inline Page* GetPage(void* pMemory)
{
    return &pPages[((UINT_PTR)pMemory - (UINT_PTR)pReserve) >> nPageShift];
}

어레이를 바이트 단위로 이동하려고 할 때 이런 시스템을 사용해 본 적이 있습니다.포인터가 한 번에 여러 바이트를 이동하는 경우가 많기 때문에 진단이 매우 어려운 문제가 발생합니다.

예를 들어 int 포인터:

int* my_pointer;

움직이다my_pointer++(표준 32비트시스템에서는) 4바이트가 진행됩니다.단, 이사((int)my_pointer)++1바이트 앞당깁니다.

(char*)에 포인터를 넣는 것 이외에는 이 방법밖에 없습니다.((char*)my_pointer)++

물론, (char*)가 더 말이 되기 때문에 제가 자주 사용하는 방법입니다.

포인터 값은 난수 발생기를 시드하는 데 유용한 엔트로피 소스가 될 수도 있습니다.

int* p = new int();
seed(intptr_t(p) ^ *p);
delete p;

Boost UUID 라이브러리는 이 트릭과 다른 트릭을 사용합니다.

오브젝트에 대한 포인터를 타입 없는 핸들로 사용하는 오래된 좋은 전통이 있습니다.예를 들어 플랫 C 스타일 API를 사용하여 2개의 C++ 유닛 간의 상호작용을 구현하기 위해 사용하는 사람도 있습니다.이 경우 핸들 타입은 정수 타입 중 하나로 정의되며, 어떤 메서드든 포인터를 정수 타입으로 변환해야 추상 타입이 없는 핸들을 파라미터의 하나로 기대하는 다른 메서드로 전송할 수 있습니다.또한 순환 의존 관계를 끊을 다른 방법이 없는 경우도 있습니다.

언급URL : https://stackoverflow.com/questions/7146813/when-is-an-integer-pointer-cast-actually-correct

반응형