7
\$\begingroup\$

I see that on Linux there are various built-in functions to convert endianness for specific integer width types, and they are probably faster than anything one can write and compile on their own. But I want to write a size_t (under Linux in C), which does not have a specified width, to big-endian array of bytes, as fast as possible.

Is this the fastest way?

#include <limits.h>

// write "from" to array of bytes "to" in big-endian fashion
void big_endian_size(unsigned char to[], size_t from) {

    unsigned char *p = to + (sizeof(size_t) - 1);

    while (1) {
        *p = from;

        if (to == p)
            break;

        p--;
        from >>= CHAR_BIT;
    }
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

If you're looking for performance then you should, at least, provide a specialized version for the most common cases:

void big_endian_size(uint8_t to[], size_t from) {
#if UINT32_MAX == SIZE_MAX
    // Specialization for 4 bytes size_t
#elif UINT64_MAX == SIZE_MAX
    // Same as above but for 8 bytes size_t
#else
    // Your current implementation...
#endif
}

Specialized versions may simply reuse this:

to[3] = from & 0x000000FF);
to[2] = (from & 0x0000FF00) >> 8;
// And so on...

Note that you may also simply return another size_t (instead of an array) with its bytes rotated. This will be considerably faster (also if implemented with a plain for to go through all bytes).

Also note that if you extract the 32 bit swap code into a separate function then you can use it both for 32 bit and 64 bit sizes (for example see byteswap.h for an implementation).

Do you need more? Assembly. For example on X86/X64 assembly you have BSWAP that does it in one instruction (I remember also ARM has but I don't remember the mnemonic), just check for target architecture to provide more and specialized versions.

Notes: Why &? Because truncation (let's imagine uint8_t val = 0xffffffff >> 8) is platform specific.

\$\endgroup\$
13
  • \$\begingroup\$ Well does not exactly work the way you wrote, for 8 bytes, you would want to compare SIZE_MAX with UINT8_MAX, not ULONG_MAX. But thank you for this idea of comparing SIZE_MAX, I did not know it before. And also, for using the preprocessor, as doing this at runtime would of course not work, due to overflow. \$\endgroup\$
    – user137993
    Commented May 4, 2017 at 11:11
  • \$\begingroup\$ Oh, and I don't see why you need the &'s. If you are sure you need it , please tell me why. Second, I don't understand why you cast to a signed type. Again, if you are sure, tell me why. \$\endgroup\$
    – user137993
    Commented May 4, 2017 at 11:14
  • 2
    \$\begingroup\$ SIZE_MAX is the maximum VALUE you can store in size_t, what I'm doing there is checking if size_t is 4 bytes (it matches uint32_tmaximum value) or 8 bytes. If you compare against UINT8_MAX then...you will never execute that code. \$\endgroup\$ Commented May 4, 2017 at 11:20
  • 2
    \$\begingroup\$ Casting can be omitted but compiler may/should emit a warning. I'm also using & to mask the value to the single byte I want to extract. It might be unnecessary (because shifted value will be truncated) but then you're in the realm of implementation-defined code (conversion from bigger value to a smaller one) which should be avoided if possible. \$\endgroup\$ Commented May 4, 2017 at 11:23
  • 1
    \$\begingroup\$ (uint8_t)(0xffff >> 8) is neither undefined nor implementation defined. (int)from on the other hand is implementation defined and unnecessary to boot. \$\endgroup\$
    – Voo
    Commented May 4, 2017 at 16:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.