Is it safe to cast size_t to unsigned long int?
In C89, size_t
is defined as an unsigned integer type. Unlike future standards, C89 defines what the list of unsigned integer types are as the following:
- unsigned char
- unsigned short
- unsigned int
- unsigned long
As such, size_t
in C89 will never be larger than unsigned long
, and therefore the cast is always safe - both in that it will not cause any undefined behaviour and in that it will always be large enough to hold the value in entirety.
Worth noting; the C89 standard states: "A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any strictly conforming program" Meaning that no extension could change this behaviour - while still conforming to the C89 standard, as the unsigned integer types have been specifically listed and therefore cannot be altered.
In future standards, this is not a guarantee and while you will not get undefined behaviour - you may lose data where unsigned long
is smaller than size_t
, meaning that you would display incorrect data to your user. In this situation I'd be hesitant to label it as "safe".
As an important additional note; this answer refers to compilers that are compliant with the C89 standard. It is possible for your C89 compiler to be "less than compliant" in the respects above, in which case - treat the behaviour to be similar to that of C99 or newer where you will not see undefined behaviour, but may suffer data loss if size_t
is larger than unsigned long
. To be clear though, this would not be complying with the C89 standard.
Beyond this, while my interpretation of the standard (1.7 Compliance) is that while it states extensions must not alter the behaviour of a "strictly conforming program" and as such cannot alter the fact that size_t
must be unsigned long
at largest without complying; it does not change the fact that such extensions do exist. For example GNU GCC does provide an extension that adds unsigned long long
. In my view this is non-compliant, but the reality is you must be prepared to deal with such things and as such - while the standard says what you are doing is completely safe, you must be prepared for potential data loss where non-compliant compilers or extensions are used.
Please see here for previous discussion on this topic: https://stackoverflow.com/a/39441237/955340
size_t n = foo();
printf("%lu\n", (long unsigned int) n);
Provided that
size_t
is defined as eitherunsigned int
orlong unsigned int
... Is the cast safe?
Yes, the cast is safe with no undefined behavior nor loss of information on C89, C99, C11.
But what happens if that proviso is not true?
Assuming the range of size_t
will be within the range of unsigned long
is very reasonable. Add a compile time test: ref
#include <limits.h>
#if defined(__STDC__)
#if defined(__STDC_VERSION__)
#if (__STDC_VERSION__ >= 199901L)
#include <stdint.h>
#if SIZE_MAX > ULONG_MAX
#error Re-work printf size code
#endif
#endif
#endif
#endif
The point is that when code had a dependency - add a test. Even if it acceptable on all known machines today and historically, the future has unknowns.
C, today, with its immense flexibility does allow SIZE_MAX > ULONG_MAX
, but it is certainly rare. IMO, SIZE_MAX > ULONG_MAX
is beyond the pale.
Such tests are common as from time to time, though possible, it is simply not practicable or budgeted to write super portable code.
#include <limits.h>
#if CHAR_BIT != 8 && CHAR_BIT != 16 && CHAR_BIT != 32 && CHAR_BIT != 64
#error Code depends on char size as a common power of 2.
#endif
I need a portable way to print the value of a variable n of type size_t.
Yet to address OP's top level goal, a simple portable helper function can be written.
// This approach works with any unsigned type
void print_size_t(size_t n) {
if (n >= 10) print_size_t(n/10);
putchar((int) (n%10) + '0');
}
To avoid recursion, a slightly longer function:
#include <limits.h>
void print_size_t(size_t n) {
char buf[sizeof n * CHAR_BIT / 3 + 2]; // 1/3 is more than log2(10)
char *p = &buf[sizeof buf - 1]; // Start at end of buf[]
*p = '\0';
do {
p--;
*p = (char) (n%10 + '0');
n /= 10;
} while (n); // Use a do {} while so print_size_t(0) prints something
fputs(p, stdout);
}