¿Qué tiene de malo este casting en código C para AVR?

8

Definí dos variables:

uint8_t a[2];
uint16_t b;

A continuación, quiero usar acomo variable de tipo uint16_t, por ejemplo

b = (uint16_t)a;

Pero esto está mal! Mis programas no funcionan correctamente con dicho código. Todo está bien, cuando sustituyo ba uint8_t b[2]las operaciones elementwise uso.

¿Por qué?


fuente
55
¿Por qué no arroja algunos valores en su ejemplo y nos dice cuál es su expectativa de "correcto" para que podamos ayudarlo sin especular sobre su intención semántica?
vicatcu
1
Esto sería mucho mejor para Stack Overflow.
Sharptooth

Respuestas:

16

aes un puntero a una matriz de bytes. Si lo convierte a uint16_t y se lo asigna b, bcontendrá la dirección de la base de la matriz (donde está almacenada) en SRAM. Si desea tratar los dos bytes de la matriz acomo un número entero, use una unión como lo sugiere el usuario 14284, pero tenga en cuenta que la unión representará la matriz de bytes en la ordenación de bytes de memoria de la arquitectura (en AVR eso sería poco -endian, que significa que el byte 0 es el byte menos significativo). La forma de escribir eso en código es:

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

Otra forma de hacer esto sin usar una unión es lanzar aa un puntero uint16_t y luego desreferenciarlo así:

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

Si está utilizando el búfer para almacenar datos de Big Endian (por ejemplo, el orden de bytes de la red), entonces deberá intercambiar bytes para utilizar cualquiera de estas técnicas. Una forma de hacerlo sin ramificaciones o variables temporales es:

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

Por cierto, este no es un AVR o incluso un problema solo incrustado. Nivel de aplicación de red de código escrito para PC normalmente llamadas de llamadas funciones htonl, htons(sede de la red, de 32 y 16 bits variantes) y ntohl, ntohs(red de acogida, de 32 y 16 bits variantes), cuyo objetivo son las implementaciones de la arquitectura depende de si se intercambie los bytes o no (bajo el supuesto de que los bytes transmitidos 'en el cable' siempre son big-endian cuando forman parte de palabras de varios bytes).

vicatcu
fuente
Esta es una respuesta genial. La clave siendo ' a' por sí sola es un puntero.
Jon L
2
"Otra forma de hacer esto sin usar una unión es lanzar un puntero uint16_t y luego desreferenciarlo" . En realidad, este tipo de conversión a menudo rompe las estrictas reglas de alias. No deberías hacerlo a menos que estés compilando -fno-strict-aliasing.
Jim Paris
3

Si su intención es concatenar las dos variables de 8 bits en una variable de 16 bits, use a union. Si desea convertir un solo miembro aen b, especifique qué elemento de la matriz desea usar.

Joe Hass
fuente
2

En su código, está lanzando solo el puntero a la matriz.

Necesita emitir el valor señalado por a.

b = (uint16_t)*a;

Nunca utilicé AVR, pero si está trabajando con una arquitectura de 16 bits, debe asegurarse de que a esté alineada por palabras. No hacerlo puede dar lugar a una excepción.

Bruno Ferreira
fuente
1
... esta no es su intención en absoluto ... quiere que b esté relacionado con ambos elementos de a (esto sería totalmente discrepante con a [1]), tampoco hay excepciones ni restricciones sobre lo que puede lanzar en avr-gcc
vicatcu
0

Cada miembro de a es un número de 8 bits. No puede contener nada más grande. Echándola a 16 bits no hace nada a una . Simplemente extrae cualquier valor que a pueda ser capaz de contener, y lo convierte a 16 bits para que coincida con el formato de b cuando el valor se almacena allí.

Ni siquiera se refirió a un miembro de a . Debe usar un [0] o un [1] (¡y no un [2]!). Si usa un solo, solo obtiene la dirección del mismo. (Buena captura, Bruno).

La declaración de un a ser una matriz de dos números de 8 bits no significa que sea un número de 16 bits, tampoco. Puede hacer algunas cosas mediante programación para almacenar y recuperar valores de 16 bits utilizando secuencias de 8 bits, pero no de la forma en que estaba pensando.

gbarry
fuente
0

Si desea convertir los bytes en aun valor de 16 bits y la representación es little endian (los 8 bits inferiores del valor vienen en el primer byte), haga

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

Para una representación big-endian hacer

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

Evite usar moldes de puntero o uniones para hacer esto, ya que eso conduce a problemas de portabilidad.

starblue
fuente