Passing pointer to uint16_t to subroutine that expects uint8_t[] array in C - how to?

Viewed 334

I have a subroutine which expects two arguments: uint8_t[] array buffer and uint16_t size:
void getData(uint8_t * pData, uint16_t size)
Instead of 2-byte array pointer I would like to pass a pointer to uint16_t variable so I do not have to convert array content to uint16_t variable later on. Here is what I tried:

uint16_t value;
uint8_t values[2];
// instead of:
getData(values, 2);
value = (values[0] << 8) | values[1];
// I want to simply call:
getData((uint8_t *)&value, 2);

The above approach does not yield the expected result. What is the correct way?
Please advise.

2 Answers
getData((uint8_t *)&value, 2);

The above approach does not yield the expected result.

The problem is that different computer systems use different order for multi-byte integers. Your first example interprets the data as most significant byte in the lowest address and least significant byte in the highest address. This is known as "big endian" byte order (big end first).

If your second attempt produced a different result, then we can deduce that your system uses the other order, "little endian". This is in fact, quite typical. For example, the x86 CPU architecture uses little endian byte order.

What is the correct way?

The correct and portable way to interpret a sequence of bytes as big endian integer is the one that you didn't want to do:

// instead of:
getData(values, 2);
value = (values[0] << 8) | values[1];

The way that you want to do is correct only for interpreting sequence of bytes as an integer in the "native" byte order. That's a different operation on systems where the native byte order isn't big endian. This operation behaves differently on systems of different endianness and is thus not portable.

Both versions of code are endian dependant. If you assume that it always would be working on little-endian machine, the original approach works. On big endian platform the second approach works because memory setup for a multi-byte value matches mathematical notation.

There is reason of simplicity why big-endian order is used to communicate over network and various hardware interfaces.

Many platforms include "endian.h" header which can contain macro definitions to detect whether your CPU or memory controller are big-endian, little-endian, or "middle-endian" (variable or unusual order).

FOr crossing line between Microsoft compilers whichhave that definition in "winsock.h" and POSIX-compatible compilers, I tend to use following snippet:

#ifdef _WIN32
#include <windows.h>   // or appropriate winsockX.h
#   ifndef __LITTLE_ENDIAN
#       define __LITTLE_ENDIAN 1234
#   endif
#   ifndef __BYTE_ORDER
#       define __BYTE_ORDER __LITTLE_ENDIAN
#   endif
#else
#include <netinet/in.h>
#include <endian.h>
#   if __BYTE_ORDER != __LITTLE_ENDIAN
#       if __BYTE_ORDER != __BIG_ENDIAN
#           define __UNKNOWN_ENDIAN
#       endif
#   endif
#endif

And then I would use __BYTE_ORDER for something like following, but likely wrapped up by some sort of function as "implementation details".

#if ((__BYTE_ORDER == __LITTLE_ENDIAN)) 
  getData(values, 2);
  value = (values[0] << 8) | values[1];

#else
  getData((uint8_t *)&value, 2);
#endif
Related