2012-05-10 37 views
19

Muchas veces quiero una función para recibir un número variable de argumentos, terminada por nulo, por ejemploTypesafe varargs en C con gcc

#define push(stack_t stack, ...) _push(__VARARG__, NULL); 
func _push(stack_t stack, char *s, ...) { 
    va_list args; 
    va_start(args, s); 
    while (s = va_arg(args, char*)) push_single(stack, s); 
} 

¿Puedo instruir gcc o sonido metálico para advertir si foo recibe char* variables no ? Algo similar a __attribute__(format), pero para varios argumentos del mismo tipo de puntero.

+2

Si todos los tipos deben ser del mismo tipo, ¿ha considerado pasar una serie de ellos? –

+0

Ninguna matriz nativa en C89. No se puede pasar, por ejemplo, 'f ({1,2,3,0})' con el compilador MS C. – mikebloch

+3

¿quieres algo que funcione con gcc o MS C? Por favor marque apropiadamente. Con C99 hay soluciones que son seguras en cuanto a tipo de letra. –

Respuesta

15

Sé que está pensando en usar __attribute__((sentinel)) alguna manera, pero esto es una cortina de humo.

Lo que se quiere es hacer algo como esto:

#define push(s, args...) ({     \ 
    char *_args[] = {args};      \ 
    _push(s,_args,sizeof(_args)/sizeof(char*)); \ 
}) 

que envuelve:

void _push(stack_t s, char *args[], int argn); 

que se puede escribir exactamente la forma en que sería de desear, lo puede escribir!

Entonces se le puede llamar:

push(stack, "foo", "bar", "baz"); 
push(stack, "quux"); 
+0

¿No provocará una copia innecesaria de los punteros 'char *'? –

+0

@ Chi-Lan - Dependiendo de la implementación de '_push()' GCC eliminará las cargas innecesarias. – geocar

+0

Estoy tratando de hacer algo similar a esto. Realmente quiero un __ atributo que garantice la seguridad de tipo de los varargs. Básicamente quiero asegurarme de que sean todos char *. ¿Alguien sabe cómo hacer eso? –

1

sólo puedo pensar en algo como esto:

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

typedef struct tArg 
{ 
    const char* Str; 
    struct tArg* Next; 
} tArg; 

tArg* Arg(const char* str, tArg* nextArg) 
{ 
    tArg* p = malloc(sizeof(tArg)); 
    if (p != NULL) 
    { 
    p->Str = str; 
    p->Next = nextArg; 
    } 
    else 
    { 
    while (nextArg != NULL) 
    { 
     p = nextArg->Next; 
     free(nextArg); 
     nextArg = p; 
    } 
    } 
    return p; 
} 

void PrintR(tArg* arg) 
{ 
    while (arg != NULL) 
    { 
    tArg* p; 
    printf("%s", arg->Str); 
    p = arg->Next; 
    free(arg); 
    arg = p; 
    } 
} 

void (*(*(*(*(*(*(*Print8 
    (const char* Str)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*) 
{ 
    printf("%s", Str); 
    // There's probably a UB here: 
    return (void(*(*(*(*(*(*(*) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*))&Print8; 
} 

int main(void) 
{ 
    PrintR(Arg("HELLO", Arg(" ", Arg("WORLD", Arg("!", Arg("\n", NULL)))))); 
// PrintR(Arg(1, NULL));  // warning/error 
// PrintR(Arg(&main, NULL)); // warning/error 
// PrintR(Arg(0, NULL));  // no warning/error 
// PrintR(Arg((void*)1, NULL)); // no warning/error 

    Print8("hello")(" ")("world")("!")("\n"); 
// Same warning/error compilation behavior as with PrintR() 
    return 0; 
} 
+3

¡Oh hermano! No estoy seguro de si reír o llorar ...;-) –

+0

@ Chi-Lan: C es peculiarmente expresivo. :) –

+0

'Print8' es la mejor y la peor parte de C que he visto en mi vida. – huon

-1

El problema con C variadics es que realmente están atornillados después, no es realmente diseñado a la lengua. El principal problema es que los parámetros variados son anónimos, no tienen identificadores ni identificadores. Esto lleva a las macros VA inmanejables para generar referencias a parámetros sin nombres. También lleva a la necesidad de contar esas macros donde comienza la lista variadic y de qué tipo se espera que sean los parámetros.

Toda esta información realmente debería estar codificada con la sintaxis adecuada en el idioma en sí.

Por ejemplo, se podría extender la sintaxis de C existente con parámetros formales después de la elipsis, al igual que

void foo (... int counter, float arglist); 

Por convención, el primer parámetro podría ser para el número del argumento y el segundo para la lista de argumentos. Dentro del cuerpo de la función, la lista podría tratarse sintácticamente como una matriz.

Con tal convención, los parámetros variadic ya no serían anónimos. Dentro del cuerpo de la función, el contador puede ser referenciado como cualquier otro parámetro y los elementos de la lista se puede hacer referencia como si fueran elementos de la matriz de un parámetro de matriz, al igual que

void foo (... int counter, float arglist) { 
    unsigned i; 
    for (i=0; i<counter; i++) { 
    printf("list[%i] = %f\n", i, arglist[i]); 
    } 
} 

Con esta característica integrada en la propia lengua , cada referencia a arglist[i] se traduciría a las direcciones respectivas en el marco de la pila. No habría necesidad de hacer esto a través de macros.

Además, el recuento de argumentos sería insertado automáticamente por el compilador, reduciendo aún más la posibilidad de error.

Una llamada a

foo(1.23, 4.56, 7.89); 

se compilará como si hubiera sido escrito

foo(3, 1.23, 4.56, 7.89); 

Dentro del cuerpo de la función, cualquier acceso a un elemento más allá del número real de los argumentos que se le pasan podría ser se verifica en el tiempo de ejecución y causa una falla en el tiempo de compilación, lo que mejora en gran medida la seguridad.

Por último, todos los parámetros variados se escriben y se pueden verificar en tiempo de compilación al igual que se verifican los parámetros no variados.

En algunos casos de uso, sería deseable tener tipos alternativos, como cuando se escribe una función para almacenar claves y valores en una colección. Esto también podría tener cabida simplemente permitiendo más parámetros formales después de la elipsis, al igual que

void store (collection dict, ... int counter, key_t key, val_t value); 

Esta función podría entonces ser llamado como

store(dict, key1, val1, key2, val2, key3, val3); 

pero serían compilados como si hubiera sido escrito

store(dict, 3, key1, val1, key2, val2, key3, val3); 

Los tipos de parámetros reales serían el tiempo de compilación contrastado con los parámetros variadic formales.

Dentro del cuerpo de la función del contador de nuevo se hace referencia por su identificador, claves y valores sería referenciado como si fueran arrays,

key[i] se refiere a la clave de la clave/valor par i-ésimo value[i] se refiere al valor del par de valores i-ésimo

y estas referencias se compilarán en sus respectivas direcciones en el marco de la pila.

Nada de esto es realmente difícil de hacer, ni lo ha sido alguna vez. Sin embargo, la filosofía de diseño de C simplemente no favorece dichas características.

Sin un implementador de aventurarse compilador de C (o C implementador preprocesador) tomar la iniciativa para poner en práctica este o un esquema similar es poco probable que alguna vez vimos nada de este tipo en C

El problema es que la gente que están interesados ​​en la seguridad de tipo y están dispuestos a poner en el trabajo para construir sus propios compiladores generalmente llegan a la conclusión de que el lenguaje C está más allá de salvamento y uno puede comenzar de nuevo con un lenguaje mejor diseñado para empezar.

He estado allí, finalmente decidí abandonar el intento, luego implementé uno de los idiomas de Wirth y agregué el tipo de variantes seguras a eso en su lugar. Desde entonces me encontré con otras personas que me contaron sobre sus propios intentos abortados. Las variantes seguras de tipo adecuado en C parecen estar a punto de seguir siendo difíciles de alcanzar.

+0

La razón por la cual no tiene que ver con la sintaxis, pero los cambios al ABI: el ABI x86_64 y el ABI de llamada rápida i386 por ejemplo no requieren que los argumentos tengan una "dirección", y mientras los motores de pila en las CPU modernas realmente hacer mucho por el costo de derramar argumentos, el doble derroche del precioso L1 lo convierte en una pérdida neta. Si solo quisiera jugar con la sintaxis y la expresividad, podría probar C4 ya que tiene su propio ABI (virtual) de todos modos: https://github.com/rswier/c4 – geocar

+0

Si esta sintaxis se diseñó en el idioma, entonces los implementadores tendrían que asignarlo a sus respectivos ABI. No hay ninguna razón por la que no puedas hacer eso. Las referencias se resuelven en tiempo de compilación (al menos en términos de referencias reubicables) y el compilador tiene conocimiento del ABI objetivo. Además, no hay ninguna razón por la cual una lista de argumentos variados no pueda ser mapeada a un conjunto de registros. El esfuerzo de implementación es más o menos el mismo que con la implementación de las macros VA. El verdadero problema es que los parámetros variados son anónimos. Eso se interpone en el camino de hacer que el tipo sea seguro. – trijezdci

+0

C se diseñó alrededor de un ABI, y no al revés. Si quieres un Pascal que se parece a C, puedes construir uno de c4 como sugiero, es arquitectónicamente similar a muchas implementaciones de Pascal. – geocar