Come trovare 'sizeof' (un puntatore che punta a un array)?


309

Prima di tutto, ecco un po 'di codice:

int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

C'è un modo per scoprire la dimensione dell'array che ptrpunta (invece di limitarne la dimensione, che è di quattro byte su un sistema a 32 bit)?


84
Ho sempre usato i genitori con sizeof - certo che lo fa sembrare una chiamata di funzione, ma penso che sia più chiaro.
Paul Tomblin,

20
Perchè no? Hai qualcosa contro le parentesi superflue? Penso che legga un po 'più facilmente con loro, me stesso.
David Thornley,

6
@Paul: beh .. supponendo che il lato sinistro di quella chiamata sia un puntatore a int, lo scriverei come int * ptr = malloc (4 * sizeof * ptr); che per me è molto più chiaro. Meno parentesi da leggere e portare in primo piano il constsant letterale, come in matematica.
Rilassati il

4
@unwind - non allocare un array di puntatori quando intendevi un array di ints!
Paul Tomblin,

6
Non esiste un "puntatore che punta a un array" qui. Solo un puntatore che punta a un int.
nuovo

Risposte:


269

No, non puoi. Il compilatore non sa a cosa punta il puntatore. Ci sono trucchi, come terminare l'array con un valore fuori banda noto e quindi contare la dimensione fino a quel valore, ma non sta usando sizeof().

Un altro trucco è quello menzionato da Zan , che è quello di riporre le dimensioni da qualche parte. Ad esempio, se si sta allocando dinamicamente l'array, allocare un blocco uno più grande di quello necessario, riporre la dimensione nel primo int e tornare ptr+1come puntatore all'array. Quando hai bisogno delle dimensioni, decrementa il puntatore e sbircia il valore nascosto. Ricorda solo di liberare l'intero blocco a partire dall'inizio e non solo l'array.


12
Mi dispiace per aver postato un commento così tardi, ma se il compilatore non sa a cosa punta il puntatore, come fa libero a sapere quanta memoria cancellare? So che queste informazioni sono archiviate internamente per funzioni come gratuite. Quindi la mia domanda è: perche 'può farlo anche il compilatore?
viki.omega9

11
@ viki.omega9, perché free rileva le dimensioni in fase di esecuzione. Il compilatore non è in grado di conoscere le dimensioni perché è possibile rendere l'array di dimensioni diverse a seconda dei fattori di runtime (argomenti della riga di comando, contenuto di un file, fase lunare, ecc.).
Paul Tomblin,

15
Follow-up rapido, perché non esiste una funzione in grado di restituire le dimensioni nel modo libero?
viki.omega9

5
Bene, se potessi garantire che la funzione è stata chiamata solo con memoria malloced e la libreria tiene traccia della memoria malloced nel modo in cui ho visto la maggior parte (usando un int prima del puntatore restituito), allora potresti scriverne uno. Ma se il puntatore si trova su un array statico o simile, fallirebbe. Allo stesso modo, non vi è alcuna garanzia che la dimensione della memoria allocata sia accessibile al proprio programma.
Paul Tomblin,

9
@ viki.omega9: un'altra cosa da tenere a mente è che la dimensione registrata dal sistema malloc / free potrebbe non essere la dimensione richiesta. Malloc 9 byte e ottieni 16. Malloc 3K byte e ottieni 4K. O situazioni simili.
Zan Lynx,

85

La risposta è no."

Quello che fanno i programmatori C è memorizzare le dimensioni dell'array da qualche parte. Può far parte di una struttura oppure il programmatore può imbrogliare un po 'di malloc()memoria in più rispetto a quanto richiesto per memorizzare un valore di lunghezza prima dell'inizio dell'array.


3
Ecco come vengono implementate le stringhe pascal
dsm

6
e apparentemente le stringhe pasquali sono il motivo per cui Excel corre così velocemente!
Adam Naylor,

8
@Adam: è veloce. Lo uso in un elenco di implementazioni di stringhe mie. È velocissimo per la ricerca lineare perché lo è: carica dimensione, precarica pos + dimensione, confronta la dimensione con la dimensione della ricerca, se uguale a strncmp, passa alla stringa successiva, ripeti. È più veloce della ricerca binaria fino a circa 500 stringhe.
Zan Lynx,

47

Per gli array dinamici ( malloc o C ++ nuovi ) è necessario memorizzare le dimensioni dell'array come indicato da altri o forse costruire una struttura di gestione dell'array che gestisca l'aggiunta, la rimozione, il conteggio, ecc. Sfortunatamente C non lo fa quasi altrettanto bene C ++ poiché in pratica devi costruirlo per ogni diverso tipo di array che stai memorizzando, il che è ingombrante se hai più tipi di array che devi gestire.

Per gli array statici, come quello nel tuo esempio, esiste una macro comune utilizzata per ottenere le dimensioni, ma non è consigliabile in quanto non controlla se il parametro è realmente un array statico. La macro viene tuttavia utilizzata nel codice reale, ad esempio nelle intestazioni del kernel Linux, sebbene possa essere leggermente diversa da quella seguente:

#if !defined(ARRAY_SIZE)
    #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#endif

int main()
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", ARRAY_SIZE(days));
    printf("%u\n", sizeof(ptr));
    return 0;
}

Puoi google per motivi di diffidare di macro come questa. Stai attento.

Se possibile, lo stdlib C ++ come vector, che è molto più sicuro e più facile da usare.


11
ARRAY_SIZE è un paradigma comune utilizzato da programmatori pratici ovunque.
Sanjaya R,

5
Sì, è un paradigma comune. È comunque necessario utilizzarlo con cautela, poiché è facile dimenticarlo e utilizzarlo su un array dinamico.
Ryan,

2
Sì, buon punto, ma la domanda che si poneva riguardava quella del puntatore, non quella dell'array statico.
Paul Tomblin,

2
Quella ARRAY_SIZEmacro funziona sempre se il suo argomento è un array (ovvero espressione del tipo di array). Per il tuo cosiddetto "array dinamico", non ottieni mai un vero "array" (espressione del tipo di array). (Naturalmente, non è possibile, poiché i tipi di array includono le loro dimensioni in fase di compilazione.) Ottieni solo un puntatore al primo elemento. La tua obiezione "non verifica se il parametro è realmente un array statico" non è realmente valida, poiché sono diversi poiché uno è un array e l'altro no.
nuovo

2
Esiste una funzione template mobile che fa la stessa cosa ma impedisce l'uso di puntatori.
Natalie Adams,

18

Esiste una soluzione pulita con modelli C ++, senza usare sizeof () . La seguente funzione getSize () restituisce la dimensione di qualsiasi array statico:

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

Ecco un esempio con una struttura foo_t :

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

struct foo_t {
    int ball;
};

int main()
{
    foo_t foos3[] = {{1},{2},{3}};
    foo_t foos5[] = {{1},{2},{3},{4},{5}};
    printf("%u\n", getSize(foos3));
    printf("%u\n", getSize(foos5));

    return 0;
}

Produzione:

3
5

Non ho mai visto la notazione T (&)[SIZE]. Puoi spiegarci cosa significa? Inoltre potresti menzionare constexpr in questo contesto.
WorldSEnder

2
È bello se usi c ++ e in realtà hai una variabile di un tipo di array. Nessuno dei due è il caso nella domanda: il linguaggio è C e la cosa da cui l'OP vuole ottenere la dimensione dell'array è un semplice puntatore.
Oguk,

questo codice porterebbe a gonfiare il codice ricreando lo stesso codice per ogni diversa combinazione dimensione / tipo o è magicamente ottimizzato dall'esistenza da parte del compilatore?
user2796283,

@WorldSEnder: questa è la sintassi C ++ per un riferimento al tipo di array (senza nome variabile, solo dimensioni e tipo di elemento).
Peter Cordes,

@ user2796283: questa funzione è completamente ottimizzata in fase di compilazione; nessuna magia è necessaria; non sta combinando nulla con una singola definizione, ma semplicemente la sta incorporando in una costante di compilazione. (Ma in una build di debug, sì, avresti un sacco di funzioni separate che restituiscono costanti diverse. La magia del linker potrebbe unire quelle che usano la stessa costante. Il chiamante non passa SIZEcome arg, è un parametro template che ha essere già noto con la definizione della funzione.)
Peter Cordes,

5

Per questo esempio specifico, sì, c'è, SE usi typedefs (vedi sotto). Ovviamente, se lo fai in questo modo, sei altrettanto adatto a utilizzare SIZEOF_DAYS, poiché sai a cosa punta il puntatore.

Se hai un puntatore (void *), come viene restituito da malloc () o simili, allora no, non c'è modo di determinare a quale struttura di dati punta il puntatore e quindi, non c'è modo di determinarne le dimensioni.

#include <stdio.h>

#define NUM_DAYS 5
typedef int days_t[ NUM_DAYS ];
#define SIZEOF_DAYS ( sizeof( days_t ) )

int main() {
    days_t  days;
    days_t *ptr = &days; 

    printf( "SIZEOF_DAYS:  %u\n", SIZEOF_DAYS  );
    printf( "sizeof(days): %u\n", sizeof(days) );
    printf( "sizeof(*ptr): %u\n", sizeof(*ptr) );
    printf( "sizeof(ptr):  %u\n", sizeof(ptr)  );

    return 0;
} 

Produzione:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4

5

Come indicato da tutte le risposte corrette, non è possibile ottenere queste informazioni dal solo valore del puntatore in decomposizione dell'array. Se il puntatore decaduto è l'argomento ricevuto dalla funzione, allora la dimensione dell'array di origine deve essere fornita in qualche altro modo affinché la funzione venga a conoscenza di quella dimensione.

Ecco un suggerimento diverso da quello che è stato fornito finora, che funzionerà: Passa invece un puntatore all'array. Questo suggerimento è simile ai suggerimenti di stile C ++, tranne per il fatto che C non supporta modelli o riferimenti:

#define ARRAY_SZ 10

void foo (int (*arr)[ARRAY_SZ]) {
    printf("%u\n", (unsigned)sizeof(*arr)/sizeof(**arr));
}

Ma questo suggerimento è un po 'sciocco per il tuo problema, poiché la funzione è definita per conoscere esattamente la dimensione dell'array che viene passato (quindi, non è necessario usare sizeof affatto sull'array). Quello che fa, tuttavia, è offrire un certo tipo di sicurezza. Vi proibirà di passare in una matrice di dimensioni indesiderate.

int x[20];
int y[10];
foo(&x); /* error */
foo(&y); /* ok */

Se si suppone che la funzione sia in grado di operare su qualsiasi dimensione di array, sarà necessario fornire le dimensioni alla funzione come informazioni aggiuntive.


1
+1 per "Non è possibile ottenere queste informazioni dal solo valore del puntatore decaduto dell'array" e fornire una soluzione alternativa.
Max

4

Non esiste una soluzione magica. C non è un linguaggio riflessivo. Gli oggetti non sanno automaticamente cosa sono.

Ma hai molte scelte:

  1. Ovviamente, aggiungi un parametro
  2. Avvolgere la chiamata in una macro e aggiungere automaticamente un parametro
  3. Usa un oggetto più complesso. Definire una struttura che contiene l'array dinamico e anche le dimensioni dell'array. Quindi, passa l'indirizzo della struttura.

Gli oggetti sanno cosa sono. Ma se fai riferimento a un oggetto secondario, non c'è modo di ottenere informazioni sull'oggetto completo o un oggetto secondario più grande
MM

2

La mia soluzione a questo problema è quella di salvare la lunghezza dell'array in un array struct come meta-informazione sull'array.

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

struct Array
{
    int length;

    double *array;
};

typedef struct Array Array;

Array* NewArray(int length)
{
    /* Allocate the memory for the struct Array */
    Array *newArray = (Array*) malloc(sizeof(Array));

    /* Insert only non-negative length's*/
    newArray->length = (length > 0) ? length : 0;

    newArray->array = (double*) malloc(length*sizeof(double));

    return newArray;
}

void SetArray(Array *structure,int length,double* array)
{
    structure->length = length;
    structure->array = array;
}

void PrintArray(Array *structure)
{       
    if(structure->length > 0)
    {
        int i;
        printf("length: %d\n", structure->length);
        for (i = 0; i < structure->length; i++)
            printf("%g\n", structure->array[i]);
    }
    else
        printf("Empty Array. Length 0\n");
}

int main()
{
    int i;
    Array *negativeTest, *days = NewArray(5);

    double moreDays[] = {1,2,3,4,5,6,7,8,9,10};

    for (i = 0; i < days->length; i++)
        days->array[i] = i+1;

    PrintArray(days);

    SetArray(days,10,moreDays);

    PrintArray(days);

    negativeTest = NewArray(-5);

    PrintArray(negativeTest);

    return 0;
}

Ma devi preoccuparti di impostare la giusta lunghezza dell'array che vuoi archiviare, perché non c'è modo di controllare questa lunghezza, come hanno spiegato in modo massiccio i nostri amici.


2

Puoi fare qualcosa del genere:

int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
int *ptr = days + 1;
printf("array length: %u\n", ptr[-1]);
return 0;

1

No, non è possibile utilizzare sizeof(ptr)per trovare la dimensione dell'array che ptrpunta.

Anche se l'allocazione di memoria aggiuntiva (superiore alla dimensione dell'array) sarà utile se si desidera memorizzare la lunghezza in spazio aggiuntivo.


1
int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

Dimensione dei giorni [] è 20 che non è un numero di elementi * dimensione del tipo di dati. Mentre la dimensione del puntatore è 4, indipendentemente da ciò a cui punta. Perché un puntatore punta ad un altro elemento memorizzando il suo indirizzo.


1
sizeof (ptr) è la dimensione del puntatore e sizeof (* ptr) è la dimensione del puntatore a cui
Amitābha,

0
 #define array_size 10

 struct {
     int16 size;
     int16 array[array_size];
     int16 property1[(array_size/16)+1]
     int16 property2[(array_size/16)+1]
 } array1 = {array_size, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 #undef array_size

array_size sta passando alla variabile size :

#define array_size 30

struct {
    int16 size;
    int16 array[array_size];
    int16 property1[(array_size/16)+1]
    int16 property2[(array_size/16)+1]
} array2 = {array_size};

#undef array_size

L'utilizzo è:

void main() {

    int16 size = array1.size;
    for (int i=0; i!=size; i++) {

        array1.array[i] *= 2;
    }
}

0

Nelle stringhe c'è un '\0'carattere alla fine in modo che la lunghezza della stringa possa essere ottenuta usando funzioni come strlen. Il problema con un array intero, ad esempio, è che non è possibile utilizzare alcun valore come valore finale, quindi una possibile soluzione è indirizzare l'array e utilizzare come valore finale il NULLpuntatore.

#include <stdio.h>
/* the following function will produce the warning:
 * ‘sizeof’ on array function parameter ‘a’ will
 * return size of ‘int *’ [-Wsizeof-array-argument]
 */
void foo( int a[] )
{
    printf( "%lu\n", sizeof a );
}
/* so we have to implement something else one possible
 * idea is to use the NULL pointer as a control value
 * the same way '\0' is used in strings but this way
 * the pointer passed to a function should address pointers
 * so the actual implementation of an array type will
 * be a pointer to pointer
 */
typedef char * type_t; /* line 18 */
typedef type_t ** array_t;
int main( void )
{
    array_t initialize( int, ... );
    /* initialize an array with four values "foo", "bar", "baz", "foobar"
     * if one wants to use integers rather than strings than in the typedef
     * declaration at line 18 the char * type should be changed with int
     * and in the format used for printing the array values 
     * at line 45 and 51 "%s" should be changed with "%i"
     */
    array_t array = initialize( 4, "foo", "bar", "baz", "foobar" );

    int size( array_t );
    /* print array size */
    printf( "size %i:\n", size( array ));

    void aprint( char *, array_t );
    /* print array values */
    aprint( "%s\n", array ); /* line 45 */

    type_t getval( array_t, int );
    /* print an indexed value */
    int i = 2;
    type_t val = getval( array, i );
    printf( "%i: %s\n", i, val ); /* line 51 */

    void delete( array_t );
    /* free some space */
    delete( array );

    return 0;
}
/* the output of the program should be:
 * size 4:
 * foo
 * bar
 * baz
 * foobar
 * 2: baz
 */
#include <stdarg.h>
#include <stdlib.h>
array_t initialize( int n, ... )
{
    /* here we store the array values */
    type_t *v = (type_t *) malloc( sizeof( type_t ) * n );
    va_list ap;
    va_start( ap, n );
    int j;
    for ( j = 0; j < n; j++ )
        v[j] = va_arg( ap, type_t );
    va_end( ap );
    /* the actual array will hold the addresses of those
     * values plus a NULL pointer
     */
    array_t a = (array_t) malloc( sizeof( type_t *) * ( n + 1 ));
    a[n] = NULL;
    for ( j = 0; j < n; j++ )
        a[j] = v + j;
    return a;
}
int size( array_t a )
{
    int n = 0;
    while ( *a++ != NULL )
        n++;
    return n;
}
void aprint( char *fmt, array_t a )
{
    while ( *a != NULL )
        printf( fmt, **a++ );   
}
type_t getval( array_t a, int i )
{
    return *a[i];
}
void delete( array_t a )
{
    free( *a );
    free( a );
}

Il tuo codice è pieno di commenti, ma penso che renderebbe tutto più semplice se tu aggiungessi una spiegazione generale di come funziona al di fuori del codice, come testo normale. Puoi per favore modificare la tua domanda e farlo? Grazie!
Fabio dice di reintegrare Monica l'

La creazione di un array di puntatori per ciascun elemento in modo da poterlo cercare linearmente NULLè probabilmente l'alternativa meno efficiente che si possa immaginare semplicemente per archiviarne uno separato size. Soprattutto se si utilizza sempre questo ulteriore livello di riferimento indiretto.
Peter Cordes,
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.