开发者

Union to unsigned long long int cast

I have a union as follows:

typedef unsigned long GT_U32;
typedef unsigned short GT_U16;
typedef unsigned char GT_U8;

typedef union
{
    GT_U8   c[8];
    GT_U16  s[4];
    GT_U32  l[2];
} GT_U64;

I want to cast this union into the following:

typedef unsigned long long int UINT64;

The casting function I wrote is as follows:

UINT64 gtu64_to_uint64_cast(GT_U64 number_u)
{
    UINT64 casted_number = 0;

    casted_number = number_u.l[0];
    casted_number = casted_number << 32;
    casted_number = casted_number | number_u.l[1];

    return casted_number;
}

This function is using the l member to perform the shifting and bitwise or. What will happen if the s or c members of the union are used to set its values?

I am not sure if this function will always cast the values correctly. I suspect it has something to do with the byte ordering of long and short. Can any body help?

Full example program is listed below.

#include <stdio.h>

typedef unsigned long GT_U32;
typedef unsigned short GT_U16;
typedef unsigned char GT_U8;

typedef union
{
    GT_U8   c[8];
    GT_U16  s[4];
    GT_U32  l[2];
} GT_U64;

typedef unsigned long long int UINT64;

UINT64 gtu64_to_uint64_cast(GT_U64 number_u)
{
    UINT64 casted_number = 0;

    casted_number = number_u.l[0];
    casted_number = casted_number << 32;
    casted_number = casted_number | number_u.l[1];

    return casted_number;
}

int main()
{
    UINT64 left;
    GT_U64 right;

    right.s[0] = 0x00;
    right.s[1] = 0开发者_运维知识库x00;
    right.s[2] = 0x00;
    right.s[3] = 0x01;
    left = gtu64_to_uint64_cast(right);

    printf ("%llu\n", left);
    return 0;
}


That's really ugly and implementation-dependent - just use memcpy, e.g.

UINT64 gtu64_to_uint64_cast(GT_U64 number_u)
{
    UINT64 casted_number;

    assert(sizeof(casted_number) == sizeof(number_u));

    memcpy(&casted_number, &number_u, sizeof(number_u));

    return casted_number;
}


First of all, please use the typedefs from "stdint.h" for such a purpose. You have plenty of assumptions of what the width of integer types would be, don't do that.

What will happen if the s or c members of the union are used to set its values?

Reading a member of a union that has been written to through another member may cause undefined behavior if there are padding bytes or padding bits. The only exception from that is unsigned char that may always be used to access the individual bytes. So access through c is fine. Access through s may (in very unlikely circumstances) cause undefined behavior.

And there is no such thing like a "correct" cast in your case. It simply depends on how you want to interpret an array of small numbers as one big number. One possible interpretation for that task is the one you gave.


This code should work independantly of padding, endianess, union accessing and implicit integer promotions.

uint64_t gtu64_to_uint64_cast (const GT_U64* number_u)
{
    uint64_t casted_number = 0;
    uint8_t  i;


    for(i=0; i<8; i++)
    {
      casted_number |= (uint64_t) number_u->c[i] << i*8U;
    }

    return casted_number;
}


If you can't change the declaration of the union to include an explicit 64-bit field, perhaps you can just wrap it? Like this:

UINT64 convert(const GT_U64 *value)
{
  union {
    GT_U64 in;
    UINT64 out;
  } tmp;

  tmp.in = *value;
  return tmp.out;
}

This does violate the rule that says you can only read from the union member last written to, so maybe it'll set your hair on fire. I think it will be quite safe though, don't see a case where a union like this would include padding but of course I could be wrong.

I mainly wanted to include this since just because you can't change the declaration of the "input" union doesn't mean you can't do almost the same thing by wrapping it.


Probably an easier way to cast is to use union with a long long member:

typedef unsigned long long int UINT64;
typedef unsigned long GT_U32;
typedef unsigned short GT_U16;
typedef unsigned char GT_U8;

typedef union
{
    GT_U8   c[8];
    GT_U16  s[4];
    GT_U32  l[2];
    UINT64 ll;
} GT_U64;

Then, simply accessing ll will get the 64-bit value without having to do an explicit cast. You will need to tell your compiler to use one-byte struct packing.


You don't specify what "cast the values correctly" means.

This code will cast in the simplest possible way, but it'll give different results depending on your systems endianness.

UINT64 gtu64_to_uint64_cast(GT_U64 number_u) {
  assert(sizeof(UINT64) == sizeof(GT_U64));
  return *(UINT64 *) &number_u;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜