Tipos de puntero incompatibles que pasan en la macro _Generic

8 minutos de lectura

avatar de usuario
Pedro Varo

El siguiente código genera 2 advertencias que se describen en el título de la pregunta.

#include <stdio.h>

static void _print_f(float *f){printf("float : %f\n", *f);}
static void _print_i(int *i)  {printf("int   : %d\n", *i);}

#define print(num) _Generic((num), \
    int*   : _print_i(num),        \
    float* : _print_f(num))


int main(void)
{
    print((&(int){10}));
    print((&(float){10.f}));

    return 0;
}

PRODUCCIÓN:

int   : 10
float : 10.000000

Lo sé, esta macro podría escribirse de la siguiente manera:

#define print(num) _Generic((num), \
    int*   : _print_i,             \
    float* : _print_f)(num)

y en ese caso, no habrá ninguna advertencia, sin embargo, mi ejemplo es un fragmento ficticio que escribí para demostrar el problema. En mi base de código real, elegí la solución anterior, porque algunos otros argumentos “predeterminados” pero específicos del tipo deben pasarse a la función seleccionada.

Entonces la pregunta es: Incluso si la macro funciona como debería y el resultado es exactamente lo que espero, ¿por qué se generan las advertencias?


Banderas y Medio Ambiente:

/* Mac OS X 10.9.4
   Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn) */
cc -Wall -v -g -std=c11 -fmacro-backtrace-limit=0 -I/usr/local/include
   -c -o build/tmp/main.o main.c

Actualización1:

¡Olvidé pegar el rastreo completo! Aquí está el primero:

main.c:39:11: warning: incompatible pointer types passing 'int *'
to parameter of type 'float *' [-Wincompatible-pointer-types]
    print((&(int){10}));
          ^~~~~~~~~~~~
main.c:31:23: note: expanded from macro 'print'
    float* : _print_f(num))
                      ^
main.c:26:29: note: passing argument to parameter 'f' here
static void _print_f(float *f){printf("float : %f\n", *f);}
                            ^

Y aquí está el segundo:

main.c:40:11: warning: incompatible pointer types passing 'float *'
to parameter of type 'int *' [-Wincompatible-pointer-types]
    print((&(float){10.f}));
          ^~~~~~~~~~~~~~~~
main.c:30:23: note: expanded from macro 'print'
    int*   : _print_i(num),        \
                      ^
main.c:27:27: note: passing argument to parameter 'i' here
static void _print_i(int *i)  {printf("int   : %d\n", *i);}
                          ^

Actualización2:

Hasta que los desarrolladores de clang solucione este error, aquí hay una solución fea para silenciar las advertencias, que funcionará si todas las claves en la lista de asociación son tipos, O todas son punteros a tipos; y fallará si los tipos Y los punteros a los tipos también están en las claves:

/* HACK: re-casting pointers to mute warnings */
#define print(num) _Generic((num), \
    int*   : _print_i((int*)num),  \
    float* : _print_f((float*)num))

  • _Generic merecería completamente su propia etiqueta, pero aparentemente StackOverflow no me deja crearlo.

    – Pascal Cuoq

    14 de julio de 2014 a las 18:48

  • @PascalCuoq lo hice, mira => Flags & Envs

    – Pedro Varo

    14/07/2014 a las 18:50

  • Parece un falso positivo. Hay un comentario en alguna parte del sitio web de Clang (en la parte inferior de clang-analyzer.llvm.org ) en el sentido de que tratan la emisión de falsos positivos como errores, por lo que vale la pena informar.

    – Pascal Cuoq

    14/07/2014 a las 18:58

  • Su solución alternativa funciona en este caso, pero no en los casos en que el lanzamiento en sí activa una advertencia; mira el ejemplo que publiqué.

    –Keith Thompson

    14/07/2014 a las 20:13

  • @KeithThompson interesante. Mi solución funciona si todas las claves en la lista de asociaciones son punteros a tipos o si todas son tipos básicos. Cambié tu ejemplo a int y float y print_i((int)i) y print_f((float)f) y también está funcionando para eso… Mi truco solo fallará si las claves de la lista asociada son una combinación de tipos simples y punteros a tipos.

    – Pedro Varo

    14/07/2014 a las 20:23

Esto no es un error en clang, pero desafortunadamente es lo que requiere el estándar C11. Todas las ramas de un _Generic La expresión principal debe ser una expresión válida y, por lo tanto, válida en todas las circunstancias. El hecho de que solo se evaluará una de las ramas no tiene nada que ver con esto.

Su versión alternativa es lo que C11 prevé para situaciones como esta: elige la función (y no la llamada evaluada) como resultado del tipo de expresión genérica y aplica esa función a los argumentos.

  • El problema con lo que dice, aunque mencionó que no tiene nada que ver con la bifurcación, es que en 6.5.1.1§3: “La expresión de control de una selección genérica no se evalúa”.“No se evalúa ninguna de las expresiones de ninguna otra asociación genérica de la selección genérica”. => ¡así que no puede ser “inválido” en absoluto, porque en realidad no se pasaron argumentos a la expresión de asignación! De todos modos, toda esta “validez” es una frase demasiado abstracta en una situación como esta, y también me pregunto dónde especifica el estándar “validez” sobre las expresiones de asignación.

    – Pedro Varo

    14/07/2014 a las 23:41

  • @PeterVaro, las expresiones en cuestión deben ser “expresión de asignación” como se especifica en el árbol de sintaxis. Si no son tales expresiones, no son válidas. Una expresión que no se evalúa aún debe ser sintácticamente correcta. tienes lo mismo para sizeof expresiones En sizeof *ala subexpresión *a no se evalúa por lo que si a sería un puntero nulo, el programa no se bloquearía. sin embargo *a debe ser una expresión válida para que el compilador pueda analizarla correctamente y también pueda determinar el tipo resultante (y de ahí su tamaño).

    – Jens Gusted

    15 de julio de 2014 a las 14:32


  • ahora entiendo un poco lo que quiso decir con expresión “válida”, y probablemente esto también explica por qué mi solución funciona cuando vuelvo a lanzar el num variable directamente. Sin embargo, no explica por qué la solución alternativa no funciona para ambos tipos y punteros a tipos al mismo tiempo en la lista asociada. De todos modos, lo que tampoco entiendo es por qué llamar a una función y pasar una variable como argumento con un tipo conocido (básicamente == sintaxis válida) no se reconoce como válido en primer lugar (_print_i(num))?

    – Pedro Varo

    15 de julio de 2014 a las 14:42

  • @PeterVaro, la sección 6.5.2.2 “Llamadas a funciones” tiene una “restricción” que requiere que los parámetros de las llamadas a funciones tengan el tipo correcto según lo especificado por el prototipo de la función. Cualquier “violación de restricción” invalida un programa y requiere un diagnóstico del compilador.

    – Jens Gusted

    15/07/2014 a las 22:35


  • Por supuesto, eso es válido para las llamadas a funciones “regulares”, sin embargo, en las llamadas a funciones “condicionales”, creo que no es necesario que cumpla con esos requisitos. Verá, si solo se evaluará una de las expresiones de asignación (que es el caso de acuerdo con el estándar), num no puede alcanzar las funciones que tienen diferentes tipos de argumentos, por lo tanto, todas las declaraciones son válidas en sus “propias situaciones”, por lo que todavía me parece un error en lugar de una implementación adecuada.

    – Pedro Varo

    15 de julio de 2014 a las 22:48


avatar de usuario
keith thompson

CORRECCION : Esto no es (hasta donde puedo decir) un error en clang, sino una interpretación correcta de cómo _Generic se supone que debe comportarse. Sólo una de las asociaciones genéricas en un selección genérica expresión se evalúa, pero todas ellas deben ser expresiones válidas. (_Generic no actúa como una macro.)

Vea la respuesta de Jens Gustedt.


Esto definitivamente parece un error en clang. Estoy razonablemente seguro de que su código es C11 válido. Veo lo mismo con la versión 3.4 en Linux Mint.

he reunido un demostración ligeramente simplificada :

#include <stdio.h>

static void print_i(int i)   { puts("int"); }
static void print_ip(int *i) { puts("int*"); }

#define print(num) _Generic((num), \
    int    : print_i(num),         \
    int*   : print_ip(num))

int main(void) {
    int i = 10;
    print(i);
    print(&i);
}

El resultado es correcto, pero recibo las siguientes advertencias:

c.c:12:11: warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'int *'; take the address with & [-Wint-conversion]
    print(i);
          ^
          &
c.c:8:23: note: expanded from macro 'print'
    int*   : print_ip(num))
                      ^
c.c:4:27: note: passing argument to parameter 'i' here
static void print_ip(int *i) { puts("int*"); }
                          ^
c.c:13:11: warning: incompatible pointer to integer conversion passing 'int *' to parameter of type 'int'; remove & [-Wint-conversion]
    print(&i);
          ^~
c.c:7:22: note: expanded from macro 'print'
    int    : print_i(num),         \
                     ^
c.c:3:25: note: passing argument to parameter 'i' here
static void print_i(int i)   { puts("int"); }
                        ^
2 warnings generated.

  • @PeterVaro: No lo he informado. Siéntete libre de hacerlo.

    –Keith Thompson

    14/07/2014 a las 19:46

  • No, esto no es un error en el sonido metálico, sino una interpretación correcta del estándar C11.

    – Jens Gusted

    14/07/2014 a las 21:12

  • @JensGustedt: Amplíe eso.

    –Keith Thompson

    14/07/2014 a las 21:13

  • @KeithThompson, ¿viste mi respuesta?

    – Jens Gusted

    14/07/2014 a las 21:15

  • @JensGustedt: Lo tengo ahora (me lo perdí antes). Y creo que tienes razón.

    –Keith Thompson

    14/07/2014 a las 21:18


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad