7

Some background:

the header stdint.h is part of the C standard since C99. It includes typedefs that are ensured to be 8, 16, 32, and 64-bit long integers, both signed and unsigned. This header is not part of the C89 standard, though, and I haven't yet found any straightforward way to ensure that my datatypes have a known length.

Getting to the actual topic

The following code is how SQLite (written in C89) defines 64-bit integers, but I don't find it convincing. That is, I don't think it's going to work everywhere. Worst of all, it could fail silently:

/*
** CAPI3REF: 64-Bit Integer Types
** KEYWORDS: sqlite_int64 sqlite_uint64
**
** Because there is no cross-platform way to specify 64-bit integer types
** SQLite includes typedefs for 64-bit signed and unsigned integers.
*/
#ifdef SQLITE_INT64_TYPE
  typedef SQLITE_INT64_TYPE sqlite_int64;
  typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
#elif defined(_MSC_VER) || defined(__BORLANDC__)
  typedef __int64 sqlite_int64;
  typedef unsigned __int64 sqlite_uint64;
#else
  typedef long long int sqlite_int64;
  typedef unsigned long long int sqlite_uint64;
#endif
typedef sqlite_int64 sqlite3_int64;
typedef sqlite_uint64 sqlite3_uint64;

So, this is what I've been doing so far:

  • Checking that the "char" data type is 8 bits long, since it's not guaranteed to be. If the preprocessor variable "CHAR_BIT" is not equal to 8, compilation fails
  • Now that "char" is guaranteed to be 8 bits long, I create a struct containing an array of several unsigned chars, which correspond to several bytes in the integer.
  • I write "operator" functions for my datatypes. Addition, multiplication, division, modulo, conversion from/to string, etc.

I have abstracted this process in a header file, which is the best I can do with what I know, but I wonder if there is a more straightforward way to achieve this.

I'm asking because I want to write a portable C library.

Community
  • 1
  • 1
Lehonti
  • 133
  • 8
  • 4
    One solution might be to use a compile-time assertion to verify that the data sizes your compiler does give you are what you expect. Example of this is here: http://www.pixelbeat.org/programming/gcc/static_assert.html – David Oct 06 '16 at 19:47
  • 4
    If you want modern C features, write modern C code, not ancient C. – too honest for this site Oct 06 '16 at 19:53
  • Basic integer types is usually part of run time support library, like glibc. Those libraries are never designed to be very portable. The effort to make them portable is usually implement the same thing in different ways for different target if necessary. – user3528438 Oct 07 '16 at 14:51
  • @Olaf, then it wouldn't be as portable – Lehonti Nov 29 '17 at 17:40
  • @Lehonti Sure, if traffic on highways is too irritating for horse-carriages, disallow modern cars instead of moving forward and release the poor horses to freedom. I wonder why you then accepted an answer basically stating the same. _shakehead_ – too honest for this site Nov 29 '17 at 18:30
  • @Olaf, I get your point. One should strive to use modern tools when possible. In an ideal world, all platforms would support modern standards. – Lehonti Nov 29 '17 at 18:55
  • @Lehonti: There is no "modern" or "nto modern" C standard, but only **one** C standard. You might want to understand this first. – too honest for this site Nov 29 '17 at 19:34
  • @Olaf, care to explain a little? – Lehonti Nov 30 '17 at 20:31
  • What is unclear about "only one C standard"? Reading the foreword might be a good idea. – too honest for this site Nov 30 '17 at 20:58
  • @Olaf, I used to think of C89, C99, and C11 as several different C standards, some more recent than others. However, you say there is only one C standard. That's what's unclear. Care to explain a little? – Lehonti Nov 30 '17 at 21:42
  • I don't see how I can be more clear. Read the **current version** of the standard. – too honest for this site Nov 30 '17 at 22:32

2 Answers2

13

First, you should ask yourself whether you really need to support implementations that don't provide <stdint.h>. It was standardized in 1999, and even many pre-C99 implementations are likely to provide it as an extension.

Assuming you really need this, Doug Gwyn, a member of the ISO C standard committee, created an implementation of several of the new headers for C9x (as C99 was then known), compatible with C89/C90. The headers are in the public domain and should be reasonably portable.

http://www.lysator.liu.se/(nobg)/c/q8/index.html

(As I understand it, the name "q8" has no particular meaning; he just chose it as a reasonably short and unique search term.)

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 1
    The nature of an outdated standard is that there exist *somewhere* outdated platforms that are compatible with only said standard. A developer should not have to ask himself why he wants to maximize portability. –  Mar 16 '17 at 21:24
  • 3
    @jrodatus: A developer should ask himself the reason for *any* design decision. For example, C introduced prototypes in 1989; I'm not going to write code that uses old-style function declarations unless I have a specific need to do so. The same applies to ``; the only real difference is that it was introduced 18 years ago rather than 28. Of course if you do need that kind of portability, you should do whatever it takes to ensure that you have it. – Keith Thompson Mar 16 '17 at 23:07
  • 1
    Apparently there's a reason behind the Q8 name... From the q8/Q8defs.h library header: " Aside: Why "Q8"? That was used as a system external symbol prefix in old CDC Fortran implementations, to avoid link-time name-space collisions with user-defined symbols, on the assumption that no user would ever think of using such a name." – Giuppox Aug 03 '22 at 23:20
2

One rather nasty quirk of integer types in C stems from the fact that many "modern" implementations will have, for at least one size of integer, two incompatible signed types of that size with the same bit representation and likewise two incompatible unsigned types. Most typically the types will be 32-bit "int" and "long", or 64-bit "long" and "long long". The "fixed-sized" types will typically alias to one of the standard types, though implementations are not consistent about which one.

Although compilers used to assume that accesses to one type of a given size might affect objects of the other, the authors of the Standard didn't mandate that they do so (probably because there would have been no point ordering people to do things they would do anyway and they couldn't imagine any sane compiler writer doing otherwise; once compilers started doing so, it was politically difficult to revoke that "permission"). Consequently, if one has a library which stores data in a 32-bit "int" and another which reads data from a 32-bit "long", the only way to be assured of correct behavior is to either disable aliasing analysis altogether (probably the sanest choice while using gcc) or else add gratuitous copy operations (being careful that gcc doesn't optimize them out and then use their absence as an excuse to break code--something it sometimes does as of 6.2).

supercat
  • 77,689
  • 9
  • 166
  • 211