1

In standard C, integer types may have identical size, alignment, and representation, but still be different types with different conversion rank. For example, this applies to unsigned long and unsigned long long on x86_64-linux. It’s possible to distinguish such types using the _Generic feature of C11, even through a typedef:

#if 0 /* valid shell script */
${CC-cc} -std=c11 -Wall -o "${0%.c}" "$0" && exec "${0%.c}"
#endif

#include <assert.h>
#include <limits.h>
#include <stddef.h>

#if !defined __linux__ || !defined __LP64__
#error wrong platform
#endif

int main(void) {
    size_t n;
    assert((size_t)-1 == ULONG_MAX && (size_t)-1 == ULLONG_MAX);
    assert(sizeof(size_t) == sizeof(unsigned long) &&
           sizeof(size_t) == sizeof(unsigned long long));
    assert(_Alignof(size_t) == _Alignof(unsigned long) &&
           _Alignof(size_t) == _Alignof(unsigned long long));
    assert(_Generic(n,
                    unsigned long long: !"should not be chosen",
                    unsigned long: "should be chosen"));
    return 0;
}

It’s also possible to make a program fail to compile if the types are different, as you’d want in a configure script:

#include <stddef.h>
int main(void) { (void)sizeof((size_t *)0 - (unsigned long long *)0); return 0; }

Can you detect the difference before C11 without having the compilation fail and without resorting to compiler-dependent features such as GNU cpp’s __SIZE_TYPE__?

Alex Shpilkin
  • 776
  • 7
  • 17
  • https://stackoverflow.com/q/3385515/10622916 shows replacement implementations for `static_assert` or `_Static_assert` that can be used with older C standards. – Bodo Apr 12 '23 at 13:59
  • 1
    If `size_t` is one of `unsigned long int` or `unsigned long long int` and those are the same size etc., then there would be no reason for the implementation not to make it the same as `unsigned long int`. There is a "recommended practice" section in the C standard regarding the `size_t` and `ptrdiff_t` types: "The types used for `size_t` and `ptrdiff_t` should not have an integer conversion rank greater than that of `signed long int` unless the implementation supports objects large enough to make this necessary." – Ian Abbott Apr 12 '23 at 14:15
  • Are you looking for configurable type-dependent behavior for pre-C11 compiler? – tstanisl Apr 12 '23 at 14:15
  • 1
    The pre-C11 stance was "don't use these naive, crappy types to begin with, but stick to stdint.h types and ignore what underlying, non-portable type they boil down to". And that still applies post-C11. Why would you care if `uint32_t` happens to have the same conversion rank as something else, if you only ever use `uint32_t` when you need 4 bytes? – Lundin Apr 12 '23 at 14:20
  • @tstanisl I’m figuring out how to make a C interpreter borrow the host compiler’s idea of what type it should return from `sizeof` and pointer subtraction (and if it matters which one with the same range I choose in a world with no `_Generic` or `__auto_type`). – Alex Shpilkin Apr 12 '23 at 16:03
  • @IanAbbott True. However, in an ILP32 world there is the [precedent](https://stackoverflow.com/questions/48606439) of Darwin, where `ptrdiff_t` is `signed int` (32-bit), but `size_t` is `unsigned long` (also 32-bit but angrier). That’s a similar perversion even if it doesn’t technically violate the standard’s recommendation. – Alex Shpilkin Apr 12 '23 at 16:15

1 Answers1

1

Can you detect the difference before C11 without having the compilation fail and without resorting to compiler-dependent features such as GNU cpp’s __SIZE_TYPE__?

No.

Prior to C11, standard C had no introspection of any kind. _Generic was the first standard feature that provided for conditioning compilation or execution behavior on the type of an expression.

And even _Generic has limited usefulness, because the type of every expression is known at compile time. The main use case is when you re-use the same source code in multiple distinct contexts (that is, via a macro). You can indeed use it (in C11 and later) to test certain [static] assertions about types, but usually it is better to write the code so that it does not depend on assumptions about types that are not directly supported by the language spec.

Prior to C11, you could test various properties of C data types, such as storage size, signedness, and number of value bits, and maybe even alignment requirement, but as you observe in the question, C allows for distinct types to have all these properties the same.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157