Hesham Eraqi
Hesham Eraqi

Reputation: 2542

Pointers Casting Endianness

#include "stdio.h"

typedef struct CustomStruct
{
  short Element1[10];
}CustomStruct;

void F2(char* Y)
{
  *Y=0x00;
  Y++; 
  *Y=0x1F;    
}

void F1(CustomStruct* X)
{
  F2((char *)X);
  printf("s = %x\n", (*X).Element1[0]);
}

int main(void)
{
  CustomStruct s;
  F1(&s);

  return 0;
}

The above C code prints 0x1f00 when compiled and ran on my PC.

But when I flash it to an embedded target (uController) and debugging, I find that (*X).Element1[0] = 0x001f.

1- Why the results are different on PC and on the embedded target?

2- What can I modify in this code so that it prints 0x001f in the PC case, without changing the core of code (by adding a compiler option or something maybe).

Upvotes: 0

Views: 2032

Answers (3)

bames53
bames53

Reputation: 88215

shorts are typically two bytes and 16 bits. When you say:

short s;
((char*)&s)[0] = 0x00;
((char*)&s)[1] = 0x1f;

This sets the first of those two bytes to 0x00 and the second of those two bytes to 0x1f. The thing is that C++ doesn't specify what setting the first or second byte does to the value of the overall short, so different platforms can do different things. In particular, some platforms say that setting the first byte affects the 'most significant' bits of the short's 16 bits and setting the second byte affects the 'least significant' bits of the short's 16 bits. Other platforms say the opposite; That setting the first byte affect the least significant bits and setting the second byte affects the most significant bits. These two platform behaviors are referred to as big-endian and little-endian respectively.


The solution to getting consistent behavior independent of these differences is to not access the bytes of the short this way. Instead you should simply manipulate the value of the short using methods that the language does define, such as with bitwise and arithmetic operators.

short s;
s = (0x1f << 8) | (0x00 << 0); // set the most significant bits to 0x1f and the least significant bits to 0x00.

The problem is that, for many reasons, I can only change the body of the function F2. I can not change its prototype. Is there a way to find the sizeof Y before it have been castled or something?

You cannot determine the original type and size using only the char*. You have to know the correct type and size through some other means. If F2 is never called except with CustomStruct then you can simply cast the char* back to CustomStruct like this:

void F2(char* Y)
{
  CustomStruct *X = (CustomStruct*)Y;
  X->Element[0] = 0x1F00;
}

But remember, such casts are not safe in general; you should only cast a pointer back to what it was originally cast from.

Upvotes: 3

Joe Z
Joe Z

Reputation: 17956

It appears that the PC is little endian and the target is either big-endian, or has 16-bit char.

There isn't a great way to modify the C code on the PC, unless you replace your char * references with short * references, and perhaps use macros to abstract the differences between your microcontroller and your PC.

For example, you might make a macro PACK_BYTES(hi, lo) that packs two bytes into a short the same way, regardless of machine endian. Your example becomes:

#include "stdio.h"

#define PACK_BYTES(hi,lo) (((short)((hi) & 0xFF)) << 8 | (0xFF & (lo)))

typedef struct CustomStruct
{
  short Element1[10];
}CustomStruct;

void F2(short* Y)
{
    *Y = PACK_BYTES(0x00, 0x1F);
}

void F1(CustomStruct* X)
{
  F2(&(X->Element1[0]));
  printf("s = %x\n", (*X).Element1[0]);
}

int main(void)
{
  CustomStruct s;
  F1(&s);

  return 0;
}

Upvotes: 1

Kerrek SB
Kerrek SB

Reputation: 477464

The portable way is to change the definition of F2:

void F2(short * p)
{
    *p = 0x1F;
}

void F1(CustomStruct* X)
{
    F2(&X.Element1[0]);
    printf("s = %x\n", (*X).Element1[0]);
}

When you reinterpret an object as an array of chars, you expose the implementation details of the representation, which is inherently non-portable and... implementation-dependent.

If you need to do I/O, i.e. interface with a fixed, specified, external wire format, use functions like htons and ntohs to convert and leave the platform specifics to your library.

Upvotes: 2

Related Questions