2

I wonder what's the recommended way to convert integer to/from little-endian in a portable way.

Is there any library for that?

We can use htonl and ntohl and then do another big-endian to (from) little-endian conversion, but it's not efficient.

Jens
  • 8,423
  • 9
  • 58
  • 78
Nan Hua
  • 3,414
  • 3
  • 17
  • 24
  • What are you trying to achieve? Unless you do some funky casting and dereferencing of pointers to integers of various sizes, you need not worry about endianess. See also this question on ["htonl() vs __builtin_bswap32()"](http://stackoverflow.com/questions/21527957/htonl-vs-builtin-bswap32) which talks about alternative ways to byte-swap numbers. – Jens Apr 14 '16 at 23:31
  • 2
    If you have to fiddle with endianess, you're already treading in the realms of "not portable". So isolate this in such a way that the rest of your code works with suitable abstractions, and ***small*** platform specific implementations are implemented for specific platforms. – Disillusioned Apr 14 '16 at 23:31

2 Answers2

1

The portable way is to use bit shifts and masks into an appropriately sized string. Notice I say string, because this is really the only time you need to concern yourself with endianness -- when transferring bytes between systems.

If you want to avoid unnecessary conversions (e.g. converting to little-endian on a little-endian architecture), there is no completely portable way to do it at compile-time. But you can check at runtime to dynamically select the set of conversion functions.

This does have the disadvantage where the code can't be inlined. It might be more efficient to write the conversions in the portable way and use templates or inlining. Combined with semi-portable compile-time checks, that's about as good as you'll get.

Further reading: Detecting Endianness at compile-time.

Community
  • 1
  • 1
paddy
  • 60,864
  • 6
  • 61
  • 103
  • the solutions in that link do not actually work/are not portable. It turns out that it's illegal to cast an integer to a sequence of bytes in constexpr context, whether by explicit cast or union. – Richard Hodges Apr 15 '16 at 00:19
1

This is a great question. It prompted me to see if there was any way to determine endianness at compile time using constexpr expression.

It turns out that without preprocessor tricks it's not possible, because there's no way to turn an integer into a sequence of bytes (via casts or unions) when evaluating in a constexpr context.

However it turns out that in gcc, a simple run-time check gets optimised away when compiled with -O2, so this is actually optimally efficient:

#include <cstdint>
#include <iostream>

constexpr bool is_little_endian()
{
    union endian_tester {
        std::uint16_t   n;
        std::uint8_t    p[4];
    };

    constexpr endian_tester sample = {0x0102}; 
    return sample.p[0] == 0x2;
}

template<class Int>
Int to_little_endian(Int in)
{
  if (is_little_endian()) {
    return in;
  }
  else {
    Int out = 0;
    std::uint8_t* p = reinterpret_cast<std::uint8_t*>(std::addressof(out));
    for (std::size_t byte = 0 ; byte < sizeof(in) ; ++byte)
    {
      auto part = (in >> (byte * 8)) & 0xff;
      *p++ = std::uint8_t(part);
    }

    return out;
  }
}

int main()
{
    auto x = to_little_endian(10); 
    std::cout << x << std::endl;
}

here's the assembler output when compiling on an intel (little-endian) platform:

main:
        subq    $8, %rsp
#
# here is the call to to_little_endian()
#

        movl    $10, %esi
#
# that was it - it's been completely optimised away
#

        movl    std::cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        movq    %rax, %rdi
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xorl    %eax, %eax
        addq    $8, %rsp
        ret
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142