Wie bestimme ich die Größe meines Arrays in C?


Antworten:


1260

Zusammenfassung:

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

Vollständige Antwort:

Um die Größe Ihres Arrays in Bytes zu bestimmen, können Sie den sizeof Operator verwenden:

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

Auf meinem Computer sind Ints 4 Bytes lang, also ist n 68.

Um die Anzahl der Elemente im Array zu bestimmen, können wir die Gesamtgröße des Arrays durch die Größe des Array-Elements teilen. Sie können dies mit dem Typ wie folgt tun:

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

und erhalten Sie die richtige Antwort (68/4 = 17), aber wenn sich die Art der aÄnderung ändert, hätten Sie einen bösen Fehler, wenn Sie vergessen hätten, auch die zu ändern sizeof(int).

Der bevorzugte Divisor ist also sizeof(a[0])oder das Äquivalent sizeof(*a)die Größe des ersten Elements des Arrays.

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

Ein weiterer Vorteil ist, dass Sie den Array-Namen jetzt einfach in einem Makro parametrisieren und erhalten können:

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

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

6
Der generierte Code ist identisch, da der Compiler den Typ von * int_arr zur Kompilierungszeit kennt (und daher den Wert von sizeof (* int_arr)). Es wird eine Konstante sein, und der Compiler kann entsprechend optimieren.
Mark Harrison

10
Dies sollte bei allen Compilern der Fall sein, da die Ergebnisse von sizeof als Konstante für die Kompilierungszeit definiert sind.
Mark Harrison

451
Wichtig : Hören Sie hier nicht auf zu lesen, lesen Sie die nächste Antwort! Dies funktioniert nur für Arrays auf dem Stapel , z. B. wenn Sie malloc () verwenden oder auf einen Funktionsparameter zugreifen, haben Sie kein Glück. Siehe unten.
Markus

7
Für die Windows-API-Programmierung in C oder C ++ ist das ARRAYSIZEMakro definiert WinNT.h(das von anderen Headern abgerufen wird ). WinAPI-Benutzer müssen also kein eigenes Makro definieren.
Lumi

17
@ Markus funktioniert für jede Variable, die einen Array-Typ hat; Dies muss nicht "auf dem Stapel" sein. ZB static int a[20];. Ihr Kommentar ist jedoch für Leser nützlich, die den Unterschied zwischen einem Array und einem Zeiger möglicherweise nicht erkennen.
MM

808

Der sizeofWeg ist der richtige, wenn Sie mit Arrays arbeiten, die nicht als Parameter empfangen wurden. Ein Array, das als Parameter an eine Funktion gesendet wird, wird als Zeiger behandelt, sodass sizeofdie Größe des Zeigers anstelle der Größe des Arrays zurückgegeben wird.

Daher funktioniert diese Methode innerhalb von Funktionen nicht. Übergeben Sie stattdessen immer einen zusätzlichen Parameter size_t size, der die Anzahl der Elemente im Array angibt.

Prüfung:

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

Ausgabe (in einem 64-Bit-Linux-Betriebssystem):

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

Ausgabe (in einem 32-Bit-Windows-Betriebssystem):

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

10
Warum ist es so, length of parameter:2wenn nur ein Zeiger auf das 1. Array-Element übergeben wird?
Bbvarghe

16
@Bbvarghe Das liegt daran, dass Zeiger in 64-Bit-Systemen 8 Byte (sizeof (intArray)) haben, Ints aber (normalerweise) noch 4 Byte lang sind (sizeof (intArray [0])).
Elideb

13
@Pacerier: Es gibt keinen korrekten Code - die übliche Lösung besteht darin, die Länge zusammen mit dem Array als separates Argument zu übergeben.
Jean Hominal

10
Warten Sie, damit Sie nicht direkt über einen Zeiger auf das Array zugreifen und dessen Größe sehen können. Neu bei C hier.
Sudo

7
@ Michael Trouw: Sie können die Operatorsyntax verwenden, wenn Sie sich dadurch besser fühlen : (sizeof array / sizeof *array).
Chqrlie

134

Es ist erwähnenswert, dass sizeofdies nicht hilfreich ist, wenn es sich um einen Array-Wert handelt, der in einen Zeiger verfallen ist: Obwohl er auf den Anfang eines Arrays zeigt, ist er für den Compiler dasselbe wie ein Zeiger auf ein einzelnes Element dieses Arrays . Ein Zeiger "erinnert" sich an nichts anderes über das Array, mit dem es initialisiert wurde.

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: Der Standard definiert sizeof als die Anzahl der Bytes im Objekt ergebend und diese sizeof (char) ist immer eins. Die Anzahl der Bits in einem Byte ist implementierungsspezifisch. Edit: ANSI C ++ Standard Abschnitt 5.3.3 Sizeof: "Der Operator sizeof gibt die Anzahl der Bytes in der Objektdarstellung seines Operanden an. [...] sizeof (char), sizeof (signiertes Zeichen) und sizeof (vorzeichenloses Zeichen) sind 1; das Ergebnis der Größe von, das auf einen anderen Grundtyp angewendet wird, ist implementierungsdefiniert. "
Skizz

Abschnitt 1.6 Das C ++ - Speichermodell: "Die grundlegende Speichereinheit im C ++ - Speichermodell ist das Byte. Ein Byte ist mindestens groß genug, um ein Mitglied des grundlegenden Ausführungszeichensatzes aufzunehmen, und besteht aus einer zusammenhängenden Folge von Bits, der Zahl davon ist implementierungsdefiniert. "
Skizz

2
Ich erinnere mich, dass der CRAY C mit char32 Bit hatte. Der Standard besagt lediglich, dass ganzzahlige Werte von 0 bis 127 dargestellt werden können und ihr Bereich mindestens entweder -127 bis 127 (Zeichen ist signiert) oder 0 bis 255 (Zeichen ist nicht signiert) beträgt.
vonbrand

5
Dies ist eine hervorragende Antwort. Ich möchte kommentieren, dass alle oben genannten Behauptungen als WAHR bewertet werden.
Javad

49

Die Größe des "Tricks" ist der beste Weg, den ich kenne, mit einer kleinen, aber (für mich ist dies ein großer Ärger mit Haustieren) wichtigen Änderung in der Verwendung von Klammern.

Wie der Wikipedia-Eintrag deutlich macht, ist C sizeofkeine Funktion; Es ist ein Operator . Daher ist für das Argument keine Klammer erforderlich, es sei denn, das Argument ist ein Typname. Dies ist leicht zu merken, da das Argument dadurch wie ein Cast-Ausdruck aussieht, der auch Klammern verwendet.

Also: Wenn Sie Folgendes haben:

int myArray[10];

Sie können die Anzahl der Elemente mit Code wie folgt finden:

size_t n = sizeof myArray / sizeof *myArray;

Das liest sich für mich viel einfacher als die Alternative in Klammern. Ich bevorzuge auch die Verwendung des Sternchens im rechten Teil der Abteilung, da es prägnanter ist als die Indizierung.

Natürlich ist dies auch alles Kompilierungszeit, sodass Sie sich keine Sorgen über die Aufteilung machen müssen, die sich auf die Leistung des Programms auswirkt. Verwenden Sie dieses Formular also, wo immer Sie können.

Es ist immer am besten, sizeof für ein tatsächliches Objekt zu verwenden, wenn Sie eines haben, und nicht für einen Typ, da Sie sich dann nicht darum kümmern müssen, einen Fehler zu machen und den falschen Typ anzugeben.

Angenommen, Sie haben eine Funktion, die einige Daten als Bytestrom ausgibt, beispielsweise über ein Netzwerk. Rufen wir die Funktion auf send()und lassen sie als Argumente einen Zeiger auf das zu sendende Objekt und die Anzahl der Bytes im Objekt verwenden. So wird der Prototyp:

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

Und dann müssen Sie eine Ganzzahl senden, damit Sie sie wie folgt codieren:

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

Jetzt haben Sie eine subtile Methode eingeführt, mit der Sie sich in den Fuß schießen können, indem Sie den Typ fooan zwei Stellen angeben. Wenn sich einer ändert, der andere jedoch nicht, wird der Code unterbrochen. Also mach es immer so:

send(&foo, sizeof foo);

Jetzt bist du geschützt. Sicher, Sie duplizieren den Namen der Variablen, aber das hat eine hohe Wahrscheinlichkeit, dass der Compiler ihn erkennt, wenn Sie ihn ändern.


Übrigens, sind sie identische Anweisungen auf Prozessorebene? Benötigt sizeof(int)weniger Anweisungen als sizeof(foo)?
Pacerier

@ Pacerier: Nein, sie sind identisch. Denken Sie an int x = 1+1;versus int x = (1+1);. Klammern sind hier rein ästhetisch.
Quetzalcoatl

@Aidiakapi Das stimmt nicht, betrachten Sie C99 VLAs.
Entspannen Sie am

@unwind Danke, ich stehe korrigiert. Um meinen Kommentar zu korrigieren, sizeofwird in C ++ und C89 immer konstant sein. Mit den Arrays variabler Länge von C99 kann es zur Laufzeit ausgewertet werden.
Aidiakapi

2
sizeofkann ein Operator sein, sollte aber gemäß Linus Torvalds als Funktion behandelt werden. Genau. Lesen Sie seine Begründung hier: lkml.org/lkml/2012/7/11/103
Dr. Person Person II

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

Schauen Sie sich diesen Link zur Erklärung an


7
Kleiner Nitpick: Das Ergebnis der Zeigersubtraktion hat den Typ ptrdiff_t. (In der Regel ist dies auf einem 64-Bit-System ein größerer Typ als int). Auch wenn Sie ändern , intum ptrdiff_tin diesem Code, es hat immer noch einen Fehler , wenn in arrAnspruch nimmt mehr als die Hälfte des Adressraumes.
MM

2
@MM Ein weiterer kleiner Trottel: Abhängig von Ihrer Systemarchitektur ist der Adressraum bei weitem nicht so groß wie die Zeigergröße auf den meisten Systemen. Windows beispielsweise beschränkt den Adressraum für 64-Bit-Anwendungen auf 8 TB oder 44 Bit. Selbst wenn Sie beispielsweise ein Array haben, das größer als die Hälfte Ihres Adressraums von 4,1 TB ist, ist dies kein Fehler. Nur wenn Ihr Adressraum auf diesen Systemen 63 Bit überschreitet, kann es sogar zu einem solchen Fehler kommen. Mach dir im Allgemeinen keine Sorgen.
Aidiakapi

1
@Aidiakapi unter 32-Bit x86 Linux oder unter Windows mit /3GOption Sie haben 3G / 1G Benutzer / Kernel Split, wodurch Sie Arrays Größe bis zu 75% der Adressraumgröße haben können.
Ruslan

1
Betrachten Sie foo buf1[80]; foo buf2[sizeof buf1/sizeof buf1[0]]; foo buf3[(&buf1)[1] - buf1];als globale Variablen. buf3[]Deklaration schlägt fehl, da (&buf1)[1] - buf1es sich nicht um eine Konstante handelt.
chux

2
Dies ist ein technisch undefiniertes Verhalten, da der Standard eine Dereferenzierung nach dem Ende eines Arrays ausdrücklich nicht zulässt (auch wenn Sie nicht versuchen, den gespeicherten Wert zu lesen)
MM


21

Wenn Sie den Datentyp des Arrays kennen, können Sie Folgendes verwenden:

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

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

Wenn Sie den Datentyp des Arrays nicht kennen, können Sie Folgendes verwenden:

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

Hinweis: Diese Funktion funktioniert nur, wenn das Array zur Laufzeit nicht definiert ist (wie malloc) und das Array nicht in einer Funktion übergeben wird. In beiden Fällen ist arr(Array-Name) ein Zeiger.


4
int noofele = sizeof(arr)/sizeof(int);ist nur halbwegs besser als das Codieren int noofele = 9;. Die Verwendung sizeof(arr)behält die Flexibilität bei, falls sich die Arraygröße ändert. Benötigt jedoch sizeof(int)ein Update, sollte sich die Art der arr[]Änderung ändern. Besser zu verwenden, sizeof(arr)/sizeof(arr[0])auch wenn der Typ bekannt ist. Unklar, warum intfür noofelevs. verwendet wird size_t, der Typ, der von zurückgegeben wird sizeof().
chux - Monica

19

Das Makro ARRAYELEMENTCOUNT(x), das jeder verwendet, wird falsch ausgewertet . Dies ist realistisch gesehen nur eine heikle Angelegenheit, da Sie keine Ausdrücke haben können, die zu einem Array-Typ führen.

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

ARRAYELEMENTCOUNT(p + 1);

Bewertet tatsächlich als:

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

Wohingegen

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

ARRAYELEMENTCOUNT(p + 1);

Es wird korrekt ausgewertet zu:

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

Dies hat wirklich nicht viel mit der expliziten Größe von Arrays zu tun. Ich habe gerade viele Fehler bemerkt, weil ich nicht wirklich beobachtet habe, wie der C-Präprozessor funktioniert. Sie schließen immer den Makroparameter ein, an dem möglicherweise kein Ausdruck beteiligt ist.


Das ist richtig; Mein Beispiel war schlecht. Aber genau das sollte passieren. Wie bereits erwähnt, p + 1wird dies als Zeigertyp enden und das gesamte Makro ungültig machen (genau wie wenn Sie versucht haben, das Makro in einer Funktion mit einem Zeigerparameter zu verwenden).

Letztendlich spielt der Fehler in diesem speziellen Fall keine Rolle (also verschwende ich nur die Zeit aller; huzzah!), Weil Sie keine Ausdrücke mit einer Art 'Array' haben. Aber wirklich der Punkt über Subprozessor-Evaluierungs-Untertitel ist meiner Meinung nach wichtig.


2
Danke für die Erklärung. Die Originalversion führt zu einem Fehler bei der Kompilierung. Clang meldet "Der tiefgestellte Wert ist kein Array, Zeiger oder Vektor". Dies scheint in diesem Fall vorzuziehen, obwohl Ihre Kommentare zur Auswertungsreihenfolge in Makros gut aufgenommen werden.
Mark Harrison

1
Ich hatte die Compiler-Beschwerde nicht als automatische Benachrichtigung eines falschen Typs angesehen. Vielen Dank!

3
Gibt es einen Grund, nicht zu verwenden (sizeof (x) / sizeof (*x))?
Seriousdev

16

Für mehrdimensionale Arrays ist es etwas komplizierter. Oft definieren Menschen explizite Makrokonstanten, dh

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

Diese Konstanten können aber auch zur Kompilierungszeit mit sizeof ausgewertet werden :

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

Beachten Sie, dass dieser Code in C und C ++ funktioniert. Für Arrays mit mehr als zwei Dimensionen verwenden

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

usw. ad infinitum.


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

Abhängig von der Art arrayhat, die Sie nicht verwenden müssen , sizeof(array) / sizeof(array[0])wenn arrayein Array von entweder char, unsigned charoder signed char- Zitat aus C18,6.5.3.4 / 4: „Wenn sizeof auf einen Operanden angewandt wird, den Typ char, unsigned char hat, oder signed char , (oder eine qualifizierte Version davon) das Ergebnis ist 1. " In diesem Fall können Sie einfach das tun sizeof(array), was in meiner speziellen Antwort erläutert wurde .
RobertS unterstützt Monica Cellio

15

Größe eines Arrays 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
Neugierig, dass Code size_t size_of_elementnoch intmit int n = sizeof (a) / sizeof (a[0]); und nichtsize_t n = sizeof (a) / sizeof (a[0]);
chux - Reinstate Monica

1
Hallo @Yogeesh HT, können Sie bitte den Zweifel von chux beantworten. Ich bin auch sehr neugierig zu wissen, wie int n = Größe von (a) / Größe von (a [0]) die Länge des Arrays angibt und warum wir size_t nicht für die Länge des Arrays verwenden. Kann jemand darauf antworten?
Gehirn

1
@Brain sizeof (a) gibt die Größe aller im Array vorhandenen Elemente an. A sizeof (a [0]) gibt die Größe der ersten Elemente an. Angenommen, a = {1,2,3,4,5}; sizeof (a) = 20bytes (wenn sizeof (int) = 4bytes 5 multiplizieren), sizeof (a [0]) = 4bytes, also 20/4 = 5, dh
Anzahl

2
@YogeeshHT Für sehr große Arrays wie char a[INT_MAX + 1u];, int nwie in verwendet, int n = sizeof (a) / sizeof (a[0]);ist nicht ausreichend (es ist UB). Bei der Verwendung size_t n = sizeof (a) / sizeof (a[0]);tritt dieses Problem nicht auf.
chux

13

Ich würde raten, niemals sizeofeine der beiden unterschiedlichen Größen eines Arrays zu verwenden (auch wenn es verwendet werden kann), entweder in Anzahl der Elemente oder in Bytes. Dies sind die letzten beiden Fälle, die ich hier zeige. Für jede der beiden Größen können die unten gezeigten Makros verwendet werden, um die Sicherheit zu erhöhen. Der Grund ist offensichtlich die Absicht des Codes zu Maintainer zu machen, und die Differenz sizeof(ptr)von sizeof(arr)auf dem ersten Blick (die auf diese Weise geschrieben ist nicht offensichtlich), so dass Fehler sind dann offensichtlich für jeden , den Code zu lesen.


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)(unten definiert) Wird benötigt, ebenso wie -Wsizeof-pointer-divein Buggy (ab April / 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__;  \
                }                                                       \
        )                                                               \
)

Zu diesem Thema sind wichtige Fehler aufgetreten: https://lkml.org/lkml/2015/9/3/428

Ich bin nicht einverstanden mit der von Linus bereitgestellten Lösung, bei der niemals die Array-Notation für Parameter von Funktionen verwendet wird.

Ich mag die Array-Notation als Dokumentation, dass ein Zeiger als Array verwendet wird. Dies bedeutet jedoch, dass eine narrensichere Lösung angewendet werden muss, damit kein fehlerhafter Code geschrieben werden kann.

Aus einem Array haben wir drei Größen, die wir vielleicht wissen möchten:

  • Die Größe der Elemente des Arrays
  • Die Anzahl der Elemente im Array
  • Die Größe in Bytes, die das Array im Speicher verwendet

Die Größe der Elemente des Arrays

Der erste ist sehr einfach und es spielt keine Rolle, ob es sich um ein Array oder einen Zeiger handelt, da dies auf die gleiche Weise erfolgt.

Anwendungsbeispiel:

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

qsort() benötigt diesen Wert als drittes Argument.


Für die beiden anderen Größen, die Gegenstand der Frage sind, möchten wir sicherstellen, dass es sich um ein Array handelt, und die Kompilierung unterbrechen, wenn nicht, da wir bei einem Zeiger falsche Werte erhalten . Wenn die Kompilierung fehlerhaft ist, können wir leicht erkennen, dass es sich nicht um ein Array, sondern um einen Zeiger handelte, und wir müssen den Code nur mit einer Variablen oder einem Makro schreiben, in dem die Größe des Arrays gespeichert ist Array hinter dem Zeiger.


Die Anzahl der Elemente im Array

Dies ist die häufigste und viele Antworten haben Ihnen das typische Makro ARRAY_SIZE geliefert:

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

Da das Ergebnis von ARRAY_SIZE häufig mit vorzeichenbehafteten Variablen vom Typ verwendet wird ptrdiff_t, empfiehlt es sich, eine vorzeichenbehaftete Variante dieses Makros zu definieren:

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

Arrays mit mehr als PTRDIFF_MAXMitgliedern geben ungültige Werte für diese signierte Version des Makros an, aber nach dem Lesen von C17 :: 6.5.6.9 spielen solche Arrays bereits mit dem Feuer. Nur ARRAY_SIZEund size_tsollte in diesen Fällen verwendet werden.

Neuere Versionen von Compilern wie GCC 8 warnen Sie, wenn Sie dieses Makro auf einen Zeiger anwenden, damit es sicher ist (es gibt andere Methoden, um es mit älteren Compilern sicher zu machen).

Es funktioniert, indem die Größe des gesamten Arrays in Bytes durch die Größe jedes Elements geteilt wird.

Anwendungsbeispiele:

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

Wenn diese Funktionen keine Arrays verwenden, sondern sie stattdessen als Parameter erhalten würden, würde der frühere Code nicht kompiliert, sodass es unmöglich wäre, einen Fehler zu haben (vorausgesetzt, dass eine neuere Compilerversion verwendet wird oder ein anderer Trick verwendet wird). und wir müssen den Makroaufruf durch den Wert ersetzen:

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

Die Größe in Bytes, die das Array im Speicher verwendet

ARRAY_SIZE wird häufig als Lösung für den vorherigen Fall verwendet, aber dieser Fall wird selten sicher geschrieben, möglicherweise weil er weniger häufig ist.

Der übliche Weg, um diesen Wert zu erhalten, ist die Verwendung sizeof(arr). Das Problem: das gleiche wie beim vorherigen; Wenn Sie einen Zeiger anstelle eines Arrays haben, wird Ihr Programm verrückt.

Die Lösung des Problems besteht darin, dasselbe Makro wie zuvor zu verwenden, von dem wir wissen, dass es sicher ist (es bricht die Kompilierung ab, wenn es auf einen Zeiger angewendet wird):

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

Wie es funktioniert, ist sehr einfach: Es macht die Teilung rückgängig, die dies ARRAY_SIZEtut. Nach mathematischen Stornierungen erhalten Sie also nur eine sizeof(arr), aber mit der zusätzlichen Sicherheit der ARRAY_SIZEKonstruktion.

Anwendungsbeispiel:

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

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

memset() benötigt diesen Wert als drittes Argument.

Wenn das Array als Parameter (Zeiger) empfangen wird, wird es wie zuvor nicht kompiliert, und wir müssen den Makroaufruf durch den Wert ersetzen:

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

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

Update (23. April 2020): -Wsizeof-pointer-divist fehlerhaft :

Heute habe ich herausgefunden, dass die neue Warnung in GCC nur funktioniert, wenn das Makro in einem Header definiert ist, der kein Systemheader ist. Wenn Sie das Makro in einem Header definieren, der in Ihrem System installiert ist (normalerweise /usr/local/include/oder /usr/include/) ( #include <foo.h>), gibt der Compiler KEINE Warnung aus (ich habe GCC 9.3.0 ausprobiert).

Also haben #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))und wollen wir es sicher machen. Wir benötigen C11 _Static_assert()und einige GCC-Erweiterungen: Anweisungen und Deklarationen in Ausdrücken , __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]);                                \
}                                                                       \
)

Jetzt ARRAY_SIZE()ist es völlig sicher, und daher sind alle seine Derivate sicher.


Update: libbsd bietet __arraycount():

Libbsd stellt das Makro __arraycount()in bereit <sys/cdefs.h>, was unsicher ist, da es keine Klammern enthält, aber wir können diese Klammern selbst hinzufügen, und daher müssen wir nicht einmal die Unterteilung in unseren Header schreiben (warum sollten wir bereits vorhandenen Code duplizieren? ). Dieses Makro ist in einem Systemheader definiert. Wenn wir es also verwenden, müssen wir die obigen Makros verwenden.

#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))

Einige Systeme bieten nitems()in <sys/param.h>statt und einige Systeme bieten beides. Sie sollten Ihr System überprüfen und das System verwenden, das Sie haben, und möglicherweise einige Präprozessorbedingungen für die Portabilität verwenden und beide unterstützen.


Update: Ermöglicht die Verwendung des Makros im Dateibereich:

Leider kann die ({})Erweiterung gcc im Dateibereich nicht verwendet werden. Um das Makro im Dateibereich verwenden zu können, muss sich die statische Zusicherung darin befinden sizeof(struct {}). Multiplizieren Sie es dann mit 0, um das Ergebnis nicht zu beeinflussen. Eine Umwandlung in (int)kann gut sein, um eine zurückgegebene Funktion zu simulieren (int)0(in diesem Fall ist sie nicht erforderlich, kann dann aber für andere Zwecke wiederverwendet werden).

#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
Würde es Ihnen etwas ausmachen zu erklären, warum das Downvote? Dies zeigt eine Lösung für eine unsichere und häufig vorkommende Konstruktion ( sizeof(arr)), die an keiner anderen Stelle gezeigt wird : ARRAY_BYTES(arr).
Cacahuete Frito

2
ARRAY_SIZE ist häufig genug, um frei verwendet zu werden, und ARRAY_BYTES ist in seinem Namen sehr explizit und sollte neben ARRAY_SIZE definiert werden, damit ein Benutzer beide leicht sehen kann. Aufgrund seiner Verwendung glaube ich nicht, dass jemand, der den Code liest, Zweifel daran hat, was es tut. Was ich damit gemeint habe, ist, keine einfache zu verwenden sizeof, sondern stattdessen diese Konstruktionen zu verwenden; Wenn Sie diese Konstruktionen jedes Mal schreiben
möchten

3
..., also stehe ich zu der Hauptschlussfolgerung: Eine einzelne sizeofist eindeutig unsicher (Gründe sind in der Antwort enthalten), und nicht Makros zu verwenden, sondern die von mir bereitgestellten Konstruktionen jedes Mal zu verwenden, ist noch unsicherer, also der einzige Weg ist Makros.
Cacahuete Frito

3
@ MarkHarrison Ich kenne den Unterschied zwischen Zeigern und Arrays. Aber es gab Zeiten, in denen ich eine Funktion hatte, die ich später in kleine Funktionen umgestaltete, und was zuerst ein Array, später ein Zeiger war, und das ist ein Punkt, an dem man, wenn man vergisst, die Größe zu ändern, sie verschraubt und es leicht zu sehen ist eine von diesen.
Cacahuete Frito

3
@hyde Auch dass ich den Unterschied kenne, bedeutet nicht, dass jeder den Unterschied kennt, und warum nicht etwas verwenden, das im Grunde 100% dieser Fehler beseitigt? Dieser Fehler hat es fast in Linux geschafft; es ist bei Linus angekommen, was bedeutet, dass es eine Menge Prüfung bestanden hat, und was auch bedeutet, dass derselbe Fehler es in Linux in einem anderen Teil des Kernels geschafft hat, wie er sagt.
Cacahuete Frito

11

"Sie haben eine subtile Art eingeführt, sich in den Fuß zu schießen."

C 'native' Arrays speichern ihre Größe nicht. Es wird daher empfohlen, die Länge des Arrays in einer separaten Variablen / const zu speichern und sie immer dann zu übergeben, wenn Sie das Array übergeben, d. H.

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

Sie sollten native Arrays immer vermeiden (es sei denn, Sie können sich in diesem Fall nicht um Ihren Fuß kümmern). Wenn Sie C ++ schreiben, verwenden Sie den 'Vektor'-Container der STL . "Im Vergleich zu Arrays bieten sie fast die gleiche Leistung" und sind weitaus nützlicher!

// 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();

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


Ich habe gelesen, dass der richtige Weg, ganzzahlige Konstanten in C zu deklarieren, darin besteht, eine enumDeklaration zu verwenden.
Raffi Khatchadourian

Die Frage bezieht sich auf C, nicht auf C ++. Also keine STL.
Cacahuete Frito

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

6
Beachten Sie, dass dies nur für tatsächliche Arrays funktioniert, nicht für Zeiger, die zufällig auf Arrays verweisen.
David Schwartz

5

Wenn Sie dies wirklich tun möchten, um Ihr Array zu umgehen, empfehle ich, eine Struktur zu implementieren, um einen Zeiger auf den Typ zu speichern, von dem Sie ein Array und eine Ganzzahl, die die Größe des Arrays darstellt, möchten. Dann können Sie das an Ihre Funktionen weitergeben. Weisen Sie diesem Zeiger einfach den Wert der Array-Variablen (Zeiger auf das erste Element) zu. Dann können Sie Array.arr[i]das i-te Element abrufen und Array.sizedie Anzahl der Elemente im Array ermitteln.

Ich habe einen Code für Sie eingefügt. Es ist nicht sehr nützlich, aber Sie könnten es um weitere Funktionen erweitern. Um ehrlich zu sein, sollten Sie, wenn dies die gewünschten Dinge sind, die Verwendung von C beenden und eine andere Sprache mit diesen integrierten Funktionen verwenden.

/* 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
Code, der strcpy(d.name, name);ohne Behandlung des Überlaufs funktioniert, kann nicht hochgestuft werden .
chux

4

Am besten speichern Sie diese Informationen beispielsweise in einer Struktur:

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

Implementieren Sie alle erforderlichen Funktionen wie Erstellen, Zerstören, Überprüfen der Gleichheit und alles andere, was Sie benötigen. Es ist einfacher, als Parameter zu übergeben.


6
Irgendein Grund für int elementsvs. size_t elements?
chux

3

Die Funktion sizeofgibt die Anzahl der Bytes zurück, die von Ihrem Array im Speicher verwendet werden. Wenn Sie die Anzahl der Elemente in Ihrem Array berechnen möchten, sollten Sie diese Anzahl durch den sizeofVariablentyp des Arrays teilen . Angenommen int array[10];, wenn die Ganzzahl vom Variablentyp in Ihrem Computer 32 Bit (oder 4 Byte) beträgt, sollten Sie Folgendes tun, um die Größe Ihres Arrays zu ermitteln:

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

2

Sie können den &Operator verwenden. Hier ist der Quellcode:

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

Hier ist die Beispielausgabe

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

7
Ich habe nicht abgelehnt, aber das ist, als würde man einen Nagel mit einem Ziegelstein schlagen, weil man keinen Hammer neben sich bemerkt hat. Außerdem neigen die Leute dazu, nicht initialisierte Variablen zu verwenden ... aber hier denke ich, dass es Ihrem Zweck gut genug dient.
Dmitri

2
@Dmitri hier werden keine nicht initialisierten Variablen abgerufen
MM

1
Hmmm. Zeigersubtraktion führt zu ptrdiff_t. sizeof()führt zu size_t. C definiert nicht , welcher breiter oder höher / gleicher Rang ist. Die Art des Quotienten ((char *)(&a+1)-(char *)a)/(sizeof(a[0]))ist also nicht sicher size_tund daher kann das Drucken mit zzu UB führen. Einfach zu benutzen printf("The size of array a is %zu\n", sizeof a/sizeof a[0]);ist ausreichend.
chux

1
(char *)(&a+1)-(char *)aist keine Konstante und kann zur Laufzeit auch bei fester Größe berechnet werden a[10]. sizeof(a)/sizeof(a[0])wird in diesem Fall konstant zur Kompilierungszeit ausgeführt.
chux

1

Die einfachste Antwort:

#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

Eine elegantere Lösung wird sein

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

0

Neben den bereits gegebenen Antworten möchte ich auf einen Sonderfall durch die Verwendung von hinweisen

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

Wenn aes sich entweder um ein Array von charhandelt unsigned charoder signed charSie es nicht sizeofzweimal sizeofverwenden müssen, ergibt sich immer ein Ausdruck mit einem Operanden dieser Typen 1.

Zitat aus C18,6.5.3.4 / 4:

Wenn sizeofauf einen Operanden angewandt , die Aktivität hat char, unsigned charoder signed char, (oder eine qualifizierte Version davon) das Ergebnis ist 1.“

Somit sizeof(a) / sizeof (a[0])wäre äquivalent , NUMBER OF ARRAY ELEMENTS / 1wenn aein Array des Typs char, unsigned charoder signed char. Die Division durch 1 ist redundant.

In diesem Fall können Sie einfach abkürzen und Folgendes tun:

sizeof(a)

Zum Beispiel:

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

Wenn Sie einen Beweis wollen, finden Sie hier einen Link zu GodBolt .


Trotzdem behält die Abteilung die Sicherheit bei, wenn sich der Typ erheblich ändert (obwohl diese Fälle selten sind).


1
Sie ziehen es wahrscheinlich vor, weiterhin ein Makro mit der Division anzuwenden, da sich der Typ in Zukunft möglicherweise ändert (obwohl dies möglicherweise unwahrscheinlich ist) und die Division zur Kompilierungszeit bekannt ist, sodass der Compiler sie wegoptimiert (falls dies nicht geändert wird) Ihr Compiler).
Cacahuete Frito

1
@CacahueteFrito Ja, darüber habe ich inzwischen auch nachgedacht. Ich nahm es als Randnotiz in die Antwort. Vielen Dank.
RobertS unterstützt Monica Cellio

-1

Hinweis: Dieser kann Ihnen undefiniertes Verhalten geben, wie von MM im Kommentar angegeben.

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

Weitere Details finden Sie hier und auch hier .


2
Dies ist technisch undefiniertes Verhalten; Der *Operator darf nicht auf einen Past-the-End-Zeiger angewendet werden
MM

3
"undefiniertes Verhalten" bedeutet, dass der C-Standard das Verhalten nicht definiert. Wenn Sie es in Ihrem Programm versuchen, kann alles passieren
MM

@MM sagen Sie, *(&a+1) - a;ist anders als (&a)[1] - a;oben, nicht beide *(&a+1)und (&a)[1]zählen als 1 nach dem Ende?
QuentinUK

@QuentinUK Ihre beiden Ausdrücke sind beide gleich, x[y]definiert als*(x + (y))
MM

@ MM Ich dachte schon. Aber die andere Antwort von Arjun Sreedharan hat 38 Pfeile und dies hat -1. Und Arjun Sreedharans Antwort erwähnt kein undefiniertes Verhalten.
QuentinUK
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.