He examinado algunas formas de hacerlo en C, pero solo he encontrado para C99.

Pero encontré la solución a continuación, tomada de Lock Less.

El caso es que no entiendo muy bien cómo funciona y me gustaría saber los fundamentos de lo que está sucediendo allí para poder entenderlo con mayor claridad.

Busqué la Web durante un tiempo y encontré esto sobre < fuerte> __ VA_ARGS__ , pero eso por sí solo no fue suficiente por desgracia.

Realmente agradecería una explicación o alguna orientación sobre este asunto, cualquier tipo de referencia ayudaría.

He compilado este código con GCC-5.4.1 con la marca -ansi .

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>

#define COUNT_PARMS2(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _, ...) _
#define COUNT_PARMS(...)\
    COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

void count_overload1(int p1)
{
    printf("One param: %d\n", p1);
}

void count_overload2(double *p1, const char *p2)
{
    printf("Two params: %p (%f) %s\n", p1, *p1, p2);
}

void count_overload3(int p1, int p2, int p3)
{
    printf("Three params: %c %d %d\n", p1, p2, p3);
}

void count_overload_aux(int count, ...)
{
    va_list v;
    va_start(v, count);

    switch(count)
    {
        case 1:
        {
            int p1 = va_arg(v, int);
            count_overload1(p1);
            break;
        }

        case 2:
        {
            double *p1 = va_arg(v, double *);
            const char *p2 = va_arg(v, const char *);
            count_overload2(p1, p2);
            break;
        }

        case 3:
        {
            int p1 = va_arg(v, int);
            int p2 = va_arg(v, int);
            int p3 = va_arg(v, int);
            count_overload3(p1, p2, p3);
            break;
        }

        default:
        {
            va_end(v);

            printf("Invalid arguments to function 'count_overload()'");
            exit(1);
        }
    }

    va_end(v);
}
#define count_overload(...)\
    count_overload_aux(COUNT_PARMS(__VA_ARGS__), __VA_ARGS__)


int main(int argc, char const *argv[])
{
    double d = 3.14;
    count_overload(1);
    count_overload(&d, "test");
    count_overload('a',2,3);
    return 0;
}

La salida es:

One param: 1
Two params: 0x7ffc0fbcdd30 (3.140000) test
Three params: a 2 3
0
Bernardo Duarte 25 feb. 2018 a las 02:54

2 respuestas

La mejor respuesta

Analicemos las macros COUNT_PARMS y COUNT_PARMS2. Primero COUNT_PARMS:

#define COUNT_PARMS(...)\
    COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

Dado que la macro no contiene argumentos con nombre, cualquier parámetro que se le pase se coloca en lugar de __VA_ARGS__.

Entonces las siguientes llamadas:

COUNT_PARMS(arg1)
COUNT_PARMS(arg1, arg2)
COUNT_PARMS(arg1, arg2, ,arg3)

Se expandirá a:

COUNT_PARMS2(arg1,   10,    9,  8, 7, 6, 5, 4, 3, 2, 1)
COUNT_PARMS2(arg1, arg2,   10,  9, 8, 7, 6, 5, 4, 3, 2, 1)
COUNT_PARMS2(arg1, arg2, arg3, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
                                                  // x

Separé los argumentos para que pueda ver cuáles se corresponden entre sí. Tome nota especial de la columna marcada x. Este es el número de parámetros pasados a COUNT_PARMS, y es el undécimo argumento en cada caso.

Ahora veamos COUNT_PARMS2:

#define COUNT_PARMS2(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _, ...) _

Hay 11 argumentos de nombres, más ... para dar cuenta de cualquier argumento adicional. El cuerpo completo de la macro es _, que es el nombre del undécimo argumento. Entonces, el propósito de esta macro es tomar 11 o más argumentos y reemplazarlos con solo el undécimo argumento.

Mirando de nuevo la definición de COUNT_PARAMS, se expande de tal manera que llama a COUNT_PARMS2 siendo el undécimo parámetro el número de parámetros pasados a COUNT_PARAMS. Así es como ocurre la magia.

Ahora, mirando las llamadas a funciones en main:

count_overload(1);
count_overload(&d, "test");
count_overload('a',2,3);

Estos se expanden a esto:

count_overload_aux(COUNT_PARMS(1), 1);
count_overload_aux(COUNT_PARMS(&d, "test"), &d, "test");
count_overload_aux(COUNT_PARMS('a',2,3), 'a',2,3);

Luego esto:

count_overload_aux(COUNT_PARMS2(1, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), 1);
count_overload_aux(COUNT_PARMS2(&d, "test", 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), &d, "test");
count_overload_aux(COUNT_PARMS2('a',2,3, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), 'a',2,3);

Luego esto:

count_overload_aux(1, 1);
count_overload_aux(2, &d, "test");
count_overload_aux(3, 'a',2,3);

El resultado final es que puede llamar a una función que toma un número variable de argumentos sin tener que decir explícitamente cuántos hay.

5
dbush 25 feb. 2018 a las 00:25

la gran respuesta de dbush explica lo que hacen las macros. me gustaría amplíe esto y hable sobre la elipsis ... que se usa aquí. Tu dices que leer sobre las macros variadic y __VA_ARGS__ no ayudó, así que Suponga que es posible que tampoco entienda bien C elipsis.

En C, una forma de declarar una función que toma un número variable de argumentos es para utilizar la elipsis .... Un buen ejemplo de tal función es printf, que can toma al menos un parámetro, pero acepta muchos más.

El prototipo de printf es:

int printf(const char *format, ...);

El ... se usa para declarar los puntos suspensivos. Tenga en cuenta que ... solo puede aparecen al final de los argumentos nombrados y no debería ser un registro variable, una función o un tipo de matriz, por lo tanto:

void foo(...)
{
}

No es válido, el compilador le mostraría un error como este:

c.c:6:10: error: ISO C requires a named argument before ‘...’
 void foo(...)
          ^~~

Entonces, ¿cómo se usa esto? Utiliza va_list definido en stdarg.h

#include<stdio.h>
#include<stdarg.h>

int sum(int num_of_values, ...)
{
    va_list ap;

    // use the last named argument
    va_start(ap, num_of_values);

    int s = 0;
    for(int i = 0; i < num_of_values; ++i)
    {
        int v = va_arg(ap, int);
        s += v;
    }

    va_end(ap);

    return s;
}

int main(void)
{
    printf("The sum is: %d\n", sum(5, 1, 2, 3, 4, 5));
}

Que generará The sum is: 15.

Entonces, cuando su función tiene una elipsis, primero debe declarar una variable de tipo va_list y llame a va_start con esa variable como primer argumento y el último argumento nombrado como segundo argumento.

Luego, puede obtener los valores mediante va_arg(ap, <type>), donde <type> es el tipo de valor, en el caso del ejemplo anterior, sería int. Funciones como printf analiza el formato y usa los especificadores de conversión para obtener la tipo. Cuando printf encuentra un %d, funcionará va_arg(ap, int), si %f es encontró que funcionaría va_arg(ap, float) y si se encuentra %s, funcionaría va_arg(ap, char*) y así sucesivamente. Es por eso que printf tiene un comportamiento indefinido cuando el formato y los argumentos no coinciden, porque se usaría un tipo incorrecto en el va_arg llamada que se enreda con llamadas posteriores de va_arg Al final, se debe llamar a va_end.

Para un micro kernel que tuve que escribir durante mis días en la universidad, tuve para implementar estas va_* - macros. Usé el comportamiento del compilador que poner todos los argumentos en el marco de la pila, por lo que mi va_start calculó la dirección en esa pila del siguiente valor después del último argumento nombrado. va_arg se movió por la pila basado en el cálculo de va_start más un desplazamiento determinado por el tipo mientras también actualizando la variable ap con el último argumento consumido. Era Es difícil hacerlo funcionar, pero al final funcionó en ese sistema, sin embargo, la misma implementación en un x86_64 solo produce basura.

Cómo se implementa exactamente esto, por ejemplo en el compilador de GCC, no lo sé, pero sospecho que GCC hace algo similar. Revisé el código fuente gcc/builtins.c : 4855 pero, como de costumbre, Encuentro que el código GCC es muy complicado de seguir.

2
user2736738 25 feb. 2018 a las 04:29