정수 <-> 포인터 캐스트가 실제로 올바른 때는 언제입니까?
일반적인 민속은 다음과 같이 말합니다.
유형 시스템에는 이유가 있습니다. 정수와 포인터는 고유 한 유형이며, 이들 사이의 캐스트는 대부분의 경우 잘못된 행위이며 설계 오류를 나타낼 수 있으므로 피해야합니다.
이러한 캐스트를 수행하는 경우에도, 어떤 가정은 (주조 정수와 포인터의 크기 변경할 수 없다
void*
에int
코드가 64에서 실패 할 수있는 가장 간단한 방법입니다), 대신 중int
하나를 사용해야intptr_t
또는uintptr_t
에서stdint.h
.
것을 알고, 때 실제로 유용 등의 캐스트를 수행하기 위해?
(참고 : 이식성에 비해 코드가 약간 짧다고해서 "실제로 유용한"것으로 간주되지는 않습니다.)
내가 아는 한 가지 사례 :
- 일부 잠금없는 다중 프로세서 알고리즘 은 2 바이트 이상으로 정렬 된 포인터가 일부 중복성을 가지고 있다는 사실을 이용합니다. 그런 다음 포인터의 최하위 비트를 부울 플래그로 사용합니다. 적절한 명령어 세트가있는 프로세서를 사용하면 잠금 메커니즘이 필요하지 않을 수 있습니다 (포인터와 부울 플래그가 분리 된 경우 필요함).
(참고 :이 방법은 java.util.concurrent.atomic.AtomicMarkableReference를 통해 Java에서도 안전하게 수행 할 수 있습니다.)
더 있나요?
나는 때때로 포인터가 어떻게 든 해시 섬의 일부가 될 필요가있을 때 정수에 대한 포인터를 캐스팅합니다. 또한 포인터가 항상 하나 또는 두 개의 여분의 비트가 남아 있음을 보장하는 특정 구현에서 일부 비트 조작을 수행하기 위해 정수로 캐스팅합니다. 여기서 AVL 또는 RB 트리 정보를 추가적인 회원. 그러나 이것은 모두 구현에 따라 다르므로 어떤 종류의 일반적인 솔루션으로 생각하지 않는 것이 좋습니다. 또한 때때로 위험 포인터가 그러한 것으로 구현 될 수 있다고 들었습니다.
어떤 상황에서는 요청 ID로 서버에 전달하는 객체 별 고유 ID가 필요합니다. 상황에 따라 메모리를 절약해야하고 그만한 가치가있는 경우 개체의 주소를 이러한 ID로 사용하고 일반적으로 정수로 캐스팅해야합니다.
임베디드 시스템 (예 : 캐논 카메라, chdk 참조)으로 작업 할 때 종종 마법의 추가 기능이 있으므로 (void*)0xFFBC5235
거기에서도 이와 유사한 것이 종종 발견됩니다.
편집하다:
그냥 우연히 (내 마음 속에서) pthread_self()
일반적으로 부호없는 정수에 대한 typedef 인 pthread_t를 반환합니다. 내부적으로는 문제의 스레드를 나타내는 일부 스레드 구조체에 대한 포인터입니다. 일반적으로 불투명 핸들을 위해 다른 곳에서 사용할 수 있습니다.
일반적으로 유형의 정렬을 확인할 때 잘못 정렬 된 메모리가 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
요점을 보여줍니다)
절반의 공간을 사용하여 이중 연결 목록 저장
XOR 링크 된 목록은 같은 크기의 단일 값으로 다음 및 이전 포인터를 결합한 제품입니다. 두 포인터를 함께 xor-ing하여이를 정수처럼 처리해야합니다.
내 생각에 가장 유용한 경우는 실제로 프로그램을 훨씬 더 효율적으로 만들 수있는 잠재력이있는 경우입니다. 많은 표준 및 공통 라이브러리 인터페이스가 void *
일종의 콜백 함수로 다시 전달 하는 단일 인수를 취합니다 . 콜백에 많은 양의 데이터가 필요하지 않고 단일 정수 인수 만 필요하다고 가정합니다.
함수가 반환되기 전에 콜백이 발생하면 로컬 (자동) int
변수 의 주소를 전달하기 만하면됩니다 . 그러나이 상황에 대한 가장 좋은 실제 예 pthread_create
는 "콜백"이 병렬로 실행되고 pthread_create
반환 전에 포인터를 통해 인수를 읽을 수 있다는 보장이없는 경우입니다 . 이 경우 3 가지 옵션이 있습니다.
malloc
단일int
및 새 스레드를 읽도록free
합니다.int
및 동기화 객체 (예 : 세마포어 또는 장벽)를 포함하는 호출자 로컬 구조체에 포인터를 전달하고 호출자가 를 호출 한 후 대기하도록합니다pthread_create
.- 주조
int
로void *
와 값을 전달한다.
옵션 3은 추가 동기화 단계를 포함하는 다른 선택보다 훨씬 더 효율적입니다 (옵션 1의 경우 동기화는 malloc
/ free
에 있으며 할당 및 해제 스레드가 동일하지 않기 때문에 비용이 거의들 것입니다) .
한 가지 예는 Windows에 있습니다 (예 : SendMessage()
및 PostMessage()
함수). 그들은 걸릴 HWnd
(윈도우에 대한 핸들), 메시지 (필수 유형), 메시지에 대한 두 개의 매개 변수하는 WPARAM
과를 LPARAM
. 두 매개 변수 유형은 모두 필수이지만 때로는 보내는 메시지에 따라 포인터를 전달해야합니다. 그런 다음 LPARAM
또는에 대한 포인터를 캐스팅해야합니다 WPARAM
.
나는 일반적으로 전염병처럼 그것을 피할 것 입니다. 포인터를 저장해야하는 경우 가능하면 포인터 유형을 사용하십시오.
임베디드 시스템에서 레지스터가 메모리 맵의 고정 주소에있는 메모리 매핑 된 하드웨어 장치에 액세스하는 것은 매우 일반적입니다. 나는 종종 C와 C ++에서 하드웨어를 다르게 모델링하지만 (C ++를 사용하면 클래스와 템플릿을 활용할 수 있음) 일반적인 아이디어는 둘 다에 사용할 수 있습니다.
간단한 예 : 하드웨어에 타이머 주변 장치가 있고 2 개의 32 비트 레지스터가 있다고 가정합니다.
고정 속도 (예 : 마이크로 초마다)로 감소하는 자유 실행 "틱 카운트"레지스터
타이머를 시작하고, 타이머를 중지하고, 카운트를 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 가지 변형이 있으며 장단점은 영원히 토론 할 수 있지만 여기서 요점은 정수를 포인터로 캐스팅하는 일반적인 사용을 설명하는 것입니다. 이 코드는 이식 가능하지 않으며 특정 장치에 연결되어 있으며 메모리 영역이 제한이 없다고 가정합니다.
컴파일러 + 플랫폼 조합의 동작에 대해 완전히 알고 있고이를 악용하려는 경우가 아니라면 이러한 캐스트를 수행하는 것은 결코 유용하지 않습니다 (질문 시나리오는 그러한 예 중 하나입니다).
The reason I say it is never useful is because in general, you don't have control of the compiler, nor full knowledge of what optimisations it may choose to do. Or to put it another way, you aren't able to precisely control the machine code it will generate. So in general, you can't implement this sort of trick safely.
The only time i cast a pointer
to an integer
is when i want to store a pointer, but the only storage i have available is an integer.
When is it correct to store pointers in ints? It's correct when you treat it as what it is: The use of a platform or compiler specific behavior.
The problem is only when you have platform/compiler specific code littered throughout your application and you have to port your code to another platform, because you've made assumptions that don't hold true any longer. By isolating that code and hiding it behind an interface that makes no assumptions about the underlying platform, you eliminate the problem.
So as long as you document the implementation, separate it behind a platform independent interface using handles or something that doesn't depend on how it works behind the scenes, and then make the code compile conditionally only on platforms/compilers where it's been tested and works, then there's no reason for you not to use any sort of voodoo magic you come across. You can even include large chunks of assembly language, proprietary API calls, and kernel system calls if you want.
That said, if your "portable" interface uses integer handles, integers are the same size as pointers on the implementation for a certain platform, and that implementation uses pointers internally, why not simply use the pointers as integer handles? A simple cast to an integer makes sense in that case, because you cut out the necessity of a handle/pointer lookup table of some sort.
You may need to access memory at a fixed known address, then your address is an integer and you need to assign it to a pointer. This is somewhat common in embedded systems. Conversely, you may need to print a memory address and thus need to cast it to integer.
Oh, and don't forget you need to assign and compare pointers to NULL, which is usually a pointer cast of 0L
I have one use for such a thing in network wide ID's of objects. Such a ID would combine identifications of machine (e.g IP address), process id and the address of the object. To be sent over a socket the pointer part of such an ID must be put into a wide enough integer such that it survives transport back and forth. The pointer part is only interpreted as a pointer (= cast back to a pointer) in the context where this makes sense (same machine, same process), on other machines or in other processes it just serves to distinguish different objects.
The things one needs to have that working is the existence uintptr_t
and uint64_t
as a fix width integer type. (Well only works on machines that have at most 64 addresses :)
under x64, on can use the upper bits of pointers for tagging (as only 47 bits are used for the actual pointer). this is great for things like run time code generation (LuaJIT uses this technique, which is an ancient technique, according to the comments), to do this tagging and tag checking you either need a cast or a union
, which basically amount to the same thing.
casting of pointers to integers can also be very helpful in memory management systems that make use of binning, ie: one would be able to easily find the bin/page for an address via some math, an example from a lockless allocator I wrote a while back:
inline Page* GetPage(void* pMemory)
{
return &pPages[((UINT_PTR)pMemory - (UINT_PTR)pReserve) >> nPageShift];
}
I've used such systems when I'm trying to walk byte-by-byte through an array. Often times, the pointer will walk multiple bytes at a time, which causes problems that are very difficult to diagnose.
For example, int pointers:
int* my_pointer;
moving my_pointer++
will result in advancing 4 bytes (in a standard 32-bit system). However, moving ((int)my_pointer)++
will advance it one byte.
It's really the only way to accomplish it, other than casting your pointer to a (char*). ((char*)my_pointer)++
Admittedly, the (char*) is my usual method since it makes more sense.
Pointer values can also be a useful source of entropy for seeding a random number generator:
int* p = new int();
seed(intptr_t(p) ^ *p);
delete p;
The boost UUID library uses this trick, and some others.
There is an old and good tradition to use pointer to an object as a typeless handle. For instance, some people use it for implementing interaction between two C++ units with flat C-style API. In that case, handle type is defined as one of integer types and any method have to convert a pointer into an integer before it can be transfered to another method that expects an abstract typeless handle as one of its parameter. In addition, sometimes there is no other way to break up a circular dependency.
참고URL : https://stackoverflow.com/questions/7146813/when-is-an-integer-pointer-cast-actually-correct
'Programing' 카테고리의 다른 글
Java의 어떤 작업이 원 자성으로 간주됩니까? (0) | 2020.10.15 |
---|---|
Xcode 4를 사용한 iPhone Ad Hoc 빌드 (0) | 2020.10.15 |
동기 코드를 비동기 호출로 래핑 (0) | 2020.10.15 |
Python 디코딩 유니 코드는 지원되지 않습니다. (0) | 2020.10.15 |
CSS를 사용한 SVG 그라디언트 (0) | 2020.10.15 |