Search code examples
c++cdesign-patternscompatibility32bit-64bit

Best practice for compatibility with 32-bit systems for softwares using version dependent data files?


In the easy case, when the program is independent of any external data-base, one could write something of the form:

#include <stdint.h>
#if UINTPTR_MAX == 0xffffffff
/* 32-bit */
typedef some_type_for_32_bit_version Type;

#elif UINTPTR_MAX == 0xffffffffffffffff
/* 64-bit */
typedef some_type_for_64_bit_version Type;
#endif

And continue to work with the desired type.

Now suppose the program reads some meta/data file at the beginning, some bit represents if the file is built for 32-bit or 64-bit (files built for 32-bit should work on 64-bit as well, files built for 64-bit should only work on 64-bit). The program may operate pretty much the same for both cases, but has subtle differences, e.g. some variable is uint32_t for 32-bit and uint64_t for 64-bit.

A bad solution would be to start by reading that bit, have different version of that variable and any struct/union/function that use it, and have a ton of unnecessary if statements.

I thought of having some loader program that reads that byte, writes to some #define in another file, runs a compiler and finally runs the generated program - but that seems too nasty and I'm not thrilled by the idea of having to suffer compilation time at every run.

Is there any general design for that? Something specific to c? c++?


Solution

  • There are two things you need:

    • You only want to write one parser that can be used in both configurations depending on a flag in a file. The concept that helps here is templates.

      In C++, you can use templates to make a function or class work for multiple types. There is no real nice mechanism for this in C, so there it is even more important to keep the dynamic part as small as possible to avoid code duplication. A simple emulation of a template would be to define all the code as macro with the type as argument and instantiate it twice for the two types.

    • Dynamic choice of the variants at runtime. A concept that can make more complex setups more managable here is dynamic dispatch. There you would create a generic interface and instantiate either the 32 or 64 bit variant depdending on the type.

      In C++, this would be an interface class with virtual methods. In C, this can be easily replicated as a struct containing function pointers.

    I assume in this case it would be sufficient to store these values in the native pointer sized integer for both the 32bit and 64bit version of your program, but if they need to retain their original file size, more code of your program needs to be dynamic.

    Based on your description I'd suggest something simple like the following though:

    // When reading the file:
    bool flagIs64bit = readThatFlag();
    if (currentArchIs32bit() && flagIs64bit) {
      return NotSupported;
    }
    
    // At the core of the parser:
    uintptr_t readType(void *p) {
      if (flagIs64bit) {
        return (uintptr_t)*(uint64_t*)p;
      } else {
        return (uintptr_t)*(uint32_t*)p;
      }
    }
    

    (Comment on the design: Binary files should generally be independent of the architecture that they are used on. That way they are compatible to as many systems as possible. That means only explicit width types should be used (u)intNN_t and their endianess might need to be converted.)