Actualmente estoy tratando de familiarizarme con las reglas de alias estricto de C y, según mi comprensión actual, este código las está violando.
Hemos convertido el puntero del búfer en el puntero de configuración de la estructura y, según el estándar C, debería conducir a un comportamiento indefinido, ¿verdad?
static inline void libusb_fill_control_transfer( struct libusb_transfer *transfer, libusb_device_handle *dev_handle, unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) { struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer; transfer->dev_handle = dev_handle; transfer->endpoint = 0; transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; transfer->timeout = timeout; transfer->buffer = buffer; if (setup) transfer->length = (int) (LIBUSB_CONTROL_SETUP_SIZE + libusb_le16_to_cpu(setup->wLength)); transfer->user_data = user_data; transfer->callback = callback; }Editar.
Esto es parte del proyecto libusb https://github.com/libusb/libusb/blob/7ffad5c137ed4c1d8a3ac485f35770fb979ca53a/libusb/libusb.h#L1578
Editar 2.
Agregar definición de estructura libusb_control_setup
struct libusb_control_setup { /** Request type. Bits 0:4 determine recipient, see * \ref libusb_request_recipient. Bits 5:6 determine type, see * \ref libusb_request_type. Bit 7 determines data transfer direction, see * \ref libusb_endpoint_direction. */ uint8_t bmRequestType; /** Request. If the type bits of bmRequestType are equal to * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to * \ref libusb_standard_request. For other cases, use of this field is * application-specific. */ uint8_t bRequest; /** Value. Varies according to request */ uint16_t wValue; /** Index. Varies according to request, typically used to pass an index * or offset */ uint16_t wIndex; /** Number of bytes to transfer */ uint16_t wLength; };Suponiendo que el buffer parámetros en realidad no apunte a un objeto de tipo struct libusb_control_setup (¿probablemente una matriz de caracteres unsigned char ?), Entonces sí, esta es una violación estricta de alias que es un comportamiento indefinido .
Las reglas que rigen el alias se especifican en la sección 6.5p7 del estándar C :
Un objeto tendrá acceso a su valor almacenado solo mediante una expresión lvalue que tenga uno de los siguientes tipos: 88)
- un tipo compatible con el tipo efectivo del objeto,
- una versión calificada de un tipo compatible con el tipo efectivo del objeto,
- un tipo que es el tipo firmado o no firmado correspondiente al tipo efectivo del objeto,
- un tipo que es el tipo firmado o no firmado correspondiente a una versión calificada del tipo efectivo del objeto,
- un tipo de agregado o unión que incluye uno de los tipos antes mencionados entre sus miembros (incluido, recursivamente, un miembro de un subagregado o unión contenida), o
- un tipo de personaje.
88 ) La intención de esta lista es especificar aquellas circunstancias en las que un objeto puede o no ser alias.
Tenga en cuenta que esto no incluye tratar una char de caracteres como si fuera de otro tipo, aunque se permite lo contrario.
La forma correcta de manejar esto es crear una estructura local del tipo dado, luego usar memcpy para copiar los bytes.
struct libusb_control_setup setup; memcpy(&setup, buffer, sizeof setup);Como una adición a la respuesta de @dbush
Con respecto al ejemplo proporcionado, así es como lo habría hecho, pero dado que este es un código de un proyecto respetable que obviamente se usa en muchos lugares, no estoy seguro de qué pensar al respecto.
Muchos programadores utilizan el juego de palabras con punteros, que piensan que es seguro porque funciona en sus computadoras. Muchos programadores también piensan que usar memcpy hará que su código sea menos eficiente y más ávido de memoria.
En la mayoría de las circunstancias (cuando sea posible, por supuesto), el compilador optimizará la llamada memcpy
ejemplo:
typedef struct { int a; double b; int (*callback)(int); }mt; int foo(char *ptr, int par) { mt m; memcpy(&m, ptr, sizeof(m)); printf("%f\n", mb); m.callback(par); return ma; }El compilador x86 produce código:
.LC0: .string "%f\n" foo: push rbp mov ebp, esi sub rsp, 32 movdqu xmm1, XMMWORD PTR [rdi] mov rax, QWORD PTR [rdi+16] mov edi, OFFSET FLAT:.LC0 movaps XMMWORD PTR [rsp], xmm1 movsd xmm0, QWORD PTR [rsp+8] mov QWORD PTR [rsp+16], rax mov eax, 1 call printf mov edi, ebp call [QWORD PTR [rsp+16]] mov eax, DWORD PTR [rsp] add rsp, 32 pop rbp ret Pero ARM Cortex M0 llamará a memcpy ya que una versión no alineada del puntero causará una excepción de hardware.
.LC0: .ascii "%f\012\000" foo: push {r4, lr} movs r4, r1 sub sp, sp, #32 movs r1, r0 movs r2, #24 add r0, sp, #8 bl memcpy ldr r2, [sp, #16] ldr r3, [sp, #20] ldr r0, .L3 str r2, [sp] str r3, [sp, #4] bl printf movs r0, r4 ldr r3, [sp, #24] blx r3 ldr r0, [sp, #8] add sp, sp, #32 pop {r4, pc} .L3: .word .LC0El uso de una estructura o tipo de unión para leer o escribir datos desde un búfer de caracteres suministrado externamente plantea dos problemas potenciales:
El estándar permite que las implementaciones cuyos usuarios nunca necesiten acceder al almacenamiento como diferentes tipos y en diferentes momentos de licencia amplia supongan que los programas no realizarán dichos accesos. Si bien reconoce explícitamente la existencia de implementaciones que garantizan que todos los accesos a los objetos se tratarán como accesos al almacenamiento subyacente utilizando la semántica precisa del entorno de ejecución de alojamiento (consulte N1570 5.1.2.3 párrafo 9), ya sea que el estándar los requiera o no. para hacerlo, y permite que los programas conformes (pero no estrictamente) conformes apunten exclusivamente a tales implementaciones, no hace distinción entre las implementaciones que ofrecen tales garantías y las que no.
El comportamiento de lanzar un puntero a una estructura o tipo de unión solo tiene un comportamiento definido si el puntero satisface los requisitos de alineación más estrictos de cada miembro del mismo. Si la estructura o unión tiene un miembro cuyo requisito de alineación no es satisfecho por el puntero, la conversión al tipo de estructura o unión invocará un comportamiento indefinido y, en algunas implementaciones del mundo real, es probable que se produzca un código erróneo incluso si el puntero cumple con los requisitos. requisito de alineación para todos los miembros que se utilizan realmente .
El primer problema se puede solucionar mediante el uso de una configuración de compilador adecuada para la tarea en cuestión. El segundo, sin embargo, debe tenerse en cuenta incluso si se usa el -fno-strict-aliasing dialect . Dado algo como:
#include <string.h> struct foo { short x, y; int z; } sf; void test1(void *p) { memcpy(&sf, p, sizeof (struct foo)); } void test2(void *p) { struct foo *foop = p; memcpy(&sf, foop, sizeof (struct foo)); } Al apuntar a Cortex-M0, clang generará código para test1 que será mucho menos eficiente (casi una penalización de velocidad y espacio de 4:1) que test2 en el caso en que p esté alineado con palabras, pero el código para test2 fallará si p no está alineado con las palabras, mientras que el código más lento para test1 funcionará independientemente.
Por lo tanto, incluso cuando se usa memcpy , la decisión de emitir un puntero a un tipo de estructura debe considerar lo que se sabe sobre la alineación. Tal lanzamiento puede mejorar en gran medida la eficiencia si se conoce la alineación, pero hacer que un programa se bloquee si no se conoce.