Integers need to be converted to a byte array of defined endianness to be reliably and consistently saved and transmitted, and converted back to be accurately received and read. The goal is to be as portable as possible, while maintaining concise and fast code.

#ifndef LE_h
#define LE_h
#include <stdint.h>
inline static uint16_t le16(const uint8_t b[const static 2]) {
    return b[0]|(uint16_t)b[1]<<8;
inline static uint32_t le32(const uint8_t b[const static 4]) {
    return le16(b)|(uint32_t)le16(b+2)<<16;
inline static uint64_t le64(const uint8_t b[const static 8]) {
    return le32(b)|(uint64_t)le32(b+4)<<32;
inline static void le16b(uint8_t b[const static 2], const uint16_t n) {
    b[0] = n;
    b[1] = n>>8;
inline static void le32b(uint8_t b[const static 4], const uint32_t n) {
    le16b(b, n);
    le16b(b+2, n>>16);
inline static void le64b(uint8_t b[const static 8], const uint64_t n) {
    le32b(b, n);
    le32b(b+4, n>>32);
#endif /* LE_h */
  • 1
    \$\begingroup\$ Are you aware of htons and friends ? \$\endgroup\$
    – Reinderien
    Sep 27, 2022 at 22:25
  • \$\begingroup\$ @Reinderien, this code is converting to little-endian format, whereas the htons family convert to/from big-endian. \$\endgroup\$ Sep 28, 2022 at 8:23
  • \$\begingroup\$ Or perhaps you were saying that's a good model to follow for the interface here (including the names)? I agree with you there. \$\endgroup\$ Sep 28, 2022 at 10:59
  • \$\begingroup\$ @TobySpeight I should have been more specific, but when I said family I meant to include inverses of those functions, i.e. ntohs. \$\endgroup\$
    – Reinderien
    Sep 28, 2022 at 23:40
  • \$\begingroup\$ Yes, that's why I wrote to/from - either way, those conversions are big-endian, and this is for little-endian. (E.g. on a big-endian platform, the htons family perform no conversion - the opposite of what's wanted here). \$\endgroup\$ Jan 23, 2023 at 21:18

1 Answer 1


The goal is to be as portable as possible, while maintaining concise and fast code.

Why little?

"to a byte array of defined endianness", is a good goal. I think little is more correct, yet big endian tends to be, though not universally, the preferred endian. I'd offer users both LE and BE routines.

Signed types?

Endian issues apply to signed types too. I'd expect le16signed() and friends.

Note: Floating point has endian issues and a whole lot more encoding ones too.

Standard integer types?

For intercommunication, fixed width types are preferred, yet such a LE package would be nice if extended to include LE routine for short, int, ..., intmax_t.

(u)intN_t types are optional

A compliant compiler only needs to implement them if the corresponding type is available without padding. Consider the case of CHAR_BIT == 9, or CHAR_BIT == 16.

To port to such rare machines, the interface needs re-design. Perhaps use (u)int_leastN_t types.

Or simply add a #if CHAR_BIT != 8 #error ....

Older compilers

b[const static 2] is not valid on older compilers (of course then maybe not inline). On such, could use *b instead and/or no inline.

At least consider gracefully detecting the required version and #error when not met.

No UB seen


Pedantic warnings

Some compilers may warn about type narrowing:

b[0] = n;
// alternative
b[0] = (uint8_t) n;

Unneeded const

Style issue: the 2nd const is not needed (and IMO distracting). Unsure about the usefulness of the first const.

//                                                  vvvvv
inline static void le16b(uint8_t b[const static 2], const uint16_t n) {

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.