2010-07-09 14 views
5

Sto avendo una domanda di programmazione C: Voglio scrivere una funzione con liste di argomenti variabili, dove i tipi specifici di ciascun argomento non sono noti - solo la sua dimensione in byte. Ciò significa che, se voglio ottenere un parametro int, I (da qualche parte prima) definire: Ci sarà un parametro con sizeof (int), che viene gestito con una funzione di callback xyz.va_list con nomi di tipi sconosciuti - solo la dimensione del byte è nota!

La mia funzione argomento variabile dovrebbe ora raccogliere tutte le informazioni dal suo richiamo, le operazioni specifiche del tipo di dati reali (che possono essere anche tipi di dati definiti dall'utente) vengono elaborate solo tramite funzioni di richiamata.

Alle funzioni standard di va_arg, non è possibile pronunciare "ottieni un valore di X byte dalla mia lista parametri", quindi ho pensato di farlo in questo modo. Il mio tipo di dati è doppio in questo caso, ma può essere qualsiasi altro numero di byte (e anche quelli variabili).

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

int fn(int anz, ...) 
{ 
     char* p; 
     int    i; 
     int    j; 
     va_list args; 
     char* val; 
     char* valp; 
     int    size = sizeof(double); 

     va_start(args, anz); 

     val = malloc(size); 

     for(i = 0; i < anz; i++) 
     { 
       memcpy(val, args, size); 
       args += size; 

       printf("%lf\n", *((double*)val)); 
     } 

     va_end(args); 
} 

int main() 
{ 
     fn(1, (double)234.2); 
     fn(3, (double)1234.567, (double)8910.111213, (double)1415.161718); 

     return 0; 
} 

Funziona per me, sotto Linux (gcc). Ma la mia domanda ora è: è davvero portatile? Oppure fallirà sotto altri sistemi e compilatori?

mio approccio alternativo era quello di sostituire

   memcpy(val, args, size); 
       args += size; 

con

  for(j = 0; j < size; j++) 
        val[j] = va_arg(args, char); 

ma poi, i miei valori è andato storto.

Qualche idea o aiuto su questo?

+2

Non puoi decidere in anticipo tutti i tipi di dati che prevedi di passare? Quindi assegnare un argomento per ognuno. Riservare il primo argomento come int che quindi indica il tipo. Il chiamante della funzione HA sapere quale tipo di dato è stato superato. Un modo più tradizionale per farlo è passare un vuoto * insieme a un argomento int, quindi lanciare l'argomento void * sul tipo di dati corretto basato sull'int (o enum). –

+0

Questo è quello che voglio evitare. I miei tipi di dati sono noti, ma solo nelle funzioni di callback specifiche dell'utente che funzionano su questi dati. La mia funzione universale che voglio implementare dovrebbe solo costruire un nuovo oggetto dati in memoria lungo una definizione di oggetto dati. Questa definizione (che è una matrice di dichiarazioni di attributi) ha solo informazioni sulla dimensione di ciascuna variabile. – Jan

risposta

1

Non è portatile, mi dispiace. Il formato di va_list dipende dal compilatore/dalla piattaforma.

È necessario utilizzare va_arg() per accedere a va_list ed è necessario passare il tipo corretto dell'argomento a va_list.

Tuttavia, credo che sia possibile che se si passa un tipo di dimensione corretta a va_arg, ciò funzionerebbe. vale a dire. il tipo non è di solito rilevante, solo la sua dimensione. Tuttavia, anche questo non è garantito per funzionare su tutti i sistemi.

Penso che suggerirei di ricollegare il tuo progetto e vedere se riesci a trovare un design alternativo - ci sono più dettagli sul motivo per cui stai provando a fare questo che puoi condividere? Puoi invece passare la va_list ai callback?

Aggiornamento

Il motivo l'approccio di byte per byte non funziona è probabilmente abbastanza coinvolto. Per quanto riguarda lo standard C, il motivo per cui non funziona è perché non è consentito: è possibile utilizzare solo va_arg per accedere ai tipi identici che sono stati passati alla funzione.

Ma ho il sospetto che desideri sapere cosa sta succedendo dietro le quinte :)

La prima ragione è che quando si legge passare un "char" per una funzione, in realtà è promosso automaticamente in un int, così è archiviato in va_arg come int. Quindi, quando leggi un carattere, stai leggendo un valore di memoria "int", non un "carattere", quindi non stai leggendo un byte alla volta.

Un'ulteriore ragione ha a che fare con l'allineamento: su alcune architetture (un esempio sarebbe un processore ARM molto recente), un "doppio" deve essere allineato a un limite di 64 bit (oa volte anche di 128 bit). Cioè, per il valore del puntatore p, p% 16 (p modulo 16, in byte - es.128 bit) deve essere uguale a 0. Quindi, quando questi sono impacchettati sul va_arg, il compilatore probabilmente assicurerà che ogni doppio valore abbia spazio (padding) aggiunto in modo che si verifichi solo con l'allineamento corretto - ma non ne prendi conto quando leggi le voci un byte alla volta.

(Ci possono essere anche altre ragioni - Non sono intimamente familiare con le opere interne di va_arg.)

+0

Hmm ... passare la va_list a una funzione di callback speciale sarebbe un'opzione. Quindi, devo dichiararlo come va_list get_value (va_list l, char * buf) dove buf è la memoria su cui scrivere/copiare i dati su. Ma perché il mio secondo approccio non funziona: copia byte per byte in un buffer di memoria? – Jan

+0

Ho aggiornato la mia risposta per cercare di spiegare perché byte-per-byte non funziona. – JosephH

+0

Spero che le regole di allineamento su questa * ARM * piattaforma molto recente siano solo che un 'double' deve essere allineato su un limite di 8 * byte * (che sarebbe 64 bit). –

0

In questo caso va_args evitando sarebbe probabilmente più pulito perché si ancora finire legato a un determinato numero di argomenti nel codice al punto di chiamata. Mi piacerebbe passare con array di argomenti.

struct arg 
    { 
    void* vptr; 
    size_t len; 
    }; 

    void fn(struct arg* args, int nargs); 

Del resto, mi piacerebbe anche portare alla definizione di dati troppo, sia un int come detto in precedenza nei commenti o un puntatore ad una struct se si tratta di una più complessa definizione dei dati.

+0

Come procederesti per serializzare la definizione dei dati? –

+0

Voglio la creazione di oggetti di dati il ​​più semplice possibile: OBJ_DESC obj_desc [] = { sizeof (int), sizeof (char *), sizeof (doppio), sizeof (int *) }; ander dopo ho creare un oggetto con creare (obj_desc, 1, "Ciao Mondo", 566,2, &x); tutto alle spalle obj_desc è preso da obj_desc stessa. La creazione di un array di parametri prima sarebbe stato troppo alto. – Jan

+0

I Suppongo che la ragione per cui hai un sistema di tipo definito dai dati sia che la creazione/manipolazione debba essere basata sui dati - altrimenti perché non usare le semplici struct. Quindi, con quale frequenza stai creando oggetti in codice letterale rispetto a qualsiasi dato- il metodo utilizzato dal sistema? – Digikata

1

L'esecuzione di aritmetica su un va_list si trova all'estremità di non portatile. Si dovrebbe usare va_arg normalmente con un tipo della stessa dimensione dell'argomento e sarà probabilmente funzionare ovunque. Per motivi di "più vicino alla portabilità" è necessario utilizzare tipi di numeri interi senza segno per questo scopo (uint32_t ecc.).

+0

Quindi la callback di va_list-modifica è la mia unica opzione per ottenere un valore specifico dell'utente? – Jan

1

Un test non scientifico.

  • AIX 5.3 con GCC 4.2 - Lavori
  • HP-UX 11.23 con l'ACC 5,56 - non
  • Linux (SUSE 10.2) con GCC 4.1 - non lo fa
  • Solaris 10 con CC 5.9 - non

Tutti Linux, Solaris e HP-UX si sono lamentati della linea args += size;.

Altrimenti, è abbastanza ovvio che va_arg() è stato incluso per un motivo. Per esempio. sullo stack SPARC viene utilizzato in modo completamente diverso.

+0

Bel test! Sono stato sorpreso di vederlo anche fallito sotto linux gcc. Era in esecuzione su un sistema a 64 bit? – JosephH

+1

@JosephH: tutti i test erano a 32 bit, tranne Linux. IOW, la modalità di compilazione predefinita. In realtà ho provato per la prima volta su AIX (perché avevo già un terminale aperto) e pensavo che funzionasse davvero. Solo pochi attimi dopo per scoprire che AIX è un outlier anche in questo test ... – Dummy00001

0

Posso suggerire di sostituire gli argomenti variabili con una serie di puntatori void?

+0

beh, ma questa è un'altra soluzione con troppo overhead (non che ci avessi già pensato). Voglio solo passare i miei parametri a una chiamata di funzione, non sempre dichiarare un array da passare per ogni chiamata. L'uso della funzione dovrebbe essere il più semplice possibile. – Jan

0

Per C99, se Si può presupporre che tutti i tuoi argomenti siano tipi interi, ma potrebbero essere solo di larghezza o firma diversa che è possibile ottenere con una macro. Con ciò si può persino trasformare la lista degli argomenti in una matrice senza dolore per l'utente che la chiama.

#define myFunc(...) myRealFunc(NARGS(__VA_ARGS__), (uintmax_t const[]){ __VA_ARGS__}) 

dove

void myRealFunc(size_t len, uintmax_t const param*); 

e dove NARGS è una macro che ti dà la lunghezza del parametro __VA_ARGS__. Una cosa del genere può essere chiamato proprio come una funzione che riceverà va_list.

per spiegare un po 'quello che la macro fa:

  • si pone il numero di argomenti per il primo parametro della myRealFunc
  • crea un array temporaneo (letterale composto) del formato corretto e inizializza con gli argomenti (tutti cast uintmax_t)
  • se NARGS è fatto correttamente, questo valuta la vostra lista di argomenti solo in tempo di pre-elaborazione, come t okens e non in fase di esecuzione. Quindi i parametri non vengono eseguiti in tempo valutati più di una volta

Ora le tue funzioni di callback sarebbero chiamati da myRealFunc utilizzando qualsiasi magia che si desidera inserire lì. Dal momento che, quando chiamato, avrebbero bisogno di un parametro di un tipo intero diverso, il parametro uintmax_tparam[i] verrebbe reimpostato in quel tipo.

Problemi correlati