On the use and abuse of size_t
Just about everyone has used the size_t type - and everybody and their grandmothers know what the type means of course! The usual answers I get to ‘tell me what size_t is without Googling it’ is;
- It is the parameter type for malloc to allocate memory!
- It is a platform dependent 32/64 bit unsigned integer type!
- It is what size_type in all the C++ std template hell is! (ok I made this one up - but fuck me the size_type lark really annoys me in the std code.)
In general though the main assumption is that the size_t type is a platform agnostic way to represent any potential size of memory you could allocate or use.
The definition from ISO/IEC 9899:TC3, section 7.17 is;
size_t
which is the unsigned integer type of the result of the sizeof operator
And also relevant is section 7.18.3;
limit of size_t
SIZE_MAX 65535
So the minimum maximum value that size_t must be able to hold is 65535, which is 16 bits of precision, and size_t is only defined to be an unknown unsigned integer type. Let that sink in - size_t can, by the standard, be an uint16_t type on a platform with 32 or 64 bit addressing.
Now before the reader decides to flip their table like this fine gentleman;
I completely understand that on every platform the modern C/C++ developer uses and cares about size_t is either uint32_t on 32 bit platforms, or uint64_t on 64 bit platforms. What annoys me though is that I could take my perfectly portable (among standard MSVC/Clang/GCC compilers) C/C++ code and run it on some random compiler for a given platform, and it could fail in a non-obvious way! This in turn would probably lead me to say ‘this new platform must have shit tools!’ when in fact the platform is supporting the C standard to the letter in their approach.
This is why I don’t use size_t in my interfaces or code, it irks me that some random compiler somewhere could legitimately fail on my code.
So what is the alternative? Really the type we should be using is uintptr_t - EG. the unsigned integer type that matches the width of the pointer type. That way if I decide to allocate all of my memory I will always have a type that can encompass that size value always. But of course, there is a problem there too, from section 7.18.1.4;
The following type designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
intptr_t
The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
uintptr_t
These types are optional.
And just for emphasis ‘These types are optional’. So anyone thinking of using these types too? Please don’t! An entirely conformant compiler and toolchain could give you a big;
What I tend to do (and I understand the grievances in this) is either define my own size type in my interfaces (involves a ton of preprocessor checks for arm/x86/x86-64/ppc/mips/etc) or just use uint64_t for anything that could represent memory. So the next time you see a uint64_t representing an allocatable size in some of my code, you know why!