2

I have recently become interested in switching from writing C99 code to writing plain ANSI C (C89), as the new features in the language are not worth the extreme portability and reliability of writing it in ANSI C. One of the biggest features I thought I would miss making the transition from C99 to C89 would be the stdint.h standard library file; or so I thought. According to this site, there is no stdint.h file in the C89 standard, which is also what I found on Wikipedia. I wanted to make sure that this was indeed the case, so I wrote a minimal test program that I expected would not compile when providing the flags -ansi and -pedantic-errors in both GCC and Clang;

#include <stdio.h>
#include <stdint.h>

int main(void)
{
    printf("The size of an int8_t is %ld.\n",  sizeof(int8_t));
    printf("The size of an int16_t is %ld.\n", sizeof(int16_t));
    printf("The size of an int32_t is %ld.\n", sizeof(int32_t));
    printf("The size of an int64_t is %ld.\n", sizeof(int64_t));

    printf("The size of a uint8_t is %ld.\n",  sizeof(uint8_t));
    printf("The size of a uint16_t is %ld.\n", sizeof(uint16_t));
    printf("The size of a uint32_t is %ld.\n", sizeof(uint32_t));
    printf("The size of a uint64_t is %ld.\n", sizeof(uint64_t));

    return 0;
}

However, what I found was not a compiler error, nor a warning, but a program that compiled! Since it worked just as well on either compiler, I'm under the assumption that this is not a bug in the compiler. The output, for reference, was what one would expect for a working C99 implementation:

The size of an int8_t is 1.
The size of an int16_t is 2.
The size of an int32_t is 4.
The size of an int64_t is 8.
The size of a uint8_t is 1.
The size of a uint16_t is 2.
The size of a uint32_t is 4.
The size of a uint64_t is 8.

I have a few questions about this "feature".

  • Should I be able to rely upon a stdint.h header being provided for a C89 program?
  • If not, what steps would I have to take in creating a header that functions the same as stdint.h?
  • How did programmers, in the time before C99, solve this problem of having reliable sizes for integers in their programs in a platform-agnostic manor?
carsonalh
  • 93
  • 3
  • 9
  • 4
    > "How did programmers, in the time before C99" - A maze of twisting little ifdefs, all different. :-) – Steve Friedl Jul 16 '20 at 14:36
  • 2
    C89 isn't obliged to *stop* you from using any header that happens to be present on your system, not `stdint.h` nor `unistd.h` nor `windows.h` nor anything else. But if you were to delete `stdint.h` from your system includes directory (not recommended), your compiler would still be C89-compliant but your code would fail. – Nate Eldredge Jul 16 '20 at 14:38
  • 5
    Don't program in C89. I used to hate C99 too at first, but it contains so many needed language bug fixes. No implicit int. No unreliable negative division of integers. No "struct hack". stdint.h. Boolean types. long long. // comments. Write variables with names longer than 16 symbols. And so on. This is very fundamental stuff which was plain broken in C89. – Lundin Jul 16 '20 at 14:40
  • 2
    All these bugs arguably makes C89 less portable, because there are so many bugs in plain sight that you have to dodge if you wish to support C89. Like... who doesn't like to use integer division, for example? – Lundin Jul 16 '20 at 14:41
  • You can find the C89 standard linked from [this question](https://stackoverflow.com/questions/17014835/where-can-i-find-the-c89-c90-standards-in-pdf-format). Search it and you'll find no reference to `stdint.h` nor any of the `intXX_t` types. That said, to find a real-life compiler that actually only supports C89 and lacks `stdint.h`, you may need to travel back in time. – Nate Eldredge Jul 16 '20 at 14:43
  • @NateEldredge Even modern compilers may chose to turn off stdint.h when compiling in some strict C89 setting. – Lundin Jul 16 '20 at 14:48
  • 1
    "extreme portability" is not a good thing when it means writing in a 30+ year old, extremely brittle language. Any compiler that genuinely only supports C89 probably is so outdated and has so many other problems that it belies your claimed "reliability" also... – underscore_d Jul 16 '20 at 14:57
  • 2
    carsonalh, Curious, why use `"%ld"` with a `sizeof` argument. _That_ is not portable. `printf("%lu.\n", (unsigned long) sizeof(uint8_t));` is better. – chux - Reinstate Monica Jul 16 '20 at 19:00
  • Maybe I'm wrong, but I think the standard permits an implementation to replace `#include ` with everything in stdio.h, except the contents of stdio.h might exist in memory during compilation rather than being stored in a file in the compiler's include search path. This situation could happen with stdint.h as well, but it's a nonstandard header in C89, so it would need to exist as a file in the include search path, else you'd see a compilation error. – MemReflect Jul 16 '20 at 22:25
  • @underscore_d, I am fairly new to C, and I found your comment really insightful, as at this point, I'm not really sure what I'm doing. The main reason I didn't want to use C99 was because of VLAs (and the new definition of undefined behaviour), but I suppose there's nothing in the standard that makes you use them, so I guess I'll just make sure that they never enter any of my code bases. – carsonalh Jul 17 '20 at 12:27
  • 1
    @MemReflect: I don't think the Standard would require that `#include` names in `<...>` markers ever have any relation to file names, or that implementations meaningfully process any names not given in the Standard. – supercat Jul 27 '20 at 22:49

2 Answers2

3

Should I be able to rely upon a stdint.h header being provided for a C89 program?

No. You said you picked C89 for portability reasons and then the first thing you reach for is non-portable extensions...

If not, what steps would I have to take in creating a header that functions the same as stdint.h?

How did programmers, in the time before C99, solve this problem of having reliable sizes for integers in their programs in a platform-agnostic manor?

With a forest of macros like for example in this answer. If C89, you typedef all the names present in stdint.h for the given platform. Otherwise, if standard C, you just include stdint.h.

So you'll need your own "notstdint.h" which contains all of this and then you have to port it to each system where integer sizes are different. And yes, this makes C89 less portable than standard C.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • How does this make C89 less portable? If one writes code which uses `stdint.h` when it is guaranteed to exist, checks for a predefined macro indicating whether to use an alternate header (which could be set via build script), and if neither condition applies one uses common integer definitions, then code will be usable with a wider variety of compilers than if one only supports `stdint.h`. – supercat Jul 26 '20 at 17:39
  • @supercat Because you will have to adjust "my custom stdint.h" to each target and there's no way to know the size of all the integer types in C without typing it in manually per target. Maybe some ugly way with macro trick arithmetic based on limits.h. – Lundin Jul 26 '20 at 19:04
  • For commonplace targets, there aren't very many possibilities. If each of `uint8_t`, `uint16_t`, `uint32_t`, and `uint64_t` maps to a standard type, `unsigned short` would have to be 16 bits, `unsigned long long` would have to be 64, and one could use INT_MAX or LNG_MAX to figure out which of those would be 32 bits. – supercat Jul 27 '20 at 04:55
3

In the days of C89 when writing code for microcomputers, one could simply use:

typedef unsigned char  uint8;
typedef signed char    int8;
typedef unsigned short uint16;
typedef signed short   int16;
typedef unsigned long  uint32;
typedef signed long    int32;

At the time, int might be either 16 or 32 bits, but all the other types would have the indicated sizes on commonplace implementations for systems that could handle them (including all microcomputers). Compilers for 16-bit systems would allow a pointer that was freshly cast from int* to short* to access objects of type int, so there was no need to worry about whether int16_t should be short or int. Likewise those for 32-bit systems would allow pointer that was freshly cast from int* to long* to access objects of type int, so it wasn't necessary to worry about whether int32_t should be int or long.

supercat
  • 77,689
  • 9
  • 166
  • 211