Braden Best Braden Best - 3 months ago 8
C Question

Regarding `size_t` and the pointer types they measure

When

size_t
is undefined, it's said that the only portable way to get the definition for it is to include one of the headers that has it.

However, as I understand it, using an
#include
in a header file is bad practice, because headers shouldn't include other headers, right?

So it's the onus of the programmer to find a way to make sure a header will compile without devolving into poor programming practice.

As I understand it, the
size_t
type is the smallest type guaranteed to support the longest object your system can handle. E.g. if you run a 64-bit OS, then
size_t
should be 64 bits, because that's how RAM and pointers work in and of themselves.

As for pointers, any given pointer represents an offset from the 0th byte in RAM, where dereferencing that offset will give you the value stored at that offset. And the CPU (in this case, a 64-bit one) is given full reign to access any of the 18-quintillion-or-so bytes it is able to address with the largest unit that it supports, a.k.a. a 64-bit integer, a.k.a. a pointer.

Thus, aren't
size_t
and any given pointer type guaranteed to be the same size?

The question, then, is which of the following would be the best practice for a given header:

void some_function(ptr *some_object, size_t n);
...


The possibilities seem to be:

#include <stddef.h>
// would definitely work...but including a header in a header is ugly practice

typedef long long unsigned size_t
// to account for win64 making `long` 32-bits...but it's possibly non-portable

typedef char * size_t
// I like this one...but am not 100% sure about how portable it is


Further, are there any reasons why using a pointer for the definition of size_t would be undesirable? It seems like it should be fine. As I understand it, a pointer is guaranteed to take up the full size of a register, and should thus be the same size as the architecture's "ideal"
size_t

Answer

When size_t is undefined, it's said that the only portable way to get the definition for it is to include one of the headers that has it.

Correct.

However, as I understand it, using an #include in a header file is bad practice, because headers shouldn't include other headers, right?

You have been misinformed. There is no reason not to include headers in other headers. Multiple #includes of the same header can cause problems if they don't have proper include guards, but the standard headers are guaranteed to have such guards (or equivalent) -- and non-standard headers should have such guards. (<assert.h> is a special case.)

If you need size_t, either in a source file or in a header file, then you should use #include <stddef.h> to make the name size_t visible (or you can include one of the other standard headers that also size_t visible).

Including <stddef.h> in a header will make its declarations visible in any other files that include your header, but that's not a problem.

As I understand it, the size_t type is the smallest type guaranteed to support the longest object your system can handle.

It's an unsigned integer type chosen by the implementation to hold size values. It should be able to hold the size in bytes of any object (though that's not explicitly guaranteed). There's no guarantee that it's the smallest such type, and no benefit in assuming that it is.

As for pointers, any given pointer represents an offset from the 0th byte in RAM, where dereferencing that offset will give you the value stored at that offset.
[...]
Thus, aren't size_t and any given pointer type guaranteed to be the same size?

No, there is no such guarantee. On most modern systems, pointers do represent a byte offset from virtual address 0, but that's not guaranteed. On a system with a segmented memory model, the maximum size of a single object might be much smaller than the size of memory; a pointer might contain a segment indicator and a byte offset, with an object not allowed to occupy more than one segment.

A value of type size_t needs to represent the size of any single object. A pointer (say, of type void* or unsigned char* needs to represent the address of any object, or of any byte within it.

Don't make any unnecessary assumptions about the sizes of pointers or of size_t. Let the compiler figure it out for you.

The possibilities seem to be:

#include <stddef.h>
// would definitely work...but including a header in a header is ugly practice

No, it's not ugly practice. Just do this.

typedef long long unsigned size_t
// to account for win64 making `long` 32-bits...but it's possibly non-portable

This is absolutely non-portable. It's even possible that size_t could be wider than unsigned long long. But defining your own type that's wider than the compiler's size_t is neither useful nor necessary.

typedef char * size_t
// I like this one...but am not 100% sure about how portable it is

Absolutely not. size_t is required to be an unsigned integer type. If you've been told that integers and pointers are interchangeable, you've been badly misinformed.

Comments