In (C and) C++, pointers to different types don't necessarily have the same size. I would have hoped void *
is necessarily the largest, but it seems not even this is actually guaranteed.
My question: How can I determine what the largest size of a pointer is on my (compilation target) platform?
Note: I mean any pointer, including pointers to class member functions; things you can get with the &
operator. I don't mean entities which are "colloquially" known as pointers, i.e. not unique_ptr
's or shared_ptr
's and such.
It depends upon different issues like Operating system, CPU architecture etc. Usually it depends upon the word size of underlying processor for example for a 32 bit computer the pointer size can be 4 bytes for a 64 bit computer the pointer size can be 8 bytes.
In 64-bit data models, pointer sizes are always 64 bits.
The type is important. While pointers are all the same size, as they just store a memory address, we have to know what kind of thing they are pointing TO.
A large male can stand 28 inches at the shoulder and weigh up to 75 pounds; a small female might weigh as little as 45 pounds and stand 23 inches.
There are four completely unrelated classes of pointer types in the C++ language: object pointers, function pointers, non-static data member pointers, and non-static member function pointers. The term "pointer" generally only applies to object and function pointer types [basic.compound]/3:
[…] Except for pointers to static members, text referring to “pointers” does not apply to pointers to members. […]
Pointers and pointers to non-static members are actually treated as two completely separate kinds of compound types altogether [basic.compound]/1 (which makes sense since non-static member pointers are more like relative offsets and less like actual addresses).
Except for a conditionally-supported conversion between object and function pointers, the semantics of which (if supported at all) will be implementation-defined [expr.reinterpret.cast]/8, there is no way to convert between these four classes of pointer types.
However, the standard does specify interconvertibility amongst object pointers [expr.reinterpret.cast]/7, interconvertibility amongst function pointers [expr.reinterpret.cast]/6, interconvertiblity amongst data member pointers [expr.reinterpret.cast]/10.2, and interconvertibility amongst member function pointers [expr.reinterpret.cast]/10.1.
As a result, while there is no common pointer type that all other pointer types are related to in general, it is well-defined behavior to cast any object pointer to some arbitrary object pointer type and back. It is well-defined behavior to cast any function pointer to some arbitrary function pointer type and back. It is well-defined behavior to cast any data member pointer to some arbitrary data member pointer type and back. And it is well-defined behavior to cast any member function pointer to some arbitrary member function pointer type and back. And one thing all these different classes of pointer types have in common is that they're all object types [basic.types]/8.
While this does not strictly guarantee that, e.g., all member function pointer types are the same size, it does implicitly establish that any object of some member function pointer type can effectively be used to store any member function pointer value. There may still be member function pointer types larger than others, but they could not possibly hold more information than others since the standard requires that the conversion to and from any other member function pointer type must not lose information (the original value can always be restored). The same argument works analogously for all the other classes of pointer types.
Based on all this, I would argue that it's technically impossible to find "the largest pointer type" in standard C++. However, while it may technically be impossible to find the largest pointer type itself, based on the argument above, it is definitely possible to find an upper bound for the amount of storage needed to reliably store any value of pointer type. While those two are technically different things, in practice, the second one is most likely almost as good as the first (no reasonable compiler will just randomly add lots of padding bits to the value representation of some pointer type just because doing so is technically legal). At least I'm having a hard time imagining what else than store pointer values you could possibly be wanting to do with the kind of information you're asking for.
Using, for example
using generic_obj_ptr = void*;
using generic_fun_ptr = void (*)();
class dummy_t;
using generic_dat_mem_ptr = dummy_t dummy_t::*;
using generic_mem_fun_ptr = void (dummy_t::*)();
you can compute
auto obj_ptr_size = sizeof(generic_obj_ptr_t);
auto fun_ptr_size = sizeof(generic_fun_ptr_t);
auto dat_mem_ptr_size = sizeof(generic_dat_mem_ptr_t);
auto mem_fun_size = sizeof(generic_mem_fun_ptr_t);
auto max_ptr_size = std::max({ sizeof(generic_obj_ptr_t), sizeof(generic_fun_ptr_t), sizeof(generic_dat_mem_ptr_t), sizeof(generic_mem_fun_ptr_t) });
auto max_ptr_align = std::max({ alignof(generic_obj_ptr_t), alignof(generic_fun_ptr_t), alignof(generic_dat_mem_ptr_t), alignof(generic_mem_fun_ptr_t) });
or just use
using ptr_storage_t = std::aligned_union<0U, generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;
or even
using any_ptr_t = std::variant<generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;
or in its pure form:
using any_ptr_t = std::variant<void*, void (*)(), dummy_t dummy_t::*, void (dummy_t::*)()>;
as storage in which any object pointer value can be stored when cast to and from void*
, any function pointer value can be stored when cast to and from void (*)()
, any data member pointer can be stored when cast to and from dummy_t dummy_t::*
, and any member function pointer can be stored when cast to and from void (dummy_t::*)()
.
play with it here
The task of wrapping this in a class that takes care of all the casting for storing arbitrary values of any pointer type (don't forget to deal with possible cv qualification), shall be left as an exercise for the reader, mainly because I would really like to sleep well tonight…
There are 3 different types of pointers, which can have a different size:
A void *
is guaranteed to be large enough to hold every pointer-to-object according to the C++17 Standard 6.9.2.5:
A pointer to cv-qualified ([basic.type.qualifier]) or cv-unqualified void can be used to point to objects of unknown type. Such a pointer shall be able to hold any object pointer. An object of type cv void* shall have the same representation and alignment requirements as cv char*.
class A;
typedef void (A::*a_func_ptr)(void);
typedef void (*func_ptr)(void);
size_t a = sizeof(a_func_ptr), b = sizeof(func_ptr), c = sizeof(void*);
std::cout << std::max(a, std::max(b, c)) << std::endl;
should do the job.
edit: The C++17 Standard 6.9.2.3 says
Except for pointers to static members, text referring to “pointers” does not apply to pointers to members.
So, the largest possible pointer is either a void *
or a function pointer:
std::cout << std::max(sizeof(void*), sizeof(void(*)(void))) << std::endl;
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