1407 Stimmen

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

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

Das heißt, die Anzahl der Elemente, die das Array aufnehmen kann?

6 Stimmen

0 Stimmen

Eine Antwort ist Gegenstand von eine Metafrage .

1 Stimmen

Wenn die Antworten keinen Sinn ergeben, denken Sie daran, dass beim Kompilieren von C in Assembler die Größeninformation des Arrays verloren geht. Wenn Sie etwas deklarieren wie int foo[5]; dass 5 nirgendwo in Ihrer kompilierten ausführbaren Datei erscheint.

1711voto

Mark Harrison Punkte 281807

Kurzfassung:

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 die Funktion sizeof Betreiber:

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 in dem Array zu bestimmen, können wir dividieren die Gesamtgröße des Arrays durch die Größe des Arrayelements teilen. Sie können dies mit dem Typ tun, wie hier:

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

und erhalten die richtige Antwort (68 / 4 = 17), aber wenn die Art der a geändert haben, würden Sie einen bösen Fehler haben, wenn Sie vergessen haben, die die sizeof(int) auch.

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 nun einfach die Parameter den Array-Namen in einem Makro parametrisieren und erhalten:

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

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

0 Stimmen

Aber ist nicht sizeof(int) leistungsfähiger als sizeof(*int_arr) während der Laufzeit? Oder ist der übersetzte Code identisch?

7 Stimmen

Der erzeugte Code wird identisch sein, da der Compiler den Typ von *int_arr zur Kompilierzeit kennt (und damit den Wert von sizeof(*int_arr)). Es wird eine Konstante sein, und der Compiler kann entsprechend optimieren.

0 Stimmen

Welchen Compiler verwenden Sie?

1096voto

Elideb Punkte 10562

El sizeof Weg ist der richtige Weg wenn haben Sie es mit Arrays zu tun, die nicht als Parameter empfangen werden. Ein Array, das als Parameter an eine Funktion gesendet wird, wird als Zeiger behandelt, also sizeof gibt die Größe des Zeigers anstelle der Größe des Arrays zurück.

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

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

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

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

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

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

1 Stimmen

Aus Neugier behandelt der Compiler Arrays das gleiche wie Zeiger, wenn sie parametrisiert sind?

13 Stimmen

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

25 Stimmen

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

182voto

Magnus Hoff Punkte 20705

Es ist erwähnenswert, dass sizeof hilft nicht, wenn es sich um einen Array-Wert handelt, der in einen Zeiger zerfallen ist: Auch wenn 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 nicht an irgendetwas anderes über das Array, das zu seiner Initialisierung verwendet 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 Stimmen

@ Magnus: Der Standard definiert sizeof so, dass es die Anzahl der Bytes im Objekt liefert und dass sizeof (char) immer eins ist. Die Anzahl der Bits in einem Byte ist implementierungsspezifisch. Bearbeiten: ANSI C++ Standard Abschnitt 5.3.3 Sizeof: "Der sizeof-Operator gibt die Anzahl der Bytes in der Objektrepräsentation seines Operanden an. [...] sizeof (char), sizeof (signed char) und sizeof (unsigned char) sind 1; das Ergebnis von sizeof, angewandt auf einen anderen Grundtyp, ist implementierungsabhängig."

1 Stimmen

Abschnitt 1.6 Das C++-Speichermodell: "Die grundlegende Speichereinheit im C++-Speichermodell ist das Byte. Ein Byte ist mindestens groß genug, um jedes Mitglied des grundlegenden Zeichensatzes der Ausführung zu enthalten, und besteht aus einer zusammenhängenden Folge von Bits, deren Anzahl durch die Implementierung festgelegt ist."

2 Stimmen

Ich erinnere mich, dass der CRAY C mit char von 32 Bit. Die Norm besagt lediglich, dass ganzzahlige Werte von 0 bis 127 dargestellt werden können, wobei der Bereich mindestens entweder -127 bis 127 (char ist vorzeichenbehaftet) oder 0 bis 255 (char ist vorzeichenlos) beträgt.

64voto

unwind Punkte 377331

El sizeof "Trick" ist die beste Methode, die ich kenne, mit einer kleinen, aber (für mich ein großes Ärgernis) wichtigen Änderung bei der Verwendung von Klammern.

Wie der Wikipedia-Eintrag deutlich macht, ist C's sizeof ist keine Funktion; es ist eine Betreiber . Er benötigt also keine Klammern um sein Argument, es sei denn, das Argument ist ein Typname. Dies ist leicht zu merken, da es das Argument wie einen Cast-Ausdruck aussehen lässt, der ebenfalls Klammern verwendet.

Also: Wenn Sie Folgendes haben:

int myArray[10];

Sie können die Anzahl der Elemente mit folgendem Code ermitteln:

size_t n = sizeof myArray / sizeof *myArray;

Das liest sich für mich viel einfacher als die Alternative mit Klammern. Ich bevorzuge auch die Verwendung des Sternchens im rechten Teil der Division, da dies prägnanter ist als eine Indexierung.

Natürlich geschieht dies alles zur Kompilierzeit, so dass man sich keine Sorgen machen muss, dass die Division die Leistung des Programms beeinträchtigt. Verwenden Sie also diese Form, wo immer Sie können.

Es ist immer am besten, die sizeof auf ein tatsächliches Objekt beziehen, wenn Sie eines haben, und nicht auf einen Typ, da Sie dann nicht befürchten müssen, einen Fehler zu machen und den falschen Typ anzugeben.

Nehmen wir an, Sie haben eine Funktion, die einige Daten als Bytestrom ausgibt, z. B. über ein Netzwerk. Nennen wir die Funktion send() und als Argumente einen Zeiger auf das zu sendende Objekt und die Anzahl der Bytes im Objekt angeben. Der Prototyp wird also:

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

Und dann müssen Sie eine Ganzzahl senden, also programmieren Sie das so:

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

Jetzt haben Sie eine subtile Möglichkeit eingeführt, sich selbst in den Fuß zu schießen, indem Sie den Typ von foo an zwei Stellen. Wenn sich eine Stelle ändert, die andere aber nicht, bricht der Code. Machen Sie es also immer so:

send(&foo, sizeof foo);

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

0 Stimmen

Handelt es sich dabei um identische Anweisungen auf der Prozessorebene? Hat sizeof(int) benötigen weniger Anweisungen als sizeof(foo) ?

0 Stimmen

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

0 Stimmen

@Aidiakapi Das stimmt nicht, denken Sie an die C99 VLAs.

43voto

alx Punkte 3385

Ich würde raten, niemals zu verwenden sizeof (auch wenn es verwendet werden kann), um eine der beiden verschiedenen Größen eines Arrays zu erhalten, entweder in Anzahl der Elemente oder in Bytes, was die beiden letzten Fälle sind, die ich hier zeige. Für jede der beiden Größen können die unten gezeigten Makros verwendet werden, um sie sicherer zu machen. Der Grund dafür ist, dass die Intention des Codes für die Betreuer deutlich wird, und der Unterschied sizeof(ptr) de sizeof(arr) auf den ersten Blick (was in dieser Form nicht offensichtlich ist), so dass Fehler für jeden, der den Code liest, offensichtlich sind.


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) + must_be_array(arr))

#define ARRAY_SBYTES(arr)   ((ssize_t)ARRAY_BYTES(arr))

must_be_array(arr) (nachstehend definiert) ist erforderlich als -Wsizeof-pointer-div ist fehlerhaft (Stand: April 2020):

#define is_same_type(a, b)  __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)       (!is_same_type((arr), &(arr)[0]))
#define must_be(e)          (                                           \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        static_assert(e);                               \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)
#define must_be_array(arr)  must_be(is_array(arr))

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

Ich bin nicht einverstanden mit der Lösung, die Linus anbietet, nämlich niemals die Array-Notation für Parameter von Funktionen zu verwenden.

Ich mag die Array-Notation als Dokumentation, dass ein Zeiger als Array verwendet wird. Aber das bedeutet, dass eine narrensichere Lösung angewendet werden muss, damit es unmöglich ist, fehlerhaften Code zu schreiben.

Von einem Array haben wir drei Größen, die wir vielleicht wissen wollen:

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

Die Größe der Elemente des Arrays

Die erste ist sehr einfach, und es spielt keine Rolle, ob wir es mit einem Array oder einem Zeiger zu tun haben, denn es wird auf die gleiche Weise gemacht.

Beispiel für die Verwendung:

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

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


Bei den anderen beiden Größen, um die es in der Frage geht, wollen wir sicherstellen, dass wir es mit einem Array zu tun haben, und die Kompilierung abbrechen, wenn dies nicht der Fall ist, denn wenn wir es mit einem Zeiger zu tun haben, werden wir falsche Werte erhalten. Wenn die Kompilierung unterbrochen wird, können wir leicht erkennen, dass wir es nicht mit einem Array, sondern mit einem Zeiger zu tun hatten, und wir müssen den Code einfach mit einer Variablen oder einem Makro schreiben, das die Größe des Arrays hinter dem Zeiger speichert.


Die Anzahl der Elemente im Array

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

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

Angesichts der Tatsache, dass das Ergebnis von ARRAY_SIZE wird üblicherweise mit vorzeichenbehafteten Variablen des Typs ptrdiff_t ist es sinnvoll, eine vorzeichenbehaftete Variante dieses Makros zu definieren:

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

Arrays mit mehr als PTRDIFF_MAX Mitglieder werden für diese signierte Version des Makros ungültige Werte liefern, aber nach der Lektüre von C17::6.5.6.9 sind solche Arrays bereits ein Spiel mit dem Feuer. Nur ARRAY_SIZE y size_t sollte in diesen Fällen verwendet werden.

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

Dazu wird die Größe des gesamten Arrays in Bytes durch die Größe der einzelnen Elemente geteilt.

Beispiele für die Verwendung:

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 würden, sondern sie stattdessen als Parameter bekämen, würde der frühere Code nicht kompiliert werden, so dass es unmöglich wäre, einen Fehler zu haben (vorausgesetzt, dass eine aktuelle Compiler-Version verwendet wird oder dass 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 belegt

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

Der übliche Weg, diesen Wert zu erhalten, ist die Verwendung von sizeof(arr) . Das Problem: dasselbe wie beim vorigen Beispiel; wenn Sie einen Zeiger statt eines Arrays haben, wird Ihr Programm durchdrehen.

Die Lösung des Problems besteht in der Verwendung desselben Makros wie zuvor, 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))

Da das Ergebnis von ARRAY_BYTES manchmal mit der Ausgabe von Funktionen verglichen wird, die ssize_t ist es sinnvoll, eine vorzeichenbehaftete Variante dieses Makros zu definieren:

#define ARRAY_SBYTES(arr)       ((ssize_t)ARRAY_BYTES(arr))

Die Funktionsweise ist sehr einfach: Sie hebt die Trennung auf, die ARRAY_SIZE tut, so dass man nach mathematischen Annullierungen nur noch einen sizeof(arr) aber mit der zusätzlichen Sicherheit des ARRAY_SIZE Konstruktion.

Beispiel für die Verwendung:

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, lässt es sich nicht kompilieren, und wir müssen den Makroaufruf durch den Wert ersetzen, wie zuvor:

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

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

Aktualisierung (23/apr/2020): -Wsizeof-pointer-div ist fehlerhaft :

Heute habe ich herausgefunden, dass die neue Warnung im 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/ o /usr/include/ ) ( #include <foo.h> ), wird der Compiler KEINE Warnung ausgeben (ich habe GCC 9.3.0 ausprobiert).

Wir haben also #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) und wollen es sicher machen. Wir werden C2X brauchen static_assert() und einige GCC-Erweiterungen: Anweisungen und Erklärungen in Ausdrücken , __builtin_types_compatible_p :

#include <assert.h>

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)           (!is_same_type((arr), &(arr)[0]))
#define Static_assert_array(arr) static_assert(is_array(arr))

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

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


Update: libbsd bietet __arraycount() :

Libbsd liefert das Makro __arraycount() en <sys/cdefs.h> , das unsicher ist, weil ihm ein Klammerpaar fehlt, aber wir können diese Klammern selbst hinzufügen und brauchen daher die Division nicht einmal in unseren Header zu schreiben (warum sollten wir Code duplizieren, der bereits existiert?). Dieses Makro ist in einem Systemheader definiert, so dass wir, wenn wir es verwenden, gezwungen sind, die obigen Makros zu verwenden.

#inlcude <assert.h>
#include <stddef.h>
#include <sys/cdefs.h>
#include <sys/types.h>

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)           (!is_same_type((arr), &(arr)[0]))
#define Static_assert_array(arr) static_assert(is_array(arr))

#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))
#define ARRAY_SBYTES(arr)       ((ssize_t)ARRAY_BYTES(arr))

Einige Systeme bieten nitems() en <sys/param.h> und einige Systeme bieten beides. Sie sollten Ihr System überprüfen und dasjenige verwenden, das Sie haben, und vielleicht einige Präprozessor-Bedingungen für die Portabilität verwenden und beide unterstützen.


Aktualisierung: Erlaubt die Verwendung des Makros auf Dateiebene:

Leider ist die ({}) Die gcc-Erweiterung kann nicht im Dateisystem verwendet werden. Um das Makro im Dateisystem verwenden zu können, muss die statische Assertion innerhalb von sizeof(struct {}) . Dann multiplizieren Sie ihn mit 0 nicht zu beeinflussen das Ergebnis. Ein Wurf nach (int) könnte eine Funktion simuliert werden die Folgendes zurückgibt (int)0 (in diesem Fall ist es nicht notwendig, aber dann ist es für andere Dinge wiederverwendbar ist).

Außerdem ist die Definition von ARRAY_BYTES() kann ein wenig vereinfacht werden.

#include <assert.h>
#include <stddef.h>
#include <sys/cdefs.h>
#include <sys/types.h>

#define is_same_type(a, b)     __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)          (!is_same_type((arr), &(arr)[0]))
#define must_be(e)             (                                        \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        static_assert(e);                               \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)
#define must_be_array(arr)      must_be(is_array(arr))

#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) + must_be_array(arr))
#define ARRAY_SBYTES(arr)       ((ssize_t)ARRAY_BYTES(arr))

Anmerkungen:

Dieser Code verwendet die folgenden Erweiterungen, die absolut notwendig sind und deren Vorhandensein absolut notwendig ist, um Sicherheit zu erreichen. Wenn Ihr Compiler nicht über diese oder ähnliche Erweiterungen verfügt, können Sie dieses Sicherheitsniveau nicht erreichen.

Ich verwende auch die folgende C2X-Funktion. Das Fehlen dieser Funktion bei Verwendung eines älteren Standards kann jedoch mit einigen schmutzigen Tricks umgangen werden (siehe z.B.: Was bedeutet ":-!!" in C-Code? ) (in C11 haben Sie auch static_assert() aber sie erfordert eine Nachricht).

4 Stimmen

Würden Sie bitte erklären, warum die Abwertung? Dies zeigt eine Lösung für eine unsichere und gängige Konstruktion ( sizeof(arr) ), die nirgendwo anders gezeigt wird: ARRAY_BYTES(arr) .

3 Stimmen

ARRAY_SIZE ist allgemein genug, um frei verwendet zu werden, und ARRAY_BYTES ist sehr eindeutig in seinem Namen, sollte neben ARRAY_SIZE definiert werden, so dass ein Benutzer beide leicht erkennen kann, und durch seine Verwendung, ich glaube nicht, dass jemand, der den Code liest, Zweifel daran hat, was es tut. Was ich meinte, ist, dass man nicht einfach ein sizeof , aber verwenden Sie stattdessen diese Konstruktionen; wenn Sie diese Konstruktionen jedes Mal schreiben wollen, werden Sie wahrscheinlich einen Fehler machen (sehr häufig, wenn Sie kopieren und einfügen, und auch sehr häufig, wenn Sie sie jedes Mal schreiben, weil sie viele Klammern haben)...

4 Stimmen

..., so bleibe ich bei der wichtigsten Schlussfolgerung: ein einziger sizeof ist eindeutig unsicher (die Gründe stehen in der Antwort), und keine Makros zu verwenden, sondern die Konstruktionen zu benutzen, die ich jedes Mal zur Verfügung gestellt habe, ist sogar noch unsicherer, so dass der einzige Weg Makros sind.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X