Come posso determinare la dimensione del mio array in C?


Risposte:


1261

Sintesi:

int a[17];
size_t n = sizeof(a)/sizeof(a[0]);

Risposta completa:

Per determinare la dimensione dell'array in byte, è possibile utilizzare l' sizeof operatore:

int a[17];
size_t n = sizeof(a);

Sul mio computer, gli ints sono lunghi 4 byte, quindi n è 68.

Per determinare il numero di elementi nell'array, possiamo dividere la dimensione totale dell'array per la dimensione dell'elemento dell'array. Puoi farlo con il tipo, in questo modo:

int a[17];
size_t n = sizeof(a) / sizeof(int);

e ottieni la risposta corretta (68/4 = 17), ma se il tipo di amodifica avresti un brutto bug se ti dimenticassi di cambiare ilsizeof(int) anche quella.

Quindi il divisore preferito è sizeof(a[0])o l'equivalente sizeof(*a), la dimensione del primo elemento dell'array.

int a[17];
size_t n = sizeof(a) / sizeof(a[0]);

Un altro vantaggio è che ora puoi facilmente parametrizzare il nome dell'array in una macro e ottenere:

#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

int a[17];
size_t n = NELEMS(a);

6
Il codice generato sarà identico, poiché il compilatore conosce il tipo di * int_arr al momento della compilazione (e quindi il valore di sizeof (* int_arr)). Sarà una costante e il compilatore può ottimizzare di conseguenza.
Mark Harrison,

10
Dovrebbe essere il caso di tutti i compilatori, poiché i risultati di sizeof sono definiti come costante di compilazione.
Mark Harrison,

451
Importante : non smettere di leggere qui, leggi la prossima risposta! Funziona solo con array in pila , ad esempio se stai usando malloc () o accedi a un parametro di funzione, sei sfortunato. Vedi sotto.
Markus,

7
Per la programmazione dell'API di Windows in C o C ++, esiste il ARRAYSIZEmakro definito in WinNT.h(che viene richiamato da altre intestazioni). Quindi gli utenti di WinAPI non devono definire il proprio makro.
Lumi,

17
@Markus funziona per qualsiasi variabile che ha un tipo di array; questo non deve essere "in pila". Es static int a[20];. Ma il tuo commento è utile per i lettori che potrebbero non capire la differenza tra un array e un puntatore.
MM

808

La sizeofstrada è la strada giusta se hai a che fare con array non ricevuti come parametri. Un array inviato come parametro a una funzione viene trattato come un puntatore, quindi sizeofrestituirà la dimensione del puntatore, anziché quella dell'array.

Pertanto, all'interno delle funzioni questo metodo non funziona. Invece, passa sempre un parametro aggiuntivo che size_t sizeindica il numero di elementi nell'array.

Test:

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

void printSizeOf(int intArray[]);
void printLength(int intArray[]);

int main(int argc, char* argv[])
{
    int array[] = { 0, 1, 2, 3, 4, 5, 6 };

    printf("sizeof of array: %d\n", (int) sizeof(array));
    printSizeOf(array);

    printf("Length of array: %d\n", (int)( sizeof(array) / sizeof(array[0]) ));
    printLength(array);
}

void printSizeOf(int intArray[])
{
    printf("sizeof of parameter: %d\n", (int) sizeof(intArray));
}

void printLength(int intArray[])
{
    printf("Length of parameter: %d\n", (int)( sizeof(intArray) / sizeof(intArray[0]) ));
}

Output (in un sistema operativo Linux a 64 bit):

sizeof of array: 28
sizeof of parameter: 8
Length of array: 7
Length of parameter: 2

Output (in un sistema operativo Windows a 32 bit):

sizeof of array: 28
sizeof of parameter: 4
Length of array: 7
Length of parameter: 1

10
perché length of parameter:2se viene passato solo un puntatore al primo elemento dell'array?
Bbvarghe,

16
@Bbvarghe Questo perché i puntatori nei sistemi a 64 bit sono 8 byte (sizeof (intArray)), ma gli ints sono ancora (di solito) lunghi 4 byte (sizeof (intArray [0])).
Elideb,

13
@Pacerier: non esiste un codice corretto: la solita soluzione è passare la lunghezza insieme all'array come argomento separato.
Jean Hominal,

10
Aspetta, quindi non c'è modo di accedere all'array direttamente da un puntatore e vedere le sue dimensioni? Nuovo in C qui.
sudo,

7
@ Michael Trouw: è possibile utilizzare la sintassi all'operatore se è ti fa sentire meglio: (sizeof array / sizeof *array).
Chqrlie,

134

Vale la pena notare che sizeofnon aiuta quando si ha a che fare con un valore di array che è decaduto in un puntatore: anche se punta all'inizio di un array, per il compilatore è lo stesso di un puntatore a un singolo elemento di quell'array . Un puntatore non "ricorda" nient'altro sull'array che è stato utilizzato per inizializzarlo.

int a[10];
int* p = a;

assert(sizeof(a) / sizeof(a[0]) == 10);
assert(sizeof(p) == sizeof(int*));
assert(sizeof(*p) == sizeof(int));

1
@ Magnus: lo standard definisce sizeof come il numero di byte nell'oggetto e sizeof (char) è sempre uno. Il numero di bit in un byte è specifico dell'implementazione. Modifica: sezione ANSI C ++ standard 5.3.3 Sizeof: "L'operatore sizeof fornisce il numero di byte nella rappresentazione dell'oggetto del suo operando. [...] sizeof (char), sizeof (char char) e sizeof (char unsigned) sono 1; il risultato della dimensione della dimensione applicata a qualsiasi altro tipo fondamentale è definito dall'implementazione. "
Skizz,

Sezione 1.6 Il modello di memoria C ++: "L'unità di memoria fondamentale nel modello di memoria C ++ è il byte. Un byte è almeno abbastanza grande da contenere qualsiasi membro del set di caratteri di esecuzione di base ed è composto da una sequenza contigua di bit, il numero di cui è definita dall'implementazione. "
Skizz,

2
Ricordo che il CRAY aveva C con char32 bit. Tutto ciò che dice lo standard è che possono essere rappresentati valori interi da 0 a 127 e il suo intervallo è almeno -127 a 127 (carattere è firmato) o da 0 a 255 (carattere non firmato).
vonbrand,

5
Questa è una risposta eccellente. Voglio commentare che tutte le asserzioni di cui sopra sono valutate come VERE.
Javad,

49

La dimensione del "trucco" è il modo migliore che conosco, con una piccola ma (per me, essendo questa una grande pipì per animali domestici) un cambiamento importante nell'uso della parentesi.

Come chiarisce la voce di Wikipedia, C sizeofnon è una funzione; è un operatore . Pertanto, non richiede parentesi attorno al suo argomento, a meno che l'argomento non sia un nome di tipo. Questo è facile da ricordare, dal momento che fa apparire l'argomento come un'espressione cast, che usa anche la parentesi.

Quindi: se si dispone di quanto segue:

int myArray[10];

Puoi trovare il numero di elementi con codice come questo:

size_t n = sizeof myArray / sizeof *myArray;

Questo, secondo me, è molto più semplice dell'alternativa tra parentesi. Prediligo anche l'uso dell'asterisco nella parte destra della divisione, poiché è più conciso dell'indicizzazione.

Naturalmente, anche questo è tutto in fase di compilazione, quindi non è necessario preoccuparsi della divisione che influisce sull'esecuzione del programma. Quindi usa questo modulo ovunque tu sia.

È sempre meglio usare sizeof su un oggetto reale quando ne hai uno, piuttosto che su un tipo, da allora non devi preoccuparti di fare un errore e dichiarare il tipo sbagliato.

Ad esempio, supponiamo di avere una funzione che emette alcuni dati come un flusso di byte, ad esempio attraverso una rete. Chiamiamo la funzione send()e facciamo in modo che argomenti come puntatore all'oggetto da inviare e il numero di byte nell'oggetto. Quindi, il prototipo diventa:

void send(const void *object, size_t size);

E quindi devi inviare un numero intero, quindi codificalo in questo modo:

int foo = 4711;
send(&foo, sizeof (int));

Ora, hai introdotto un modo sottile di spararti al piede, specificando il tipo di fooin due punti. Se uno cambia ma l'altro no, il codice si interrompe. Quindi, fallo sempre così:

send(&foo, sizeof foo);

Adesso sei protetto. Certo, duplicate il nome della variabile, ma ciò ha un'alta probabilità di rompere in un modo che il compilatore può rilevare, se lo cambiate.


A proposito, sono istruzioni identiche a livello di processore? Non sizeof(int)richiede istruzioni minore rispetto sizeof(foo)?
Pacerier,

@Pacerier: no, sono identici. Pensa int x = 1+1;contro int x = (1+1);. Qui, le parentesi sono puramente assolutamente estetiche.
quetzalcoatl,

@Aidiakapi Non è vero, considera i VLA C99.
Rilassati il

@unwind Grazie, sono corretto. Per correggere il mio commento, sizeofsarà sempre costante in C ++ e C89. Con gli array a lunghezza variabile di C99, può essere valutato in fase di esecuzione.
Aidiakapi,

2
sizeofpuò essere un operatore ma dovrebbe essere trattato come una funzione secondo Linus Torvalds. Sono d'accordo. Leggi qui il suo razionale: lkml.org/lkml/2012/7/11/103
Dr. Person Person II

37
int size = (&arr)[1] - arr;

Dai un'occhiata a questo link per una spiegazione


7
Piccolo nitpick: il risultato della sottrazione del puntatore ha tipo ptrdiff_t. (In genere sul sistema a 64 bit, questo sarà un tipo più grande di int). Anche se si modifica inta ptrdiff_tquesto codice, ha ancora un bug se arroccupa più della metà dello spazio di indirizzamento.
MM

2
@MM Un altro piccolo pignolo: a seconda dell'architettura del tuo sistema, lo spazio degli indirizzi non è grande quanto la dimensione del puntatore sulla maggior parte dei sistemi. Windows, ad esempio, limita lo spazio degli indirizzi per le applicazioni a 64 bit a 8 TB o 44 bit. Quindi, anche se hai un array più grande della metà dello spazio degli indirizzi di 4,1 TB, ad esempio, non sarà un bug. Solo se lo spazio degli indirizzi supera i 63 bit su quei sistemi, è possibile riscontrare persino questo tipo di bug. In generale, non preoccuparti.
Aidiakapi,

1
@Aidiakapi su Linux x86 a 32 bit o su Windows con /3Gopzione di suddivisione utente / kernel 3G / 1G, che consente di avere dimensioni di array fino al 75% delle dimensioni dello spazio di indirizzi.
Ruslan,

1
Considera foo buf1[80]; foo buf2[sizeof buf1/sizeof buf1[0]]; foo buf3[(&buf1)[1] - buf1];come variabili globali. buf3[]la dichiarazione non riesce poiché (&buf1)[1] - buf1non è una costante.
chux - Ripristina Monica

2
Si tratta di un comportamento tecnicamente indefinito poiché lo standard non consente esplicitamente la dereferenziazione oltre la fine di un array (anche se non si tenta di leggere il valore memorizzato)
MM


21

Se conosci il tipo di dati dell'array, puoi usare qualcosa del tipo:

int arr[] = {23, 12, 423, 43, 21, 43, 65, 76, 22};

int noofele = sizeof(arr)/sizeof(int);

Oppure, se non conosci il tipo di dati dell'array, puoi usare qualcosa come:

noofele = sizeof(arr)/sizeof(arr[0]);

Nota: questa cosa funziona solo se l'array non è definito in fase di esecuzione (come malloc) e l'array non viene passato in una funzione. In entrambi i casi, arr(nome della matrice) è un puntatore.


4
int noofele = sizeof(arr)/sizeof(int);è solo a metà strada migliore della codifica int noofele = 9;. L'utilizzo sizeof(arr)mantiene la flessibilità in caso di modifica delle dimensioni dell'array. Tuttavia ha sizeof(int)bisogno di un aggiornamento se il tipo di arr[]modifica. Meglio usare sizeof(arr)/sizeof(arr[0])anche se il tipo è ben noto. Non è chiaro il motivo intper cui usando for noofelevs. size_t, il tipo restituito da sizeof().
chux - Ripristina Monica

19

La macro utilizzata ARRAYELEMENTCOUNT(x)da tutti viene valutata in modo errato . Questo, realisticamente, è solo una questione delicata, perché non è possibile avere espressioni che generano un tipo "array".

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x[0]))

ARRAYELEMENTCOUNT(p + 1);

In realtà valuta come:

(sizeof (p + 1) / sizeof (p + 1[0]));

Mentre

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x)[0])

ARRAYELEMENTCOUNT(p + 1);

Valuta correttamente a:

(sizeof (p + 1) / sizeof (p + 1)[0]);

Questo non ha molto a che fare con la dimensione delle matrici esplicitamente. Ho appena notato molti errori nel non osservare veramente come funziona il preprocessore C. Avvolgi sempre il parametro macro, in cui non potrebbe essere coinvolta un'espressione.


Questo è corretto; il mio esempio è stato negativo. Ma questo è esattamente ciò che dovrebbe accadere. Come ho già dettop + 1 finirà come un tipo di puntatore e invaliderà l'intera macro (proprio come se si tentasse di utilizzare la macro in una funzione con un parametro puntatore).

Alla fine della giornata, in questo caso particolare , la colpa non ha molta importanza (quindi sto solo sprecando il tempo di tutti; huzzah!), Perché non hai espressioni con un tipo di "array". Ma in realtà il punto sui sottotitoli di valutazione del preprocessore penso sia importante.


2
Grazie per la spiegazione. La versione originale provoca un errore di compilazione. Clang riporta "il valore sottoscritto non è un array, un puntatore o un vettore". Questo sembra un comportamento preferibile in questo caso, sebbene i tuoi commenti sull'ordine di valutazione nelle macro siano ben presi.
Mark Harrison,

1
Non avevo pensato al reclamo del compilatore come una notifica automatica di un tipo errato. Grazie!

3
C'è un motivo per non usare (sizeof (x) / sizeof (*x))?
seriousdev,

16

Per gli array multidimensionali è un po 'più complicato. Spesso le persone definiscono costanti macro esplicite, vale a dire

#define g_rgDialogRows   2
#define g_rgDialogCols   7

static char const* g_rgDialog[g_rgDialogRows][g_rgDialogCols] =
{
    { " ",  " ",    " ",    " 494", " 210", " Generic Sample Dialog", " " },
    { " 1", " 330", " 174", " 88",  " ",    " OK",        " " },
};

Ma queste costanti possono essere valutate anche in fase di compilazione con sizeof :

#define rows_of_array(name)       \
    (sizeof(name   ) / sizeof(name[0][0]) / columns_of_array(name))
#define columns_of_array(name)    \
    (sizeof(name[0]) / sizeof(name[0][0]))

static char* g_rgDialog[][7] = { /* ... */ };

assert(   rows_of_array(g_rgDialog) == 2);
assert(columns_of_array(g_rgDialog) == 7);

Si noti che questo codice funziona in C e C ++. Per array con più di due dimensioni utilizzare

sizeof(name[0][0][0])
sizeof(name[0][0][0][0])

ecc. all'infinito.


15
sizeof(array) / sizeof(array[0])

A seconda del tipo array, non è necessario utilizzare sizeof(array) / sizeof(array[0])se arrayè una matrice di uno dei due char, unsigned charoppure signed char- Citazione da C18,6.5.3.4 / 4: "Quando sizeof viene applicato a un operando che ha tipo char, unsigned char, o char char , (o una sua versione qualificata) il risultato è 1. " In questo caso puoi semplicemente fare sizeof(array)come spiegato nella mia risposta dedicata .
RobertS supporta Monica Cellio il

15

Dimensione di un array in C:

int a[10];
size_t size_of_array = sizeof(a);      // Size of array a
int n = sizeof (a) / sizeof (a[0]);    // Number of elements in array a
size_t size_of_element = sizeof(a[0]); // Size of each element in array a                                          
                                       // Size of each element = size of type

4
Curioso che codice usato size_t size_of_elementancora intcon int n = sizeof (a) / sizeof (a[0]); e nonsize_t n = sizeof (a) / sizeof (a[0]);
chux - Ripristina Monica

1
Ciao @Yogeesh HT, puoi per favore rispondere al dubbio di Chux. Sono anche molto curioso di sapere come int n = sizeof (a) / sizeof (a [0]) sta dando la lunghezza della matrice e perché non stiamo usando size_t per la lunghezza della matrice. Qualcuno può rispondere?
Cervello,

1
@Brain sizeof (a) fornisce sizeof di tutti gli elementi presenti nell'array una sizeof (a [0]) fornisce sizeof dei primi elementi. Supponiamo che a = {1,2,3,4,5}; sizeof (a) = 20bytes (se sizeof (int) = 4bytes moltiplicare 5), sizeof (a [0]) = 4bytes, quindi 20/4 = 5 cioè no degli elementi
Yogeesh HT

2
@YogeeshHT Per array molto grandi come char a[INT_MAX + 1u];, int nusato in int n = sizeof (a) / sizeof (a[0]);è insufficiente (è UB). L'uso size_t n = sizeof (a) / sizeof (a[0]);non comporta questo problema.
chux - Ripristina Monica

13

Consiglio di non usare mai sizeof(anche se può essere usato) per ottenere una delle due diverse dimensioni di un array, in numero di elementi o in byte, che sono gli ultimi due casi che mostro qui. Per ciascuna delle due dimensioni, le macro mostrate di seguito possono essere utilizzate per renderlo più sicuro. Il motivo è quello di rendere evidente l'intenzione del codice per manutentori, e la differenza sizeof(ptr)da sizeof(arr)al primo sguardo (che scritto in questo modo non è ovvio), in modo che gli insetti sono quindi evidenti per tutti la lettura del codice.


TL; DR:

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]) + must_be_array(arr))

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

#define ARRAY_BYTES(arr)    (sizeof((arr)[0]) * ARRAY_SIZE(arr))

must_be_array(arr)(definito di seguito) È necessario come -Wsizeof-pointer-divè buggy (da aprile / 2020):

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")
#define must_be_array(a)        (                                       \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        Static_assert_array(a);                         \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)

Ci sono stati importanti bug su questo argomento: https://lkml.org/lkml/2015/9/3/428

Non sono d'accordo con la soluzione fornita da Linus, che è quella di non usare mai la notazione di array per i parametri delle funzioni.

Mi piace la notazione di array come documentazione secondo cui un puntatore viene utilizzato come array. Ciò significa che è necessario applicare una soluzione a prova di errore, in modo che sia impossibile scrivere codice errato.

Da un array abbiamo tre dimensioni che potremmo voler sapere:

  • La dimensione degli elementi dell'array
  • Il numero di elementi nella matrice
  • La dimensione in byte utilizzata dall'array in memoria

La dimensione degli elementi dell'array

Il primo è molto semplice e non importa se abbiamo a che fare con un array o un puntatore, perché è fatto allo stesso modo.

Esempio di utilizzo:

void foo(ptrdiff_t nmemb, int arr[static nmemb])
{
        qsort(arr, nmemb, sizeof(arr[0]), cmp);
}

qsort() ha bisogno di questo valore come terzo argomento.


Per le altre due dimensioni, che sono l'argomento della domanda, vogliamo assicurarci di avere a che fare con un array e, in caso contrario, interrompere la compilazione, perché se abbiamo a che fare con un puntatore, avremo valori errati . Quando la compilazione viene interrotta, saremo in grado di vedere facilmente che non avevamo a che fare con un array, ma con un puntatore, e dovremo solo scrivere il codice con una variabile o una macro che memorizza la dimensione del matrice dietro il puntatore.


Il numero di elementi nella matrice

Questo è il più comune e molte risposte ti hanno fornito la tipica macro ARRAY_SIZE:

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

Dato che il risultato di ARRAY_SIZE è comunemente usato con variabili di tipo firmate ptrdiff_t, è bene definire una variante firmata di questa macro:

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

Le matrici con più di PTRDIFF_MAXmembri daranno valori non validi per questa versione firmata della macro, ma dalla lettura di C17 :: 6.5.6.9, matrici del genere stanno già giocando con il fuoco. Solo ARRAY_SIZEesize_t dovrebbe essere usato in questi casi.

Le versioni recenti dei compilatori, come GCC 8, ti avviseranno quando applichi questa macro a un puntatore, quindi è sicuro (ci sono altri metodi per renderlo sicuro con compilatori più vecchi).

Funziona dividendo la dimensione in byte dell'intero array per la dimensione di ciascun elemento.

Esempi di utilizzo:

void foo(ptrdiff_t nmemb)
{
        char buf[nmemb];

        fgets(buf, ARRAY_SIZE(buf), stdin);
}

void bar(ptrdiff_t nmemb)
{
        int arr[nmemb];

        for (ptrdiff_t i = 0; i < ARRAY_SSIZE(arr); i++)
                arr[i] = i;
}

Se queste funzioni non usassero le matrici, ma le ottenessero invece come parametri, il codice precedente non verrebbe compilato, quindi sarebbe impossibile avere un bug (dato che viene utilizzata una versione recente del compilatore o che viene utilizzato qualche altro trucco) e dobbiamo sostituire la chiamata macro con il valore:

void foo(ptrdiff_t nmemb, char buf[nmemb])
{

        fgets(buf, nmemb, stdin);
}

void bar(ptrdiff_t nmemb, int arr[nmemb])
{

        for (ptrdiff_t i = 0; i < nmemb; i++)
                arr[i] = i;
}

La dimensione in byte utilizzata dall'array in memoria

ARRAY_SIZE è comunemente usato come soluzione al caso precedente, ma questo caso è raramente scritto in modo sicuro, forse perché è meno comune.

Il modo comune per ottenere questo valore è usare sizeof(arr). Il problema: lo stesso del precedente; se hai un puntatore invece di un array, il tuo programma diventerà pazzo.

La soluzione al problema consiste nell'utilizzare la stessa macro di prima, che sappiamo essere sicura (interrompe la compilazione se viene applicata a un puntatore):

#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

Come funziona è molto semplice: annulla la divisione che lo ARRAY_SIZEfa, quindi dopo le cancellazioni matematiche si finisce con una sola sizeof(arr), ma con la sicurezza aggiuntiva della ARRAY_SIZEcostruzione.

Esempio di utilizzo:

void foo(ptrdiff_t nmemb)
{
        int arr[nmemb];

        memset(arr, 0, ARRAY_BYTES(arr));
}

memset() ha bisogno di questo valore come terzo argomento.

Come prima, se l'array viene ricevuto come parametro (un puntatore), non verrà compilato e dovremo sostituire la chiamata macro con il valore:

void foo(ptrdiff_t nmemb, int arr[nmemb])
{

        memset(arr, 0, sizeof(arr[0]) * nmemb);
}

Aggiornamento (23 / apr / 2020): -Wsizeof-pointer-divbuggy :

Oggi ho scoperto che il nuovo avviso in GCC funziona solo se la macro è definita in un'intestazione che non è un'intestazione del sistema. Se definisci la macro in un'intestazione installata nel tuo sistema (di solito /usr/local/include/o /usr/include/) ( #include <foo.h>), il compilatore NON emetterà un avviso (ho provato GCC 9.3.0).

Quindi abbiamo #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))e vogliamo renderlo sicuro. Avremo bisogno di C11 _Static_assert()e di alcune estensioni GCC: dichiarazioni e dichiarazioni in espressioni , __builtin_types_compatible_p :

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        sizeof(arr) / sizeof((arr)[0]);                                \
}                                                                       \
)

Ora ARRAY_SIZE()è completamente sicuro e quindi tutti i suoi derivati ​​saranno al sicuro.


Aggiornamento: libbsd fornisce __arraycount():

Libbsd fornisce la macro __arraycount()in <sys/cdefs.h>, che non è sicura perché manca una coppia di parentesi, ma possiamo aggiungere noi stessi quelle parentesi, e quindi non abbiamo nemmeno bisogno di scrivere la divisione nella nostra intestazione (perché dovremmo duplicare il codice che già esiste? ). Quella macro è definita in un'intestazione del sistema, quindi se la usiamo siamo costretti a usare le macro sopra.

#include <sys/cdefs.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        __arraycount((arr));                                            \
}                                                                       \
)

#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

Alcuni sistemi forniscono nitems()a <sys/param.h>posto, e alcuni sistemi forniscono sia. Dovresti controllare il tuo sistema e usare quello che hai, e forse usare alcuni condizionali del preprocessore per la portabilità e il supporto di entrambi.


Aggiornamento: consente di utilizzare la macro nell'ambito del file:

Sfortunatamente, l' ({})estensione gcc non può essere usata nell'ambito dei file. Per poter utilizzare la macro nell'ambito del file, l'asserzione statica deve essere all'interno sizeof(struct {}). Quindi, moltiplicalo 0per non influenzare il risultato. Un cast (int)potrebbe essere utile per simulare una funzione che ritorna (int)0(in questo caso non è necessario, ma è riutilizzabile per altre cose).

#include <sys/cdefs.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")
#define must_be_array(a)        (                                       \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        Static_assert_array(a);                         \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)

#define ARRAY_SIZE(arr)         (__arraycount((arr)) + must_be_array(arr))
#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

3
Ti dispiacerebbe spiegare perché il downvote? Questo dimostra una soluzione ad una costruzione pericoloso e comune ( sizeof(arr)) che non è mostrato altrove ARRAY_BYTES(arr).
Cacahuete Frito,

2
ARRAY_SIZE è abbastanza comune per essere utilizzato liberamente e ARRAY_BYTES è molto esplicito nel suo nome, dovrebbe essere definito accanto a ARRAY_SIZE in modo che un utente possa vedere entrambi facilmente e, dal suo utilizzo, non credo che chiunque legga il codice abbia dubbi su ciò che lo fa. Quello che intendevo è non usare un semplice sizeof, ma usare invece queste costruzioni; se hai voglia di scrivere queste costruzioni ogni volta, probabilmente commetterai un errore (molto comune se copi incolla, e anche molto comune se le scrivi ogni volta perché hanno molte parentesi) ...
Cacahuete Frito

3
..., quindi sostengo la conclusione principale: un singolo sizeofè chiaramente non sicuro (i motivi sono nella risposta), e non usare le macro ma usare le costruzioni che ho fornito, ogni volta, è ancora più pericoloso, quindi l'unico modo per andare sono le macro.
Cacahuete Frito,

3
@MarkHarrison Conosco la differenza tra puntatori e array. Ma ci sono state volte in cui ho avuto una funzione che in seguito ho trasformato in piccole funzioni, e quello che prima era un array, in seguito era un puntatore, ed è un punto in cui se ti dimentichi di cambiare dimensione, lo rovini ed è facile non vedere uno di quelli.
Cacahuete Frito,

3
@hyde Inoltre, che conosco la differenza non significa che tutti conoscano la differenza, e perché non usare qualcosa che sostanzialmente rimuove il 100% di quei bug? Quel bug è quasi arrivato a Linux; è arrivato a Linus, il che significa che ha passato molti controlli, e ciò significa anche che esiste la possibilità che lo stesso bug sia arrivato a Linux in un'altra parte del kernel, come dice.
Cacahuete Frito,

11

"hai introdotto un modo sottile di spararti al piede"

Le matrici "native" non memorizzano le loro dimensioni. Si consiglia pertanto di salvare la lunghezza dell'array in una variabile / const separata e passarla ogni volta che si passa all'array, ovvero:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

DOVREBBE sempre evitare le matrici native (a meno che non sia possibile, nel qual caso, badare al piede). Se stai scrivendo C ++, usa il contenitore "vector" di STL . "Rispetto agli array, offrono quasi le stesse prestazioni" e sono molto più utili!

// vector is a template, the <int> means it is a vector of ints
vector<int> numbers;  

// push_back() puts a new value at the end (or back) of the vector
for (int i = 0; i < 10; i++)
    numbers.push_back(i);

// Determine the size of the array
cout << numbers.size();

Vedi: http://www.cplusplus.com/reference/stl/vector/


Ho letto che il modo corretto di dichiarare costanti intere in C è usare una enumdichiarazione.
Raffi Khatchadourian,

La domanda riguarda C, non C ++. Quindi niente STL.
Cacahuete Frito,

11
#define SIZE_OF_ARRAY(_array) (sizeof(_array) / sizeof(_array[0]))

6
Si noti che questo funziona solo per array effettivi, non per puntatori che sembrano puntare ad array.
David Schwartz,

5

Se vuoi davvero farlo per passare intorno alla tua matrice, ti suggerisco di implementare una struttura per memorizzare un puntatore al tipo di cui desideri una matrice e un numero intero che rappresenti la dimensione della matrice. Quindi puoi passarlo alle tue funzioni. Basta assegnare il valore della variabile di matrice (puntatore al primo elemento) a quel puntatore. Quindi puoi andare Array.arr[i]per ottenere l'i-esimo elemento e usare Array.sizeper ottenere il numero di elementi nell'array.

Ho incluso del codice per te. Non è molto utile ma potresti estenderlo con più funzionalità. Ad essere onesti, se queste sono le cose che desideri, dovresti smettere di usare C e usare un'altra lingua con queste funzionalità integrate.

/* Absolutely no one should use this...
   By the time you're done implementing it you'll wish you just passed around
   an array and size to your functions */
/* This is a static implementation. You can get a dynamic implementation and 
   cut out the array in main by using the stdlib memory allocation methods,
   but it will work much slower since it will store your array on the heap */

#include <stdio.h>
#include <string.h>
/*
#include "MyTypeArray.h"
*/
/* MyTypeArray.h 
#ifndef MYTYPE_ARRAY
#define MYTYPE_ARRAY
*/
typedef struct MyType
{
   int age;
   char name[20];
} MyType;
typedef struct MyTypeArray
{
   int size;
   MyType *arr;
} MyTypeArray;

MyType new_MyType(int age, char *name);
MyTypeArray newMyTypeArray(int size, MyType *first);
/*
#endif
End MyTypeArray.h */

/* MyTypeArray.c */
MyType new_MyType(int age, char *name)
{
   MyType d;
   d.age = age;
   strcpy(d.name, name);
   return d;
}

MyTypeArray new_MyTypeArray(int size, MyType *first)
{
   MyTypeArray d;
   d.size = size;
   d.arr = first;
   return d;
}
/* End MyTypeArray.c */


void print_MyType_names(MyTypeArray d)
{
   int i;
   for (i = 0; i < d.size; i++)
   {
      printf("Name: %s, Age: %d\n", d.arr[i].name, d.arr[i].age);
   }
}

int main()
{
   /* First create an array on the stack to store our elements in.
      Note we could create an empty array with a size instead and
      set the elements later. */
   MyType arr[] = {new_MyType(10, "Sam"), new_MyType(3, "Baxter")};
   /* Now create a "MyTypeArray" which will use the array we just
      created internally. Really it will just store the value of the pointer
      "arr". Here we are manually setting the size. You can use the sizeof
      trick here instead if you're sure it will work with your compiler. */
   MyTypeArray array = new_MyTypeArray(2, arr);
   /* MyTypeArray array = new_MyTypeArray(sizeof(arr)/sizeof(arr[0]), arr); */
   print_MyType_names(array);
   return 0;
}

1
Impossibile eseguire l'upgrade del codice che non viene strcpy(d.name, name);gestito senza overflow.
chux - Ripristina Monica

4

Il modo migliore è salvare queste informazioni, ad esempio in una struttura:

typedef struct {
     int *array;
     int elements;
} list_s;

Implementa tutte le funzioni necessarie come creare, distruggere, verificare l'uguaglianza e tutto ciò di cui hai bisogno. È più facile passare come parametro.


6
Qualche motivo per int elementsvs. size_t elements?
chux - Ripristina Monica

3

La funzione sizeofrestituisce il numero di byte utilizzato dall'array in memoria. Se si desidera calcolare il numero di elementi nell'array, è necessario dividere tale numero con il sizeoftipo variabile dell'array. Diciamo che int array[10];se il numero intero di tipo variabile nel tuo computer è a 32 bit (o 4 byte), per ottenere la dimensione dell'array, dovresti fare quanto segue:

int array[10];
int sizeOfArray = sizeof(array)/sizeof(int);

2

Puoi usare l' &operatore. Ecco il codice sorgente:

#include<stdio.h>
#include<stdlib.h>
int main(){

    int a[10];

    int *p; 

    printf("%p\n", (void *)a); 
    printf("%p\n", (void *)(&a+1));
    printf("---- diff----\n");
    printf("%zu\n", sizeof(a[0]));
    printf("The size of array a is %zu\n", ((char *)(&a+1)-(char *)a)/(sizeof(a[0])));


    return 0;
};

Ecco l'output di esempio

1549216672
1549216712
---- diff----
4
The size of array a is 10

7
Non ho votato a fondo, ma è come colpire un chiodo con un mattone perché non hai notato un martello accanto a te. Inoltre, le persone tendono ad aggrottare le sopracciglia sull'uso di variabili non inizializzate ... ma qui credo che serva abbastanza bene al tuo scopo.
Dmitri,

2
@Dmitri non si accede a variabili non inizializzate qui
MM

1
Hmmm. La sottrazione del puntatore porta a ptrdiff_t. sizeof()risultati in size_t. C non definisce quale sia il grado più ampio o più alto / lo stesso. Quindi il tipo di quoziente ((char *)(&a+1)-(char *)a)/(sizeof(a[0]))non è certo size_te quindi la stampa con zpuò portare a UB. Basta usare printf("The size of array a is %zu\n", sizeof a/sizeof a[0]);è sufficiente.
chux - Ripristina Monica

1
(char *)(&a+1)-(char *)anon è una costante e può essere calcolata in fase di esecuzione, anche con dimensioni fisse a[10]. sizeof(a)/sizeof(a[0])in questo caso viene eseguito costantemente al momento della compilazione.
chux - Ripristina Monica

1

La risposta più semplice:

#include <stdio.h>

int main(void) {

    int a[] = {2,3,4,5,4,5,6,78,9,91,435,4,5,76,7,34};//for Example only
    int size;

    size = sizeof(a)/sizeof(a[0]);//Method

    printf ("size = %d",size);
    return 0;
}

1

Una soluzione più elegante sarà

size_t size = sizeof(a) / sizeof(*a);

0

Oltre alle risposte già fornite, voglio evidenziare un caso speciale con l'uso di

sizeof(a) / sizeof (a[0])

Se aè una matrice di char, unsigned charo signed charnon è necessario utilizzarla sizeofdue volte poiché sizeofrisulta sempre un'espressione con un operando di questi tipi 1.

Citazione da C18,6.5.3.4 / 4:

" Quando sizeofviene applicato a un operando che ha tipo char, unsigned charo signed char, (o una sua versione qualificata) il risultato è 1."

Pertanto, sizeof(a) / sizeof (a[0])sarebbe equivalente a NUMBER OF ARRAY ELEMENTS / 1if aè una matrice di tipo char, unsigned charoppure signed char. La divisione per 1 è ridondante.

In questo caso, puoi semplicemente abbreviare e fare:

sizeof(a)

Per esempio:

char a[10];
size_t length = sizeof(a);

Se vuoi una prova, ecco un link a GodBolt .


Tuttavia, la divisione mantiene la sicurezza, se il tipo cambia significativamente (anche se questi casi sono rari).


1
Probabilmente preferisci applicare ancora una macro con la divisione, perché il tipo potrebbe cambiare in futuro (anche se forse è improbabile) e la divisione è nota al momento della compilazione, quindi il compilatore la ottimizzerà via (se non cambia il tuo compilatore).
Cacahuete Frito,

1
@CacahueteFrito Sì, ci ho pensato anche io nel frattempo. L'ho preso come nota a margine nella risposta. Grazie.
RobertS supporta Monica Cellio il

-1

Nota: questo può darti un comportamento indefinito come indicato da MM nel commento.

int a[10];
int size = (*(&a+1)-a) ;

Per maggiori dettagli vedi qui e anche qui .


2
Questo è un comportamento tecnicamente indefinito; l' *operatore non può essere applicato a un puntatore oltre la fine
MM

3
"comportamento indefinito" significa che lo standard C non definisce il comportamento. Se lo provi nel tuo programma, allora può succedere di tutto
MM,

@MM stai dicendo che *(&a+1) - a;è diverso da (&a)[1] - a;sopra, non entrambi *(&a+1)e (&a)[1]contano come 1 dopo la fine?
QuentinUK,

@QuentinUK le tue due espressioni sono entrambe uguali, x[y]è definito come*(x + (y))
MM

@MM L'ho pensato così. Ma l'altra risposta, di Arjun Sreedharan, ha 38 frecce in su e questa ha -1. E la risposta di Arjun Sreedharan non fa menzione di comportamenti indefiniti.
QuentinUK,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.