Calculating the bytes that represent an int32_t
can be a slight nuisance due to the C standard not fully defining shifts with negative values. However, since the exact-width signed integer types are defined to use two’s complement, their representations are the same as for the corresponding unsigned integer type obtained by converting the signed type to the unsigned type. So we can convert to uint32_t
and shift to obtain the desired bytes, as the following code demonstrates.
#include <stdlib.h>
#include <limits.h>
/* Convert a big-endian object to a little-endian object or vice-versa,
in place.
*/
void ReverseEndian(void *p, size_t n)
{
// Convert to "unsigned char *" for convenience.
unsigned char *pu = p;
/* Swap bytes at the front of the object with reflected bytes from the
back.
*/
for (size_t i = 0; i < n/2; ++i)
{
unsigned char t = pu[i];
pu[i] = pu[n-1-i];
pu[n-i-1] = t;
}
}
// Store an int32_t as big-endian.
void StoreBigEndian(void *p, int32_t x)
{
// Convert to "unsigned char *" for convenience.
unsigned char *pu = p;
/* Convert to uint32_t. Since int32_t is defined to use two’s complement,
uint32_t is represented with the sign bytes, and it avoids the sign
issues.
*/
uint32_t u = x;
/* Shift each byte, starting with the least significant, to move it to
the low byte, then store those bytes in big-endian order, starting
with the highest addressed.
*/
for (size_t i = 0; i < sizeof u; ++i)
pu[sizeof u - 1 - i] = u >> i*CHAR_BIT;
}
// Store an int32_t as little-endian.
void StoreLittleEndian(void *p, int32_t x)
{
// Convert to "unsigned char *" for convenience.
unsigned char *pu = p;
/* Convert to uint32_t. Since int32_t is defined to use two’s complement,
uint32_t is represented with the sign bytes, and it avoids the sign
issues.
*/
uint32_t u = x;
/* Shift each byte, starting with the least significant, to move it to
the low byte, then store those bytes in big-endian order, starting
with the lowest addressed.
*/
for (size_t i = 0; i < sizeof u; ++i)
pu[i] = u >> i*CHAR_BIT;
}
#include <stdio.h>
// Print n bytes starting at address p.
static void PrintBytes(const void *p, size_t n)
{
// Convert to "unsigned char *" for convenience.
const unsigned char *pu = p;
printf("Hexadecimal");
for (size_t i = 0; i < n; ++i)
printf(" %0*x", (CHAR_BIT+3)/4, pu[i]);
}
#include <string.h>
int main(void)
{
int32_t i = 0x12345678;
// Buffers for big-endian and little-endian representations.
unsigned char BE[sizeof i];
unsigned char LE[sizeof i];
// Store the value as big endian and display it.
StoreBigEndian(BE, i);
printf("Big Endian: "); PrintBytes(BE, sizeof BE); printf(".\n");
// Store the value as little endian and display it.
StoreLittleEndian(LE, i);
printf("Little Endian: "); PrintBytes(LE, sizeof LE); printf(".\n");
// Copy the bytes into new int32_t objects to reinterpret them.
int32_t ibe, ile;
memcpy(&ibe, BE, sizeof ibe);
memcpy(&ile, LE, sizeof ile);
if (ibe == i)
printf("This system appears to be big endian.\n");
else if (ile == i)
printf("This system appears to be little endian.\n");
else
printf("This system appears to be neither big endian nor little endian.\n");
// Reverse the endianness and test for problems.
ReverseEndian(BE, sizeof BE);
if (memcmp(BE, LE, sizeof BE))
{
fprintf(stderr, "Error, big-endian bytes are inconsistent with little-endian bytes.\n");
exit(EXIT_FAILURE);
}
}