3
\$\begingroup\$

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 */ 
\$\endgroup\$
5
  • 1
    \$\begingroup\$Are you aware of htons and friends ?\$\endgroup\$CommentedSep 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\$CommentedSep 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\$CommentedSep 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\$CommentedSep 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\$CommentedJan 23, 2023 at 21:18

1 Answer 1

4
\$\begingroup\$

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

Good.

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) { 
\$\endgroup\$

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.