Determinare in modo affidabile il numero di elementi in una matrice

Ogni programmatore C può determinare il numero di elementi in un array con questa macro nota:

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

Ecco 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 anziché 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 a come impedire al programmatore di passare un puntatore per errore e ho trovato una soluzione:

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

Questo funziona perché un puntatore a un array 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 void ricorsivo, cioè un puntatore vuoto che punta a se stesso. Posso conviverci.)

Passare accidentalmente un puntatore invece di un array ora triggers un errore in fase di runtime:

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

Bello! Ora ho un paio di domande:

  1. Il mio uso di assert come operando sinistro dell’espressione virgola è valido come standard C? Cioè, lo standard mi permette di usare assert come espressione? Scusa se questa è una domanda stupida 🙂

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

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

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

Il mio uso di asserire come operando sinistro dell’espressione virgola è valido come standard C? Cioè, lo standard mi permette di usare affermare come espressione?

Sì, è valido poiché l’operando di sinistra dell’operatore virgola può essere un’espressione di tipo void . E la funzione assert è void come tipo di ritorno.

Il mio compilatore C pensa che int b [NUM_ELEMS (a)]; è un VLA. Un modo per convincerlo del contrario?

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

EDIT1: aggiungi l’aggiornamento di seguito.

Ho un’altra versione della tua macro che funziona al momento della 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 con l’inizializzatore per oggetti con durata di archiviazione statica. E funziona anche correttamente con il tuo esempio di int b[NUM_ELEMS(a)]

EDIT2:

per rispondere al commento @DanielFischer . La macro sopra funziona con gcc senza -pedantic solo perché gcc accetta:

 (void *) &arr == arr 

come un’espressione costante intera, mentre considera

 (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.

Per quanto ne NUM_ELEM 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 è ansible avere una singola macro che funzioni in tutti i casi.

Edit3:

Penso che valga la pena ricordare che il kernel di Linux ha una macro ARRAY_SIZE (in include/linux/kernel.h ) che implementa tale controllo quando viene eseguito sparse (il correttore dell’analisi statica del kernel).

La loro soluzione non è portatile e utilizza due estensioni GNU:

  • operatore di tipo
  • __builtin_types_compatible_p builtin function

Fondamentalmente sembra qualcosa del genere:

 #define NUM_ELEMS(arr) \ (sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));}) \ + sizeof (arr) / sizeof (*(arr))) 
  1. Sì. L’espressione sinistra di un operatore virgola viene sempre valutata come espressione void (C99 6.5.17 # 2). Dal momento che assert() è un’espressione void, nessun problema all’inizio.
  2. Può essere. Mentre il preprocessore C non conosce i tipi e i cast e non può confrontare gli indirizzi, è ansible utilizzare lo stesso trucco utilizzato per valutare sizeof () in fase di compilazione, ad esempio 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 cast (dei tipi di puntatore) non sono espressioni costanti intere.
  4. Probabilmente no (niente di nuovo sotto il Sole in questi giorni). Un numero indeterminato di vergini di sesso indeterminato 🙂