Wie finde ich die 'Größe von' (ein Zeiger, der auf ein Array zeigt)?

309

Hier ist zunächst ein Code:

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

    return 0;
}

Gibt es eine Möglichkeit, die Größe des Arrays herauszufinden, auf das verwiesen ptrwird (anstatt nur seine Größe anzugeben, die auf einem 32-Bit-System vier Bytes beträgt)?

jkidv
quelle
84
Ich habe immer Parens mit sizeof verwendet - sicher sieht es wie ein Funktionsaufruf aus, aber ich denke, es ist klarer.
Paul Tomblin
20
Warum nicht? Haben Sie etwas gegen überflüssige Klammern? Ich denke, es liest sich bei ihnen selbst etwas leichter.
David Thornley
6
@Paul: Nun ... vorausgesetzt, die linke Seite dieses Aufrufs ist ein Zeiger auf int, würde ich ihn als int * ptr = malloc (4 * sizeof * ptr) schreiben. was mir viel klarer ist. Weniger Parens zum Lesen und die wörtliche Konstante nach vorne bringen, wie in der Mathematik.
Entspannen Sie am
4
@unwind - ordne kein Array von Zeigern zu, wenn du ein Array von Ints gemeint hast!
Paul Tomblin
6
Hier gibt es keinen "Zeiger, der auf ein Array zeigt". Nur ein Zeiger, der auf ein int zeigt.
Newacct

Antworten:

269

Nein, das kannst du nicht. Der Compiler weiß nicht, auf was der Zeiger zeigt. Es gibt Tricks, wie das Beenden des Arrays mit einem bekannten Out-of-Band-Wert und das anschließende Zählen der Größe bis zu diesem Wert, aber das wird nicht verwendet sizeof().

Ein weiterer Trick ist der von Zan erwähnte , der darin besteht, die Größe irgendwo zu verstauen. Wenn Sie beispielsweise das Array dynamisch zuweisen, weisen Sie einem Block einen int zu, der größer als der von Ihnen benötigte ist, speichern Sie die Größe im ersten int und kehren Sie ptr+1als Zeiger auf das Array zurück. Wenn Sie die Größe benötigen, dekrementieren Sie den Zeiger und sehen Sie sich den versteckten Wert an. Denken Sie daran, den gesamten Block von Anfang an freizugeben und nicht nur das Array.

Paul Tomblin
quelle
12
Es tut mir leid, dass ich so spät einen Kommentar gepostet habe, aber wenn der Compiler nicht weiß, auf was der Zeiger zeigt, woher weiß free, wie viel Speicher gelöscht werden muss? Ich weiß, dass diese Informationen intern für Funktionen wie kostenlos gespeichert werden. Meine Frage ist also, warum der Compiler das auch kann.
viki.omega9
11
@ viki.omega9, weil free die Größe zur Laufzeit erkennt. Der Compiler kann die Größe nicht kennen, da Sie das Array abhängig von den Laufzeitfaktoren (Befehlszeilenargumente, Inhalt einer Datei, Mondphase usw.) unterschiedlich dimensionieren können.
Paul Tomblin
15
Schnelle Nachverfolgung, warum gibt es keine Funktion, die die Größe so zurückgeben kann, wie es kostenlos ist?
viki.omega9
5
Wenn Sie garantieren könnten, dass die Funktion nur mit malloced Speicher aufgerufen wurde und die Bibliothek den malloced Speicher so verfolgt, wie ich es am meisten gesehen habe (indem Sie ein int vor dem zurückgegebenen Zeiger verwenden), könnten Sie einen schreiben. Wenn sich der Zeiger jedoch auf ein statisches Array oder dergleichen befindet, schlägt dies fehl. Ebenso gibt es keine Garantie dafür, dass Ihr Programm auf die Größe des Malloced-Speichers zugreifen kann.
Paul Tomblin
9
@ viki.omega9: Eine andere Sache, die Sie beachten sollten, ist, dass die vom malloc / free-System aufgezeichnete Größe möglicherweise nicht der von Ihnen gewünschten Größe entspricht. Sie malloc 9 Bytes und erhalten 16. Malloc 3K Bytes und erhalten 4K. Oder ähnliche Situationen.
Zan Lynx
85

Die Antwort ist nein."

C-Programmierer speichern die Größe des Arrays irgendwo. Es kann Teil einer Struktur sein, oder der Programmierer kann ein bisschen und malloc()mehr Speicher als angefordert betrügen , um einen Längenwert vor dem Start des Arrays zu speichern.

Zan Lynx
quelle
3
So werden Pascal-Strings implementiert
dsm
6
und anscheinend sind Pascal-Strings der Grund, warum Excel so schnell läuft!
Adam Naylor
8
@ Adam: Es ist schnell. Ich benutze es in einer Liste meiner Strings-Implementierung. Die lineare Suche ist superschnell, da es sich um Folgendes handelt: Ladegröße, Prefetch-Position + Größe, Größe mit Suchgröße vergleichen, wenn gleich strncmp, zur nächsten Zeichenfolge wechseln, wiederholen. Es ist schneller als die binäre Suche mit bis zu 500 Zeichenfolgen.
Zan Lynx
47

Für dynamische Arrays ( malloc oder C ++ new ) müssen Sie die Größe des Arrays wie von anderen erwähnt speichern oder eine Array-Manager-Struktur erstellen, die das Hinzufügen, Entfernen, Zählen usw. handhabt. Leider macht C dies bei weitem nicht so gut wie C ++, da Sie es grundsätzlich für jeden unterschiedlichen Array-Typ erstellen müssen, den Sie speichern. Dies ist umständlich, wenn Sie mehrere Arten von Arrays verwalten müssen.

Für statische Arrays, wie das in Ihrem Beispiel, wird ein allgemeines Makro verwendet, um die Größe zu ermitteln. Es wird jedoch nicht empfohlen, da nicht überprüft wird, ob der Parameter wirklich ein statisches Array ist. Das Makro wird jedoch in echtem Code verwendet, z. B. in den Linux-Kernel-Headern, obwohl es sich geringfügig von dem folgenden unterscheiden kann:

#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;
}

Sie können aus Gründen googeln, um sich vor solchen Makros in Acht zu nehmen. Achtung.

Wenn möglich, die C ++ stdlib wie Vektor, die viel sicherer und einfacher zu bedienen ist.

Ryan
quelle
11
ARRAY_SIZE ist ein weit verbreitetes Paradigma, das von praktischen Programmierern überall verwendet wird.
Sanjaya R
5
Ja, es ist ein weit verbreitetes Paradigma. Sie müssen es dennoch vorsichtig verwenden, da es leicht zu vergessen ist und in einem dynamischen Array verwendet werden kann.
Ryan
2
Ja, guter Punkt, aber die gestellte Frage betraf den Zeiger und nicht das statische Array.
Paul Tomblin
2
Dieses ARRAY_SIZEMakro funktioniert immer, wenn sein Argument ein Array ist (dh ein Ausdruck vom Array-Typ). Für Ihr sogenanntes "dynamisches Array" erhalten Sie nie ein tatsächliches "Array" (Ausdruck des Array-Typs). (Dies ist natürlich nicht möglich, da Array-Typen beim Kompilieren ihre Größe angeben.) Sie erhalten lediglich einen Zeiger auf das erste Element. Ihr Einwand "prüft nicht, ob der Parameter wirklich ein statisches Array ist" ist nicht wirklich gültig, da sie sich unterscheiden, da eines ein Array ist und das andere nicht.
Newacct
2
Es gibt eine Vorlagenfunktion, die das Gleiche tut, aber die Verwendung von Zeigern verhindert.
Natalie Adams
18

Es gibt eine saubere Lösung mit C ++ - Vorlagen, ohne sizeof () zu verwenden . Die folgende Funktion getSize () gibt die Größe eines statischen Arrays zurück:

#include <cstddef>

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

Hier ist ein Beispiel mit einer foo_t- Struktur:

#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;
}

Ausgabe:

3
5
Skurton
quelle
Ich habe die Notation noch nie gesehen T (&)[SIZE]. Können Sie erklären, was das bedeutet? In diesem Zusammenhang könnte man auch constexpr erwähnen.
WorldSEnder
2
Das ist schön, wenn Sie c ++ verwenden und tatsächlich eine Variable eines Array-Typs haben. Keiner von beiden ist in der Frage der Fall: Die Sprache ist C, und das OP möchte die Arraygröße von einem einfachen Zeiger erhalten.
Oguk
Würde dieser Code dazu führen, dass der Code aufgebläht wird, indem für jede Kombination aus Größe und Typ derselbe Code neu erstellt wird, oder wird dieser Code vom Compiler auf magische Weise optimiert?
user2796283
@WorldSEnder: Das ist die C ++ - Syntax für eine Referenz vom Array-Typ (ohne Variablennamen, nur Größe und Elementtyp).
Peter Cordes
@ user2796283: Diese Funktion wird zur Kompilierungszeit vollständig wegoptimiert. es wird keine Magie benötigt; Es kombiniert nichts zu einer einzigen Definition, sondern fügt es einfach zu einer Konstante für die Kompilierungszeit hinzu. (Aber in einem Debug-Build hätten Sie eine Reihe separater Funktionen, die unterschiedliche Konstanten zurückgeben. Linker-Magie führt möglicherweise solche zusammen, die dieselbe Konstante verwenden. Der Aufrufer wird nicht SIZEals Argument übergeben, sondern als Vorlagenparameter bereits durch die Funktionsdefinition bekannt sein.)
Peter Cordes
5

Für dieses spezielle Beispiel gibt es ja, wenn Sie typedefs verwenden (siehe unten). Wenn Sie dies auf diese Weise tun, können Sie natürlich auch SIZEOF_DAYS verwenden, da Sie wissen, auf was der Zeiger zeigt.

Wenn Sie einen (void *) - Zeiger haben, wie er von malloc () oder dergleichen zurückgegeben wird, gibt es keine Möglichkeit, die Datenstruktur zu bestimmen, auf die der Zeiger zeigt, und daher auch keine Möglichkeit, seine Größe zu bestimmen.

#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;
} 

Ausgabe:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4
David
quelle
5

Wie in allen richtigen Antworten angegeben, können Sie diese Informationen nicht allein aus dem verfallenen Zeigerwert des Arrays abrufen. Wenn der verfallene Zeiger das von der Funktion empfangene Argument ist, muss die Größe des Ursprungsarrays auf eine andere Weise angegeben werden, damit die Funktion diese Größe erkennt.

Hier ist ein anderer Vorschlag als der bisher bereitgestellte, der funktionieren wird: Übergeben Sie stattdessen einen Zeiger auf das Array. Dieser Vorschlag ähnelt den Vorschlägen im C ++ - Stil, außer dass C keine Vorlagen oder Verweise unterstützt:

#define ARRAY_SZ 10

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

Dieser Vorschlag ist jedoch für Ihr Problem etwas albern, da die Funktion so definiert ist, dass sie genau die Größe des übergebenen Arrays kennt (daher muss sizeof überhaupt nicht für das Array verwendet werden). Was es jedoch tut, ist eine gewisse Typensicherheit. Es verhindert, dass Sie ein Array mit einer unerwünschten Größe übergeben.

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

Wenn die Funktion in der Lage sein soll, mit einer beliebigen Arraygröße zu arbeiten, müssen Sie die Größe der Funktion als zusätzliche Information angeben.

jxh
quelle
1
+1 für "Sie können diese Informationen nicht allein aus dem verfallenen Zeigerwert des Arrays abrufen" und eine Problemumgehung bereitstellen.
Max
4

Es gibt keine magische Lösung. C ist keine reflektierende Sprache. Objekte wissen nicht automatisch, was sie sind.

Aber Sie haben viele Möglichkeiten:

  1. Fügen Sie natürlich einen Parameter hinzu
  2. Schließen Sie den Aufruf in ein Makro ein und fügen Sie automatisch einen Parameter hinzu
  3. Verwenden Sie ein komplexeres Objekt. Definieren Sie eine Struktur, die das dynamische Array sowie die Größe des Arrays enthält. Übergeben Sie dann die Adresse der Struktur.
DigitalRoss
quelle
Objekte wissen was sie sind. Wenn Sie jedoch auf ein Unterobjekt zeigen, gibt es keine Möglichkeit, Informationen über das gesamte Objekt oder ein größeres Unterobjekt abzurufen
MM
2

Meine Lösung für dieses Problem besteht darin, die Länge des Arrays in einem Struktur-Array als Metainformation über das Array zu speichern.

#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;
}

Sie müssen sich jedoch darum kümmern, die richtige Länge des Arrays festzulegen, das Sie speichern möchten, da dies keine Möglichkeit ist, diese Länge zu überprüfen, wie unsere Freunde massiv erklärt haben.


quelle
2

Sie können so etwas tun:

int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
int *ptr = days + 1;
printf("array length: %u\n", ptr[-1]);
return 0;
Tᴏᴍᴇʀ Wᴏʟʙᴇʀɢ
quelle
1

Nein, Sie können nicht ermitteln, sizeof(ptr)auf welche Größe das Array ptrzeigt.

Das Zuweisen von zusätzlichem Speicher (mehr als die Größe des Arrays) ist jedoch hilfreich, wenn Sie die Länge in zusätzlichem Speicherplatz speichern möchten.

SKD
quelle
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;
}

Die Größe der Tage [] beträgt 20, was keiner der Elemente entspricht. * Größe des Datentyps. Während die Größe des Zeigers 4 ist, egal auf was er zeigt. Weil ein Zeiger auf ein anderes Element zeigt, indem er dessen Adresse speichert.

Shivangi Chaurasia
quelle
1
sizeof (ptr) ist die Größe des Zeigers und sizeof (* ptr) ist die Größe des Zeigers, auf den
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 ist vorbei an der Größe variabel:

#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

Verwendung ist:

void main() {

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

        array1.array[i] *= 2;
    }
}
user3065147
quelle
0

In Strings befindet '\0'sich am Ende ein Zeichen, sodass die Länge des Strings mit Funktionen wie ermittelt werden kann strlen. Das Problem mit einem ganzzahligen Array besteht beispielsweise darin, dass Sie keinen Wert als Endwert verwenden können. Eine mögliche Lösung besteht darin, das Array zu adressieren und den NULLZeiger als Endwert zu verwenden .

#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 );
}
baz
quelle
Ihr Code ist voller Kommentare, aber ich denke, es würde alles einfacher machen, wenn Sie eine allgemeine Erklärung hinzufügen würden, wie dies außerhalb des Codes als normaler Text funktioniert. Können Sie bitte Ihre Frage bearbeiten und es tun? Danke dir!
Fabio sagt Reinstate Monica
Das Erstellen eines Arrays von Zeigern auf jedes Element, damit Sie es linear durchsuchen können, NULList wahrscheinlich die am wenigsten effiziente Alternative zum sizedirekten Speichern eines separaten Elements . Vor allem , wenn Sie tatsächlich nutzen diese zusätzliche Schicht Indirektionsebene die ganze Zeit.
Peter Cordes