2012-10-08 12 views
15

Ogni C programmatore può determinare il numero di elementi in una matrice con questa macro noto:affidabile determinare il numero di elementi in un array

#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a]) 

Qui è un tipico caso d'uso:

int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19}; 
printf("%lu\n", NUM_ELEMS(numbers));   // 8, as expected 

Tuttavia, nulla impedisce al programmatore di passare accidentalmente un puntatore invece di un array:

int * pointer = numbers; 
printf("%lu\n", NUM_ELEMS(pointer)); 

Sul mio sistema, questo stampa 2, perché apparentemente, un puntatore è due volte più grande di un intero. Ho pensato come impedire il programmatore dal passaggio di un puntatore per errore, e ho trovato una soluzione:

#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a])) 

Ciò funziona perché un puntatore a una matrice ha lo stesso valore di un puntatore al suo primo elemento. Se invece si passa un puntatore, il puntatore verrà confrontato con un puntatore a se stesso, che è quasi sempre falso. (L'unica eccezione è un puntatore nullo ricorsivo, cioè, un puntatore vuoto che punta a se stesso posso vivere con questo..)

accidentalmente passando un puntatore invece di un array ora genera un errore in fase di esecuzione:

Assertion `(void*)&(pointer) == (void*)(pointer)' failed. 

Bello! Ora ho un paio di domande:

  1. È il mio utilizzo della assert come operando sinistro dello Standard in vigore espressione virgola C? Cioè, lo standard mi consente di usare assert come espressione? Scusa se questa è una domanda stupida :)

  2. Il controllo può essere eseguito in qualche modo in fase di compilazione?

  3. Il compilatore My C ritiene che int b[NUM_ELEMS(a)]; sia un VLA. Un modo per convincerlo del contrario?

  4. Sono il primo a pensarci? Se è così, quante vergini posso aspettarmi di aspettarmi in paradiso? :)

+1

Come per la parte (4), piuttosto sicuro che sia * non * 72. Penso che t hat è un valore riservato per qualcos'altro ... –

+0

Non intendi 'sizeof a [0]'? –

+0

Ogni programmatore reale sa che tenere traccia delle dimensioni degli array è il loro problema, non i compilatori e che i trucchi per "calcolarlo" sono di utilità strettamente limitata perché tali informazioni vengono conservate solo nell'ambito.Se vuoi che il compilatore si occupi di questo per te, usa un linguaggio più intelligente. Voglio dire, devi solo andare in C++ e usare 'std :: vector' o (con C++ 11)' std :: array', quindi non è un grosso cambiamento. – dmckee

risposta

9

Is my usage of assert as the left operand of the comma expression valid standard C? That is, does the standard allow me to use assert as an expression?

Sì, è valido come operando sinistro dell'operatore virgola può essere un'espressione di tipo void.E la funzione assert ha void come tipo di ritorno.

My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?

Si ritiene così perché il risultato di un'espressione virgola è mai un'espressione costante (e..g, 1, 2 non è un'espressione costante).

EDIT1: aggiungere l'aggiornamento di seguito.

ho un'altra versione della macro che funziona in fase di compilazione:

#define NUM_ELEMS(arr)             \ 
(sizeof (struct {int not_an_array:((void*)&(arr) == &(arr)[0]);}) * 0 \ 
    + sizeof (arr)/sizeof (*(arr))) 

e che sembra funzionare anche anche con inizializzatore per oggetto con la durata di archiviazione statica. Ed anche funzioni correttamente con il tuo esempio di int b[NUM_ELEMS(a)]

EDIT2:

per affrontare @DanielFischer commento. La macro precedente funziona con gccsenza-pedantic solo perché gcc accetta:

(void *) &arr == arr 

come intero espressione costante, mentre essa ritiene

(void *) &ptr == ptr 

non è un'espressione costante intera. Secondo C non sono entrambe espressioni con numero intero costante e con -pedantic, gcc emette correttamente una diagnostica in entrambi i casi.

A mia conoscenza non esiste un modo 100% portatile per scrivere questa macro NUM_ELEM. C ha regole più flessibili con espressioni costanti di inizializzatore (si veda 6.6p7 in C99) che potrebbero essere sfruttate per scrivere questa macro (ad esempio con sizeof e letterali composti) ma in C-block non richiede che gli inizializzatori siano espressioni costanti non è possibile avere una singola macro che funzioni in tutti i casi.

Edit3:

Credo che la pena ricordare che il kernel Linux ha un ARRAY_SIZE macro (in include/linux/kernel.h) che implementa tale controllo quando viene eseguito sparse (kernel statica analisi checker).

La loro soluzione non è portatile e fare uso di due estensioni GNU:

  • typeof operatore
  • __builtin_types_compatible_p funzione built

In pratica sembra che qualcosa di simile:

#define NUM_ELEMS(arr) \ 
(sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));}) \ 
    + sizeof (arr)/sizeof (*(arr))) 
+0

Impressionante! Ti concederò metà delle vergini, se per te va bene. – fredoverflow

+0

Sfortunatamente, con '-pedantic-errors', ottengo' error: bit-field 'not_an_array' width non è un'espressione costante in interi [-pedantic] 'da gcc, clang dà un errore di default :( –

+0

@DanielFischer vedi il mio secondo modifica che indirizza il tuo commento – ouah

3
  1. Sì. L'espressione sinistra di un operatore virgola viene sempre valutata come espressione void (C99 6.5.17 # 2). Poiché assert() è un'espressione vuota, nessun problema per cominciare.
  2. Forse. Sebbene il preprocessore C non conosca i tipi e i cast e non possa confrontare gli indirizzi, puoi usare lo stesso trucco utilizzato per valutare sizeof() al momento della compilazione, ad es. dichiarando un array la cui dimensione è un'espressione booleana. Quando 0 è una violazione del vincolo e deve essere emessa una diagnostica. L'ho provato qui, ma finora non hanno avuto successo ... forse la risposta in realtà è "no".
  3. No. I valori (di tipi puntatore) non sono espressioni costanti intero.
  4. Probabilmente no (niente di nuovo sotto il Sole in questi giorni). Un numero indeterminato di vergini di sesso indeterminato :-)
Problemi correlati