2013-05-23 10 views
6

Se voglio un programma per avere più formati di output di testo, avrei potuto fare qualcosa di simile:Esiste un codice printf "null" che non stampa nulla, usato per saltare un parametro?

const char *fmtDefault = "%u x %s ($%.2f each)\n"; 
const char *fmtMultiLine = "Qty: %3u\nItem: %s\nPrice per item: $%.2f\n\n"; 
const char *fmtCSV = "%u,%s,%.2f\n"; 

const char *fmt; 
switch (which_format) { 
    case 1: fmt = fmtMultiLine; break; 
    case 2: fmt = fmtCSV; break; 
    default: fmt = fmtDefault; 
} 

printf(fmt, quantity, item_description, price); 

Poiché il prezzo è specificato scorso, potrei anche aggiungere uno che non elencare i prezzi:

const char *fmtNoPrices = "%u x %s\n"; 

Ma cosa succede se invece voglio omettere la quantità? Se ho fatto questo:

const char *fmtNoQuantity = "The price of %s is $%.2f each.\n"; 

quindi si verificherà un comportamento non definito (molto probabilmente un segfault) piuttosto che quello che voglio. Questo perché tratterà il primo parametro come puntatore a una stringa, anche se in realtà è un int unsigned. Molto probabilmente questo int unsigned punta a qualcosa di diverso da dati stringa validi o (molto più probabilmente, soprattutto se non stai comprando centinaia di milioni dello stesso articolo), una posizione di memoria non valida, causando un errore di segmentazione.

Quello che voglio sapere è se c'è un codice posso mettere da qualche parte (%Z in questo esempio) per dirgli di saltare quel parametro, in questo modo:

const char *fmtNoQuantity = "%ZThe price of %s is $%.2f each."; 
+1

scanf() può utilizzare gli asterischi, ma IIRC printf() non può. Ho anche provato a usare '.0' specificatori di precisione, ma questo sembra funzionare solo su stringhe ('% .0s' non visualizzerà nulla, ma probabilmente ancora dereferenzia il puntatore se non è null) – Medinoc

+0

Dovresti usare insiemi separati di chiamate con elenchi di argomenti separati, penso. Fare altrimenti rende l'internazionalizzazione (I18N) molto più difficile. –

risposta

4

Per %s valori, c'è il codice di printf “null”(): %.0s.

Si potrebbe giungere ad una soluzione generale tramite:

Quando possibile, ri-organizzare in modo che i valori sono non-%s scorso, e poi sotto specificare la stringa di formato.

Il mio preferito è avere 3 chiamate printf() separate, una per ogni valore utilizzando il proprio formato. Quando il valore non è necessario, fornire semplicemente una stringa di formato senza specificatori.

const char * Format1q = ""; 
const char * Format1id = "The price of %s"; 
const char * Format1p = " is $%.2f each.\n"; 
... 
printf(Format1q, quantity); 
printf(Format1id, item_description); 
printf(Format1p, price); 

soluzioni strane:

Per gli altri valori che sono della stessa dimensione si potrebbe tentare il comportamento non definito di utilizzare anche %.0s. (ha funzionato con alcuni esempi in gcc 4.5.3, chissà in altri compilatori o in futuro.)

Per altri valori che sono N x della stessa dimensione di una dimensione puntatore, è possibile tentare il comportamento indefinito utilizzando anche %.0s N volte. (ha funzionato con alcuni esempi in gcc 4.5.3, chissà in altri compilatori o in futuro.)

+0

Attenzione all'internazionalizzazione (I18N). I frammenti di frase che si combinano bene in inglese possono essere un disastro nella traduzione. –

+0

Accetto sui problemi di internazionalizzazione. Inoltre, dato l'ordine fisso dei valori, è possibile che sia meglio riorganizzare i valori di una cultura diversa e che è al di là delle soluzioni presentate. – chux

+1

Le notazioni di '% 1 $ s' possono riordinare (e persino riutilizzare) i parametri, ma devi usare ogni parametro da 1..N per essere sicuro (perché i diversi tipi hanno bisogno di' printf() 'et al per usare quantità diverse di spazio sullo stack Ad esempio, un 'double' ha bisogno di 8 byte, dove' int' di solito richiede solo 4 byte. Quindi, se hai uno 'int' e un' double' nello stack, 'printf()' ha da dire come avanzare la 'va_list' che sta alla base di tutte queste cose. Questo non impedisce agli attaccanti di usare attacchi con stringhe di formato con numeri mancanti nei numeri' n $ ', ma non sono così esigenti finché funziona per loro. –

1

realtà ho capito questo da solo mentre sto cercando qualcosa per la mia domanda. È possibile anteporre un numero di parametro, seguito da $ al codice di formato, dopo lo %. Quindi sarebbe come questo:

const char *fmtNoQuantity = "The price of %2$s is $%3$.2f each."; 

Cioè, la stringa sarebbe utilizzare il secondo parametro e il galleggiante sarebbe utilizzare il terzo parametro. Nota, tuttavia, che questa è un'estensione POSIX, non una funzione standard di C.

Un metodo migliore sarebbe probabilmente quello di definire una funzione di stampa personalizzata. Qualcosa di simile a questo:


typedef enum {fmtDefault, fmtMultiLine, fmtCSV, fmtNoPrices, fmtNoQuantity} fmt_id; 

void print_record(fmt_id fmt, unsigned int qty, const char *item, float price) 
{ 
    switch (fmt) { 
    case fmtMultiLine: 
     printf("Qty: %3u\n", qty); 
     printf("Item: %s\n", item); 
     printf("Price per item: $%.2f\n\n", price); 
     break; 
    case fmtCSV: 
     printf("%u,%s,%.2f\n", qty, item, price); 
     break; 
    case fmtNoPrices: 
     printf("%u x %s\n", qty, item); 
     break; 
    case fmtNoQuantity: 
     printf("The price of %s is $%.2f each.\n", item, price); 
     break; 
    default: 
     printf("%u x %s ($%.2f each)\n", qty, item, price); 
     break; 
    } 
} 
+2

Un problema con questa estensione è che fare riferimento a un parametro senza riferirsi a tutti quelli che precedono provoca un comportamento indefinito perché la funzione 'printf' non conosce la dimensione di questi parametri. E non c'è il formato "salta questo parametro" che io conosca, quindi questo non può nemmeno essere usato per aggirare quel problema ... – Medinoc