2

I have

const uint8_t longByteTable[16][256][16] = { { { 0x00, ... } } };

declared as a three-dimensional 16x256x16 array of hardcoded octet values.

For optimisation purposes and various other reasons I need this array to be interpreted as a three-dimensional 16x256x2 array of uint64_t values:

const uint64_t reinterpretedTable[16][256][2];

What I need is a valid way to cast longByteTable to reinterpretedTable within strict ISO/ANSI C. Is this:

const uint64_t (*reinterpretedTable)[256][2] = 
    (const uint64_t(*)[256][2])longByteTable;

a proper way to do that?

P.S. I can't declare longByteTable with latter type because then it would not work properly with different endianness, and I would either need to declare different tables for diffent endianness, or perform some runtime checks and rotations. And yes, all further transformations of reinterpreted array are endianness-invariant.

aprelev
  • 390
  • 2
  • 11
  • 1
    "I can't declare `longByteTable` with latter type because then it would not work properly with different endianness" This sounds a lot like an X/Y problem. If you need to make `reinterpretedTable` to ensure some specific handling of endianness, you should be able to do it in a more portable way - say, with `hton`/`ntoh` functions. – Sergey Kalinichenko Apr 06 '16 at 10:53
  • No, I do not need the conversions and byte rotating. All transformations are endianness-independent, such as xor's and assignments. But xoring values of type `uint64_t` is in my case much faster than xoring 4 pairs of values of type `uint8_t` because it is performed with single instruction rather than 4 separate instructions. Yes, sometimes compiler sees this through and replaces bytes xor with full-register xors, but not all the time. – aprelev Apr 06 '16 at 11:02

1 Answers1

2

Because of the pointer aliasing rules of C, you cannot make such casts. The only safe way is to use a union:

typedef union
{
  uint8_t longByteTable[16][256][16]
  uint64_t reinterpretedTable[16][256][2];
} table_t;

const table_t table;

Though note that this will still make your code depend on endianess. The only way to make the code endianess-indepentent, is to assign values to/from larger integer types by using bit shifts.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thank you, this actually works. Shame I did not think of unions before. – aprelev Apr 06 '16 at 11:10
  • Finally solved with union of pointers: `union longTable_t { const uint8_t (*asBytes)[256][16]; const uint64_t (*asQWords)[256][2]; };` – aprelev Apr 08 '16 at 05:54
  • @ArsenyAprelev No, you should have a struct like above then declare a pointer to that struct. The problem with pointer aliasing is that a compiler may assume that a certain chunk of data might only get accessed through a pointer to that specific type. If it is referenced through some other type, the compiler is still allowed assume that there have been no changes to the data and therefore optimize the code accordingly. So a union of pointers solves nothing. [See this](http://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) for details. – Lundin Apr 08 '16 at 06:56
  • Okay, what about: `union qword_t { uint8_t asBytes[8]; uint64_t asQWord; };` and `const union qword_t longTable[16][256][2] = ...`? Technically, reading from `asQWord` when initialised as `asBytes` is undefined behavior? – aprelev Apr 13 '16 at 07:32
  • @ArsenyAprelev No that is fine, C allows "type punning" for unions, given that the variable representations match and that you mind any potential padding bytes. – Lundin Apr 13 '16 at 07:37