2009-10-21 8 views

risposta

57

Spesso la tecnica descritta in altre risposte è incapsulata in una macro per renderla più facile agli occhi. Qualcosa di simile:

#define COUNT_OF(arr) (sizeof(arr)/sizeof(0[arr])) 

Si noti che la macro precedente utilizza un piccolo trucco di mettere il nome della matrice nell'operatore Index ('[]') al posto del 0 - questo è fatto nel caso in cui la macro viene erroneamente usato in C++ codice con un articolo che sovraccarica operator[](). Il compilatore si lamenterà invece di dare un risultato negativo.

Tuttavia, si noti anche che se si passa un puntatore anziché un array, la macro darà un risultato non valido: questo è uno dei problemi principali nell'utilizzo di questa tecnica.

Di recente ho iniziato ad utilizzare una versione più complessa che ho rubato dal codice di base di Google Chrome:

#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x]))/((size_t)(!(sizeof(x) % sizeof(0[x]))))) 

In questa versione se un puntatore è erroneamente passata come argomento, il compilatore si lamenta in alcuni casi - in particolare se la dimensione del puntatore non è equamente divisibile in base alla dimensione dell'oggetto a cui punta il puntatore. In questa situazione una divisione per zero causerà l'errore del compilatore. In realtà almeno un compilatore che ho usato fornisce un avvertimento invece di un errore - non sono sicuro di cosa generi per l'espressione che ha una divisione per zero in esso.

Quella macro non chiude la porta sul usando erroneamente, ma si tratta il più vicino che abbia mai visto in rettilineo C.

Se si desidera una soluzione ancora più sicuro per quando si lavora in C++, dai un'occhiata a Compile time sizeof_array without using a macro che descrive un metodo basato su template piuttosto complesso che Microsoft usa in winnt.h.

+3

+1 per l'interessante versione in cromo. –

+1

+1 sia per la versione Chromium sia per il piccolo trucco dell'uso di [[x]]. –

+0

Puoi spiegare perché la sintassi '0 [arr]' funziona come 'arr [0]'? – meguli

5
sizeof array/sizeof array[0] 
+11

Si dovrebbe spiegare che questo funziona solo per matrici di dimensioni fisse in cui la dimensione è nota al momento della compilazione. –

+1

È possibile abbreviare ulteriormente utilizzando '* array' :-) –

+0

L'espressione funziona anche per VLA (array di lunghezza variabile) in cui la dimensione viene determinata in fase di esecuzione. Non funziona per gli array allocati dinamicamente (tramite 'malloc()' etc). Fallisce anche sugli "array" negli elenchi dei parametri delle funzioni, ma non sono comunque array. –

14

No, non c'è.

Per gli array di dimensioni costanti è possibile utilizzare il trucco comune Andrew accennato, sizeof(array)/sizeof(array[0]) - ma questo funziona solo nel campo di applicazione della matrice è stato dichiarato nel
sizeof(array) ti dà la dimensione di tutta la gamma, mentre sizeof(array[0]) ti dà la dimensione. del primo elemento.
Vedere Michaels answer su come eseguire il wrapping in una macro.

Per gli array allocati dinamicamente, è possibile tenere traccia delle dimensioni in un tipo integrale o, se possibile, renderlo terminato a 0 (ad esempio, allocare 1 elemento in più e impostare l'ultimo elemento su 0).

+11

Si noti che 'sizeof (array)/sizeof (array [0])' funziona solo nello stesso ambito in cui è stato dichiarato l'array. Non possiamo passare 'array' a una funzione e aspettarci che quel codice funzioni. –

+0

Ovviamente, grazie. Aggiunto questo. –

+0

Non solo funziona con array di dimensioni costanti, ma anche con array di lunghezza variabile in C99. In tal caso, tuttavia, 'sizeof' viene valutato in fase di esecuzione. –

0

Se si dispone di un oggetto a di tipo di matrice, il numero di elementi nell'array può essere espresso come sizeof a/sizeof *a. Se hai permesso al tuo oggetto array di decadere al tipo di puntatore (o aveva solo un oggetto puntatore all'inizio), in generale non c'è modo di determinare il numero di elementi nella matrice.

2

La semplice risposta, ovviamente, è no. Ma la risposta pratica è "Devo saperlo comunque", quindi discutiamo dei metodi per aggirare questo problema.

Un modo per farla franca per un po ', come accennato circa già un milione di volte, è con sizeof():

int i[] = {0, 1, 2}; 
... 
size_t i_len = sizeof(i)/sizeof(i[0]); 

Questo funziona, fino a quando cerchiamo di passare i a una funzione, o fare un puntatore a i. Quindi che dire di soluzioni più generali?

La soluzione generale accettata è quella di passare la lunghezza dell'array ad una funzione insieme all'array. Lo vediamo molto nella libreria standard:

void *memcpy(void *s1, void *s2, size_t n); 

copierà n byte da s1 a s2, permettendoci di usare n per garantire che i nostri tamponi mai troppo pieno. Questa è una buona strategia - ha un sovraccarico basso, e in realtà genera un codice efficiente (confronta con strcpy(), che deve controllare la fine della stringa e non ha modo di "conoscere" quante iterazioni deve fare, e povero confuso strncpy(), che deve controllare entrambi - sia può può essere più lento, e uno potrebbe essere accelerato utilizzando memcpy() se hai già calcolato la lunghezza della stringa per qualche motivo).

Un altro approccio è incapsulare il codice in un struct. L'hack comune è questo:

typedef struct _arr { 
    size_t len; 
    int arr[0]; 
} arr; 

Se vogliamo una serie di lunghezza di 5, facciamo questo:

arr *a = malloc(sizeof(*a) + sizeof(int) * 5); 
a->len = 5; 

Tuttavia, questo è un hack che è solo moderatamente ben definito (C99 consente di utilizzare int arr[]) ed è piuttosto laborioso. Un modo "meglio definiti" per fare questo è:

typedef struct _arr { 
    size_t len; 
    int *arr; 
} arr; 

Ma poi le nostre allocazioni (e deallocazioni) diventano molto più complicato. Il vantaggio di uno di questi approcci è, naturalmente, che ora gli array che realizzi porteranno con sé le loro lunghezze. È leggermente meno efficiente in termini di memoria, ma è abbastanza sicuro. Se si sceglie uno di questi percorsi, assicurarsi di scrivere le funzioni di supporto in modo da non dover allocare manualmente e deallocare (e lavorare con) queste strutture.

+3

"Struct hack" è un hack, ma non c'è motivo di renderlo più hacker di quanto dovrebbe essere. Le matrici di dimensione 0 erano (e sono) sempre esplicitamente illegali in C. La dichiarazione di struct corretta per "struct hack" usa array di dimensione 1. La dimensione di 'malloc' è tradizionalmente calcolata come' offsetof (arr, arr) + n * sizeof (int) '(quindi non importa quale dimensione è usata nella dichiarazione dell'array). – AnT

+0

Sono illegali, ma molti compilatori C più vecchi li hanno supportati (dalla pigrizia, senza dubbio), e ho visto 'arr [0]' molte volte (con i cring). Non ho mai visto la dimensione calcolata con 'offsetof()' anche se questo è un buon modo per assicurarlo. E non sono d'accordo. Hacks dovrebbe urlare a squarciagola "Questo è un hack! Si prega di migliorare questo codice quando ne avete la possibilità!" –

+0

Bene, direi che la bellezza di "struct hack" è che non viola alcun vincolo. Il programma deve essere compilato correttamente. "Struct hack" faceva sempre affidamento sulla "definizione" pratica del comportamento teorico non definito. E, come tutti sappiamo, la vita spesso ci fa fare affidamento su questo. Ecco perché amiamo e adoriamo quella "struct hack". Ma la versione '[0]' rende un drastico cambiamento qualitativo a tutto ciò. Introduce una violazione del vincolo, che porta al possibile errore di compilazione. È difficile amare qualcosa che non viene nemmeno compilato. (Sì, so che alcuni compilatori consentivano e ammettevano ancora '[0]' array.) – AnT

3

Il numero di elementi di un array x può essere ottenuta tramite:

sizeof(x)/sizeof(x[0]) 

devi essere consapevole che array, quando viene passato alle funzioni, sono degradati in puntatori che fanno non trasportare le informazioni di dimensione . In realtà, le informazioni sulle dimensioni sono mai disponibili per il runtime poiché sono calcolate al momento della compilazione, ma è possibile agire come se fosse disponibile dove l'array è visibile (ad esempio, dove non è stato degradato).

Quando passo array a una funzione che devo trattare come array, sempre garantirà due argomenti sono passati:

  • la lunghezza della matrice; e
  • il puntatore all'array.

Così, mentre l'array può essere trattata come un serie dove è dichiarato, è trattata come una dimensione e puntatore ovunque.

Tendo ad avere codice come:

#define countof(x) (sizeof(x)/sizeof(x[0])) 
: : : 
int numbers[10]; 
a = fn (countof(numbers),numbers); 

poi fn() avrà le informazioni sulle dimensioni a sua disposizione.

Un altro trucco che ho usato in passato (un po 'disordinato secondo me, ma lo darò qui per completezza) è avere una matrice di unione e rendere il primo elemento la lunghezza, qualcosa come:

typedef union { 
    int len; 
    float number; 
} tNumber; 
tNumber number[10]; 
: : : 
number[0].len = 5; 
a = fn (number); 

quindi fn() può accedere alla lunghezza e a tutti gli elementi e non è necessario preoccuparsi della dicotomia matrice/puntatore.

Questo ha il vantaggio aggiunto di consentire alla lunghezza di variare (ovvero il numero di elementi in uso, non il numero di unità allocate). Ma io tendo a non usarlo più poiché considero migliore la versione dell'array a due argomenti (dimensioni e dati).

+0

Quel trucco 'union' è pulito, ma preferirei usare la 'struct' hack (o una versione non-hack della stessa idea) . –

Problemi correlati