8
\$\begingroup\$

I defined two variables:

uint8_t a[2];
uint16_t b;

Next I want to use a as variable of type uint16_t, e. g.

b = (uint16_t)a;

But this is wrong! My programs doesn't works correctly with such code. All is OK when I replace b to uint8_t b[2] and use elementwise operations.

Why?

\$\endgroup\$
  • 5
    \$\begingroup\$ Why don't you throw some values into your example and tell us what your expectation of "correct" is so that we can actually help without speculating about your semantic intent. \$\endgroup\$ – vicatcu Sep 18 '12 at 17:26
  • 1
    \$\begingroup\$ This would be a much better fit for Stack Overflow. \$\endgroup\$ – sharptooth Sep 19 '12 at 7:28
16
\$\begingroup\$

a is a pointer to an array of bytes. If you cast it to a uint16_t and assign it to b, then b will contain the address of the base of the array (where it is stored) in SRAM. If you want to treat the two bytes of the array a as an integer, then use a union as suggested by user14284, but be aware that the union will represent the byte array in the memory byte ordering of the architecture (in AVR that would be little-endian, which means byte 0 is the least significant byte). The way to write that in code is:

union{
  uint8_t a[2];
  uint16_t b;
} x;

x.b[0] = 0x35;
x.b[1] = 0x4A;

// by virtue of the above two assignments
x.a == 0x4A35 // is true

Another way to do this without using a union is to cast a to a uint16_t pointer and then dereference it like so:

uint8_t a[2] = {0x35, 0x4A};
uint16_t b = *((uint16_t *) a);
b == 0x4A35; // because AVR is little endian

If you are using the buffer to store big endian data (e.g. network byte order), then you'll need to byte-swap to use either of these techniques. A way to do that without any branches or temporary variables is:

uint8_t a[2] = {0x35, 0x4A};
a[0] ^= a[1];
a[1] ^= a[0];
a[0] ^= a[1];

a[0] == 0x4A; // true
a[1] == 0x35; // true

Incidentally this is not an AVR or even an embedded-only problem. Application level networking code written for PCs typically calls functions called htonl, htons (host to network, 32- and 16-bit variants) and ntohl, ntohs (network to host, 32- and 16-bit variants) whose implementations are target architecture dependent as to whether they swap the bytes or not (under the assumption that bytes as transmitted 'on the wire' are always big-endian when they are part of multi-byte words).

\$\endgroup\$
  • \$\begingroup\$ This is a great answer. The key being 'a' on its own is a pointer. \$\endgroup\$ – Jon L Sep 18 '12 at 18:23
  • 2
    \$\begingroup\$ "Another way to do this without using a union is to cast a to a uint16_t pointer and then dereference it" -- Actually, this type of casting often breaks strict aliasing rules. You shouldn't do it unless you're compiling with -fno-strict-aliasing. \$\endgroup\$ – Jim Paris Sep 18 '12 at 18:24
3
\$\begingroup\$

If your intent is to concatenate the two 8-bit variables into a 16-bit variable, use a union. If you want to cast a single member of a into b, then specify which element of the array you want to use.

\$\endgroup\$
2
\$\begingroup\$

On your code you are casting only the pointer to the array.

You need to cast the value pointed by a.

b = (uint16_t)*a;

I never used AVR but if you are working with a 16 bit architecture you have to make sure that a is word aligned. Failing to do this may result in an exception.

\$\endgroup\$
  • 1
    \$\begingroup\$ ... this is absolutely not his intent... he wants b to be related to both elements of a (this would totally discregard a[1]), also there are no exceptions or restrictions on what you can cast in avr-gcc \$\endgroup\$ – vicatcu Sep 18 '12 at 17:28
0
\$\begingroup\$

Each member of a is an 8-bit number. It can't hold anything larger. Casting it to 16-bits does not do anything to a. It merely extracts whatever value a might be able to hold, and converts it to 16 bits so that it matches up with the format of b when the value is stored there.

You didn't even refer to a member of a. You must use a[0] or a[1] (and not a[2]!). If you use a by itself, you just get the address of it. (Good catch, Bruno).

Declaring a to be an array of two 8-bit numbers doesn't make it a 16-bit number, either. You can do some things programmatically to store and retrieve 16-bit values using sequences of 8-bit, but not the way you were thinking.

\$\endgroup\$
0
\$\begingroup\$

If you want to convert the bytes in a into a 16 bit value, and the representation is little-endian (the lower 8 bits of the value come in the first byte), do

uint16_t b = a[0] | (a[1] << 8);

For a big-endian representation do

uint16_t b = (a[0] << 8) | a[1];

Avoid using pointer casts or unions to do this, as that leads to portability problems.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy