Función/macro Variadic SIN UTILIZAR

11 minutos de lectura

avatar de usuario de dag
trozo de cuero

Una forma conocida y portátil de suprimir las advertencias del compilador de C sobre variables no utilizadas es (consulte las advertencias de parámetros no utilizados en el código C):

#define UNUSED(x) (void)(x)

Estoy buscando una forma de generalizar esto para tomar múltiples entradas (de diferente tipo):

void foo(int a, long b, void* c){

   /* Want this: */
   ALL_UNUSED(a, b, c);

   /* instead of: */
   UNUSED(a);
   UNUSED(b);
   UNUSED(c);
}

Una forma que parece funcionar es usar una función variádica

static inline void ALL_UNUSED(int dummy, ...) {}

Sin embargo, sospecho que esta solución es objetable para el ojo experto.

¿Existe un estándar compatible y portátil (es decir, que no utilice __attribute__((unused))) forma de hacer una función/macro variable UNUSED()? ¡Muchas gracias!

EDITAR

No parece existir una forma limpia de hacer lo que pedí en el contexto de C99 o el preprocesador C. Así es la vida.

En su respuesta a continuación, @Dabo muestra una forma bastante interesante de hacer lo que pedí usando una serie de macros. Esto es claro e informativo (al menos para mí), así que acepto esa respuesta. Dicho esto, no lo implementaría en un gran proyecto porque es lo suficientemente complicado como para compensar el beneficio que brinda (en mi opinión). Pero la gente llegará a diferentes conclusiones aquí.

Como se indica a continuación, el enfoque de usar una función variádica vacía tampoco es perfecto. Si bien es una línea bastante elegante, provocará advertencias sobre variables unitarias (si lo son). Además, debe confiar en su compilador para optimizarlo por completo, a lo que me opongo en principio, pero que todos los compiladores con los que he probado realmente lo hacen.

Un caso relevante es cuando el stubing funciona después de una fase temprana de diseño de interfaz de alto nivel. Luego, todas sus variables no utilizadas serán argumentos de función e inicializados por definición, y el siguiente enfoque funciona bien

static inline void UNUSED(int dummy, ...) {}

void foo(int a, long b, void* c){
    UNUSED(a, b, b); /* No warnings */
}

  • ¿Qué pasa con el uso #define UNUSED(...) (void)(__VA_ARGS__).

    – No te preocupes niño

    23 de abril de 2014 a las 5:59


  • Lamentablemente no, lo intenté. El compilador advertirá sobre un resultado de expresión no utilizado o un “valor como declaración”. Semánticamente, qué esperas (vacío) (a, b, c); ¿significar?

    – dag

    23 de abril de 2014 a las 6:07

  • Gracias por señalar eso. Probando de otra forma…

    – No te preocupes niño

    23 de abril de 2014 a las 6:10

  • Nota gcc tiene la opción -Wno-unused-variable que parece hacer lo que pides? Aunque tal vez la pregunta sea más sobre varadics y macros…

    – spinkus

    23 de abril de 2014 a las 7:20


  • #define UNUSED(x) (void)(x) Realmente evalúa ‘X’ … #define UNUSED(x) (void)(sizeof(x)) tampoco es una solución perfecta ya que C99 introdujo los famosos VLA (matrices de longitud variable) que también se evalúan cuando se pasan al sizeof()

    usuario10133158

    12/09/2018 a las 10:52


Avatar de usuario de Dabo
dabo

Basado en estos dos mensajes. Macro variádica para contar el número de argumentosy Sobrecarga de macros hice lo siguiente

#define UNUSED1(x) (void)(x)
#define UNUSED2(x,y) (void)(x),(void)(y)
#define UNUSED3(x,y,z) (void)(x),(void)(y),(void)(z)
#define UNUSED4(a,x,y,z) (void)(a),(void)(x),(void)(y),(void)(z)
#define UNUSED5(a,b,x,y,z) (void)(a),(void)(b),(void)(x),(void)(y),(void)(z)

#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5, N,...) N
#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1)

#define ALL_UNUSED_IMPL_(nargs) UNUSED ## nargs
#define ALL_UNUSED_IMPL(nargs) ALL_UNUSED_IMPL_(nargs)
#define ALL_UNUSED(...) ALL_UNUSED_IMPL( VA_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__ )

lo que se puede utilizar de la siguiente manera

 int main()
 {
    int a,b,c;
    long f,d;

    ALL_UNUSED(a,b,c,f,d);

    return 0;
  }

Eclipse macro expansión da:

  (void)(a),(void)(b),(void)(c),(void)(f),(void)(d)

compilado con gcc -Wall sin advertencias

EDITAR:

#define UNUSED1(z) (void)(z)
#define UNUSED2(y,z) UNUSED1(y),UNUSED1(z)
#define UNUSED3(x,y,z) UNUSED1(x),UNUSED2(y,z)
#define UNUSED4(b,x,y,z) UNUSED2(b,x),UNUSED2(y,z)
#define UNUSED5(a,b,x,y,z) UNUSED2(a,b),UNUSED3(x,y,z)

EDIT2

Como para inline método que publicaste, una prueba rápida

int a=0;
long f,d;

ALL_UNUSEDINLINE(a,f,&d);

da ‘f’ is used uninitialized in this function [-Wuninitialized] advertencia. Así que aquí hay al menos un caso de uso que rompe la generalidad de este enfoque.

  • ¡Muy agradable! Jugaré con esto y veré cómo se siente. La enumeración manual parece inevitable, pero difícilmente hace que alguien se enamore de cpp. ¡Gracias!

    – dag

    23 de abril de 2014 a las 8:38

  • ¿Puedo preguntarle cuál es su opinión sobre el enfoque de función variádica nula que menciono en la publicación (que es algo más breve que su impresionante solución macro)? ¿Le parece incorrecto y/o defectuoso?

    – dag

    23 de abril de 2014 a las 8:42

  • @dag mira mi edit2 con respecto a inlinesupongo que responde a tu pregunta.

    – Dabo

    23 de abril de 2014 a las 11:12

  • ¡Gracias por señalarlo! El caso de uso (no declarado) que tenía en mente era en realidad solo para suprimir las advertencias de los argumentos de función no utilizados.

    – dag

    25 de abril de 2014 a las 4:12

avatar de usuario de kugel
kugel

Tomé la increíble solución de Dabo (https://stackoverflow.com/a/23238813/5126486) y la mejoré un poco para que sea más fácil extenderla a más de 5:

#define UNUSED0()
#define UNUSED1(a)                  (void)(a)
#define UNUSED2(a,b)                (void)(a),UNUSED1(b)
#define UNUSED3(a,b,c)              (void)(a),UNUSED2(b,c)
#define UNUSED4(a,b,c,d)            (void)(a),UNUSED3(b,c,d)
#define UNUSED5(a,b,c,d,e)          (void)(a),UNUSED4(b,c,d,e)

#define VA_NUM_ARGS_IMPL(_0,_1,_2,_3,_4,_5, N,...) N
#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(100, ##__VA_ARGS__, 5, 4, 3, 2, 1, 0 )

#define ALL_UNUSED_IMPL_(nargs) UNUSED ## nargs
#define ALL_UNUSED_IMPL(nargs) ALL_UNUSED_IMPL_(nargs)
#define ALL_UNUSED(...) ALL_UNUSED_IMPL( VA_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__ )

  • No maneja 0 argumentos varadic… pero es una solución fácil. voy a editar…

    – Miguel

    11 de noviembre de 2019 a las 21:18

Avatar de usuario de Gian Lorenzo Meocci
Gian Lorenzo Meocci

Qué piensas sobre esto:

#define UNUSED(...) [__VA_ARGS__](){};

Ejemplo:

void f(int a, char* b, long d)
{
    UNUSED(a, b, d);
}

Debería expandirse ad una definición lambdas:

[a,b,d](){}; //optimized by compiler (I hope!)

===== Probado con http://gcc.godbolt.org ===== Lo he intentado con este código:

#define UNUSED(...) [__VA_ARGS__](){};

int square(int num, float a) {
  UNUSED(a);
  return num * num;
}

La salida resultante (compilada con -O0 -Wall) es:

square(int, float):
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movss   %xmm0, -8(%rbp)
    movl    -4(%rbp), %eax
    imull   -4(%rbp), %eax
    popq    %rbp
    ret

EDITAR:

Si puede usar C++ 11, esta podría ser una mejor solución:

template <typename ...Args>
void UNUSED(Args&& ...args)
{
    (void)(sizeof...(args));
}

  • Esta pregunta es sobre C, no C++11.

    usuario824425

    21/09/2016 a las 19:11

  • Conduce a “Advertencia: resultado de expresión no utilizado”, pero esto puede ser fácil de arreglar

    – Simón Warta

    16 abr 2017 a las 20:05

  • +1 comentario para el usuario 824425 y -1 respuesta para Gian Lorenzo Meocci. La pregunta es sobre C. Si fuera C++, simplemente puede omitir los nombres de los argumentos (pero dejar los tipos).

    – Sr. Stobbe

    20 de abril de 2019 a las 7:00

  • La lambda requiere que los objetos sean copiables.

    – Santo Gato Negro

    22 de abril de 2021 a las 5:41

Avatar de usuario de Matt Eding
matt eding

Aquí hay un enfoque muy simple que no requiere ningún truco especial ni incurre en ninguna sobrecarga de tiempo de ejecución.

Respuesta original (solo C++)

#define UNUSED(...) (void)sizeof(__VA_ARGS__)

https://godbolt.org/z/Gz8MfvaTz

Respuesta actualizada (C y C++)

Me había olvidado de cambiar el compilador de C++ a C y, por lo tanto, la respuesta revisada.

int main(int argc, char ** argv)
{
    /* different types */
    UNUSED(argc, argv);

    /* many variables */
    int w, x, y, z;
    UNUSED(w, x, y, z);

    /* single variable */
    void * ptr;
    UNUSED(ptr);
}

MSVC

/O2 /W4
Sin advertencias de los compiladores de C y C++.

Sonido metálico

-O3 -Pared -Wextra
Sin advertencias de los compiladores de C y C++.

CCG

-O3 -Pared -Wextra
No hay advertencias del compilador de C++ únicamente. Para el compilador de C, solo el último parámetro se ignora sin previo aviso. Entonces, en el peor de los casos, esta versión de UNUSED está a la altura de lo tradicional #define UNUSED(x) ((void)x).

Lo siguiente no causa advertencias con -Wall -Wextra tanto en GCC como en Clang, pero solo funciona para C11 y más alto:

#define UNUSED(...) ((void)sizeof((_Bool[]){__VA_ARGS__}))

  • Eso depende de los tipos de parámetros pasados, ¿no? Es posible que no tengan los mismos tipos compatibles que están permitidos como inicializadores para la matriz, lo que crea otra advertencia o incluso errores, por ejemplo, al pasar literales de cadena, obtendría “matriz de tipo inapropiado inicializada a partir de una constante de cadena”. Además, ¿por qué esto requeriría C11?

    – stefanct

    23 de septiembre a las 22:19

  • No recibo este error con los literales de cadena, pero sí, esto solo funciona para algunos tipos, ya que los tipos de estructura y unión no se pueden convertir a _Bool, por lo que deberá tomar la dirección de las variables con esos tipos. La macro requiere C11 porque los literales compuestos ((_Bool[]){__VA_ARGS__}) se agregaron en C11, si su proyecto no usa C11 (o posterior), tendrá que recurrir a una solución menos mínima. Me gusta esta solución porque todos mis proyectos son C11 y porque las estructuras y uniones no utilizadas (no punteros) son muy raras.

    – yyny

    28 de septiembre a las 15:26

  • Se han agregado literales compuestos en C99.

    – stefanct

    29 de septiembre a las 16:08

avatar de usuario de vjalle
vjalle

Una versión un poco refinada de la solución en la pregunta que acepta cualquier tipo de argumentos:

static inline void UNUSED2(int dummy, ...)
{
    (void)dummy;
}
#define UNUSED(...) UNUSED2(0, __VA_ARGS__)

Parece estar trabajando con varios compiladores, no solo GCC.

  • Eso depende de los tipos de parámetros pasados, ¿no? Es posible que no tengan los mismos tipos compatibles que están permitidos como inicializadores para la matriz, lo que crea otra advertencia o incluso errores, por ejemplo, al pasar literales de cadena, obtendría “matriz de tipo inapropiado inicializada a partir de una constante de cadena”. Además, ¿por qué esto requeriría C11?

    – stefanct

    23 de septiembre a las 22:19

  • No recibo este error con los literales de cadena, pero sí, esto solo funciona para algunos tipos, ya que los tipos de estructura y unión no se pueden convertir a _Bool, por lo que deberá tomar la dirección de las variables con esos tipos. La macro requiere C11 porque los literales compuestos ((_Bool[]){__VA_ARGS__}) se agregaron en C11, si su proyecto no usa C11 (o posterior), tendrá que recurrir a una solución menos mínima. Me gusta esta solución porque todos mis proyectos son C11 y porque las estructuras y uniones no utilizadas (no punteros) son muy raras.

    – yyny

    28 de septiembre a las 15:26

  • Se han agregado literales compuestos en C99.

    – stefanct

    29 de septiembre a las 16:08

Puedes usar el tiempo de compilación __VA_ARGS__ macro.

#define UNUSED(...) (void)(__VA_ARGS__)

ACTUALIZAR: Después de muchas pruebas, se me ocurrió una solución optimizada:

#define UNUSED(...)  __VA_ARGS__

int main()
{
    int e, x;
    char **a, **b, *c, d[45];
    x = x, UNUSED(a, b, c, d, e), x; 

    return 0;
}

NOTAS:

  1. No elimina completamente las advertencias, pero reduce ellos solo 3 mismo tipo de advertencias:
    warning: value computed is not used

  2. el primero y el ultimo x garantizar la asignación de los mismos tipos de datos.

  3. Diré que está optimizado porque para cualquier número de variables no utilizadas da 3 advertencias (puede que me equivoque, pruébelo usted mismo e infórmeme si obtiene más) y la cantidad de código (manipulaciones MACRO) requerida para lograrlo es menor.

  4. Todavía estoy trabajando en ello, publicaré si llego a una solución mejor.

  • Gracias @MadHatter, pero creo que tanto Clang como GCC todavía emiten advertencias con esto. GCC “advertencia: el operando izquierdo de la expresión de coma no tiene efecto [-Wunused-value]”, Clang: “advertencia: resultado de la expresión no utilizado [-Wunused-value]”

    – dag

    23 de abril de 2014 a las 6:12

  • Ya!! Lo compilé yo mismo con gcc -Wall y recibió la misma advertencia. Todavía estoy intentando algo…

    – No te preocupes niño

    23 de abril de 2014 a las 6:17

¿Ha sido útil esta solución?