I was reading the Wolfenstein 3D code, and I encountered ISPOINTER
macro:
#define ISPOINTER(x) ((((uintptr_t)(x)) & ~0xffff) != 0)
I know we have std::is_pointer
, but how does this macro work? I tried and failed with strange behavior which I couldn't explained why it's happend:
#define ISPOINTER(x) ((((uintptr_t)(x)) & ~0xffff) != 0)
int main()
{
int* ptr;
int val;
if (ISPOINTER(ptr)) {
std::cout << "`ptr`: Is Pointer" << std::endl;
}
if (ISPOINTER(val)) {
std::cout << "`val`: Is Pointer" << std::endl;
}
}
I don't have any output, but if I add another pointer:
#define ISPOINTER(x) ((((uintptr_t)(x)) & ~0xffff) != 0)
int main()
{
int* ptr;
int val;
int* ptr2;
if (ISPOINTER(ptr)) {
std::cout << "`ptr`: Is Pointer" << std::endl;
}
if (ISPOINTER(val)) {
std::cout << "`val`: Is Pointer" << std::endl;
}
if (ISPOINTER(ptr2)) {
std::cout << "`ptr2`: Is Pointer" << std::endl;
}
}
The output will be:
`ptr`: Is Pointer
What does ISPOINTER
doing? It's undefined behavior?
Let's do this in steps:
((uintptr_t)(x))
is simply a cast from whatever x is into a uintptr_t
(an unsigned integer type capable of storing pointer values)
~0xffff
is a bit-wise complement of 0xffff (which is 16 bits of all 1s). The result of that is a number that is all 1s except the last 16 bits.
((uintptr_t)(x)) & ~0xffff
is a bit-wise AND of the pointer value with the above number. This will effectively zero-out the 16 lowest bits of whatever the pointer value is.
The full expression now just checks if the result is zero or not. So the whole expression basically checks if any bits except the least-significant 16 are set and if so it considers it a pointer.
Since this came from Wolfenstein 3D, they probably made the assumption that all dynamically allocated memory lives in high memory addresses (higher than 2^16). So this is NOT a check if a type is a pointer or not using the type system like std::is_pointer does. This is an assumption based on the target architecture Wolfenstein 3D will likely run on.
Keep in mind that this is not a safe assumption, since "normal" values above 2^16 would also be considered pointers and the memory layout of your process can be very different depending on a lof of factors (e.g. ASLR)
It might be similar to certain parameters in Windows, which can be an ordinal value or a pointer. An ordinal value will be <64K. On that operating system, any legal pointer will be >64K. (On older 16-bit versions of Windows, only 4K was reserved.)
This code may be doing the same thing. A "resource" may be a built-in or registered value referred to by a small number, or a pointer to an ad-hoc object. This macro is used to decide whether to use it as-is or look it up in the table instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With