Conversión a nulo ** en diferentes compiladores

9

He estado ejecutando el siguiente código a través de diferentes compiladores:

int main()
{
    float **a;
    void **b;
    b = a;
}

Por lo que he podido reunir, novoid ** es un puntero genérico, lo que significa que cualquier conversión desde otro puntero no debe compilarse o al menos lanzar una advertencia. Sin embargo, aquí están mis resultados (todo hecho en Windows):

  • gcc : lanza una advertencia, como se esperaba.
  • g ++ : arroja un error, como se esperaba (esto se debe a la tipificación menos permisiva de C ++, ¿verdad?)
  • MSVC (cl.exe) : no genera ninguna advertencia, incluso con / Wall especificado.

Mi pregunta es: ¿Me estoy perdiendo algo acerca de todo esto y hay alguna razón específica por la cual MSVC no produce una advertencia? MSVC hace producir una advertencia al convertir de void ** a float **.

Otra cosa a tener en cuenta: si lo reemplazo a = bcon la conversión explícita a = (void **)b, ninguno de los compiladores arroja una advertencia. Pensé que esto debería ser un reparto no válido, entonces, ¿por qué no habría advertencias?

La razón por la que hago esta pregunta es porque estaba empezando a aprender CUDA y en la Guía de programación oficial ( https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory ) se puede encontrar el siguiente código:

// Allocate vectors in device memory
float* d_A;
cudaMalloc(&d_A, size);

que debe realizar una conversión implícita a void **for &d_A, ya que el primer argumento de cudaMalloces de tipo void **. Se puede encontrar un código similar en toda la documentación. ¿Es solo un trabajo descuidado al final de NVIDIA o, de nuevo, me falta algo? Como nvccusa MSVC, el código se compila sin advertencias.

CaptainProton42
fuente
3
Errores de los 3 publicados en vivo: godbolt.org/z/GQWMNo
Richard Critten
3
Los errores de código para mí con MSVC. ¿Qué versión está utilizando? Pero sí, void**no es un puntero genérico. Solo void*es
NathanOliver
¡Gracias por la rápida respuesta! 19.24.28315 para x64 aparentemente? Realmente no he usado MSVC antes.
CaptainProton42
2
(void**)es un elenco explícito de estilo C. Le dice al compilador que no mire de cerca lo que está haciendo y que confíe en usted. Es una anulación explícita del tipo de sistema de seguridad y se requiere que los compiladores acepten básicamente cualquier tipo de conversión. Se deben evitar los moldes de estilo C, son demasiado poderosos. Use modelos de C ++ como los static_castque se quejarán si está tratando de hacer algo que no tiene sentido.
François Andrieux
@RichardCritten lenguaje incorrecto: sin errores godbolt.org/z/RmFpgN C ++ cuda requiere conversiones explícitas como de costumbre.
P__J__

Respuestas:

4

¿Me estoy perdiendo algo acerca de todo esto y hay alguna razón específica por la cual MSVC no produce una advertencia? MSVC produce una advertencia al convertir de vacío ** a flotante **

Esta asignación sin conversión es una violación de restricción, por lo que un compilador compatible estándar imprimirá una advertencia o error. Sin embargo, MSVC no es totalmente compatible con la implementación de C.

Otra cosa a tener en cuenta: si reemplazo a = b con la conversión explícita a = (nulo **) b, ninguno de los compiladores arroja una advertencia. Pensé que esto debería ser un reparto no válido, entonces ¿por qué no habría advertencias?

Las conversiones de puntero a través de un reparto están permitidas en algunas situaciones. El estándar C dice lo siguiente en la sección 6.3.2.3p7:

Un puntero a un tipo de objeto puede convertirse en un puntero a un tipo de objeto diferente. Si el puntero resultante no está alineado correctamente para el tipo referenciado, el comportamiento es indefinido. De lo contrario, cuando se convierta nuevamente, el resultado se comparará igual al puntero original. Cuando un puntero a un objeto se convierte en un puntero a un tipo de carácter, el resultado apunta al byte direccionado más bajo del objeto. Los incrementos sucesivos del resultado, hasta el tamaño del objeto, arrojan punteros a los bytes restantes del objeto.

Por lo tanto, puede convertir entre tipos de punteros siempre que no haya problemas de alineación y solo vuelva a convertir (a menos que el objetivo sea a char *).

float* d_A;
cudaMalloc(&d_A, size);

...

¿Es solo un trabajo descuidado al final de NVIDIA o, de nuevo, me falta algo?

Presumiblemente, esta función está desreferenciando el puntero dado y escribiendo la dirección de alguna memoria asignada. Eso significaría que está tratando de escribir a un float *como si fuera un void *. Esto no es lo mismo que la conversión típica a / desde a void *. Estrictamente hablando, esto parece un comportamiento indefinido, aunque "funciona" porque los procesadores x86 modernos (cuando no están en modo real) usan la misma representación para todos los tipos de puntero.

dbush
fuente
@dbush Muy informativo, gracias! Entiendo por qué funciona si funciona. Aún así, ¿la mayoría de los compiladores no lanzarían una advertencia o incluso un error porque &d_Ano tienen el tipo requerido?
CaptainProton42
3
Tenga cuidado al interpretar esto, puede haber y hay trucos entre plantillas si compila CUDA con un compilador de C ++
talonmies