2012-05-10 17 views
19

Molte volte voglio una funzione per ricevere un numero variabile di argomenti, terminato da NULL, per esempioTypesafe varargs in 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); 
} 

Posso istruire gcc o clang per avvertire se foo riceve char* variabili non ? Qualcosa di simile a __attribute__(format), ma per più argomenti dello stesso tipo di puntatore.

+2

Se tutti i tipi devono essere dello stesso tipo, hai preso in considerazione solo il passaggio di un array di essi? –

+0

Nessun array nativo in C89. Non puoi passare ad esempio, 'f ({1,2,3,0}}' con il compilatore MS C. – mikebloch

+3

vuoi qualcosa che funzioni con gcc o MS C? Si prega di taggare in modo appropriato. Con C99 ci sono soluzioni che sono typesafe. –

risposta

15

So che stai pensando di utilizzare __attribute__((sentinel)) in qualche modo, ma questa è una falsa pista.

quello che vuoi è di fare qualcosa di simile:

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

che avvolge:

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

cui è possibile scrivere esattamente il modo in cui si spera si può scrivere!

quindi è possibile chiamare:

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

Non causerà la copia non necessaria dei puntatori 'char *'? –

+0

@ Chi-Lan - A seconda dell'implementazione di '_push()' GCC rimuoverà i carichi non necessari. – geocar

+0

Sto provando a fare qualcosa di simile a questo. Voglio davvero un __attributo che garantisca la sicurezza del tipo delle vararg. Fondamentalmente voglio essere sicuro che siano tutti char *. Qualcuno sa come farlo? –

1

Posso solo pensare a qualcosa di simile:

#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 fratello! Non sono sicuro se ridere o piangere ...;-) –

+0

@ Chi-Lan: C è particolarmente espressivo. :) –

+0

'Print8' è il bit migliore e peggiore di C che abbia mai visto. – huon

-1

Il problema con C variadics è che sono veramente avvitati in seguito, non proprio disegnato nella lingua. Il problema principale è che i parametri variadici sono anonimi, non hanno maniglie, non identificatori. Ciò porta alle macro VA ingombranti per generare riferimenti a parametri senza nomi. Ciò porta anche alla necessità di dire a quelle macro dove inizia l'elenco variadic e che tipo di parametri dovrebbero essere.

Tutte queste informazioni dovrebbero essere codificate in una sintassi corretta nella lingua stessa.

Ad esempio, si potrebbe estendere sintassi C esistente con parametri formali dopo l'ellissi, in questo modo

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

Per convenzione, il primo parametro potrebbe essere per il numero di argomenti e la seconda per la lista degli argomenti. All'interno del corpo della funzione, l'elenco potrebbe essere trattato sintatticamente come una matrice.

Con tale convenzione, i parametri variadici non sarebbero più anonimi. All'interno del corpo della funzione, il contatore può essere indicato come qualsiasi altro parametro e gli elementi dell'elenco può fare riferimento come se fossero elementi array di un parametro di matrice, in questo modo

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

Con una tale caratteristica integrata nel linguaggio stesso ogni riferimento a arglist[i] verrebbe quindi tradotto nei rispettivi indirizzi sullo stack frame. Non ci sarebbe bisogno di farlo via macro.

Inoltre, il conteggio degli argomenti verrà automaticamente inserito dal compilatore, riducendo ulteriormente le possibilità di errore.

Una chiamata

foo(1.23, 4.56, 7.89); 

sarà compilato come se fosse stata scritta

foo(3, 1.23, 4.56, 7.89); 

All'interno del corpo della funzione, qualsiasi accesso ad un elemento oltre il numero effettivo di argomenti effettivamente passati potrebbe essere controllati in fase di esecuzione e causano un errore in fase di compilazione, migliorando così notevolmente la sicurezza.

Ultimo ma non meno importante, tutti i parametri variadici sono digitati e possono essere controllati in fase di compilazione, proprio come i parametri non-variabili sono controllati.

In alcuni casi d'uso sarebbe auspicabile avere tipi alternativi, ad esempio quando si scrive una funzione per memorizzare chiavi e valori in una raccolta. Questo potrebbe anche essere ospitati semplicemente consentendo parametri più formali dopo i puntini di sospensione, in questo modo

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

Questa funzione potrebbe quindi essere chiamato come

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

ma sarebbe stato compilato come se fosse stata scritta

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

I tipi di parametri effettivi verrebbero confrontati con il tempo di compilazione rispetto ai corrispondenti parametri variadici formali.

All'interno del corpo della funzione contatore sarebbe nuovamente riferimento dal relativo identificatore, chiavi e valori dovrebbe essere indicata come se fossero array,

key[i] riferisce alla chiave della chiave/coppia valore i-esimo value[i] riferisce al valore della coppia valore i-esimo

e questi riferimenti sarebbero compilati ai rispettivi indirizzi sul telaio dello stack.

Niente di tutto questo è veramente difficile da fare, né è mai stato. Tuttavia, la filosofia di progettazione di C semplicemente non è favorevole a tali caratteristiche.

Senza un programmatore di avventurarsi compilatore C (o C preprocessore implementor) prendendo il comando per implementare questo o uno schema simile è improbabile che ci sarà mai visto nulla del genere in C.

Il problema è che la gente che sono interessati alla sicurezza del tipo e disposti a mettere il lavoro per costruire i propri compilatori di solito giungono alla conclusione che il linguaggio C è al di là del salvataggio e si può anche ricominciare con un linguaggio meglio progettato per cominciare.

Sono stato lì da solo, alla fine ho deciso di abbandonare il tentativo, quindi di implementare una delle lingue di Wirth e aggiunto tipi di variadics sicuri invece. Da allora mi imbatto in altre persone che mi hanno raccontato dei loro tentativi abortiti. Il tipo corretto di variadici sicure in C sembra pronto a rimanere elusivo.

+0

Il motivo per cui non ha a che fare con la sintassi, ma le modifiche al ABI: Il x86_64 ABI e i386 fastcall ABI di per esempio non richiedono che gli argomenti hanno un "indirizzo", e mentre i motori pila su CPU moderna davvero fare molto per il costo di spargere argomenti, il doppio spreco della preziosa L1 lo rende una perdita netta. Se volessi giocare con la sintassi e l'espressività, potresti provare C4 poiché ha il suo ABI (virtuale) comunque: https://github.com/rswier/c4 – geocar

+0

Se questa sintassi è stata progettata nella lingua, allora gli implementatori dovrebbero mapparlo ai rispettivi ABI. Non c'è motivo per cui non potresti farlo. I riferimenti sono risolti in fase di compilazione (almeno in termini di riferimenti rilocabili) e il compilatore ha conoscenza dell'ABI di destinazione. Inoltre, non vi è alcun motivo per cui una lista di argomenti variadici non possa essere mappata su un insieme di registri. Lo sforzo di implementazione è praticamente lo stesso con l'implementazione delle macro VA. Il vero problema è che i parametri variadici sono anonimi. Ciò impedisce di renderlo sicuro. – trijezdci

+0

C è stato progettato attorno a un ABI, non il contrario. Se vuoi un Pascal che assomigli a C, potresti crearne uno in C4 come suggerisco io - è architettonicamente simile a molte implementazioni Pascal. – geocar