Makro in Array-Größe, das Zeiger ablehnt

Lesezeit: 8 Minuten

Benutzeravatar von nneonneo
nneonneo

Das häufig gelehrte Standard-Makro in Array-Größe ist

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

oder eine äquivalente Formation. Solche Dinge sind jedoch stillschweigend erfolgreich, wenn ein Zeiger übergeben wird, und liefern Ergebnisse, die zur Laufzeit plausibel erscheinen können, bis die Dinge auf mysteriöse Weise auseinanderfallen.

Dieser Fehler wird nur allzu leicht gemacht: Eine Funktion, die eine lokale Array-Variable hat, wird umgestaltet, wodurch ein wenig Array-Manipulation in eine neue Funktion verschoben wird, die mit dem Array als Parameter aufgerufen wird.

Die Frage ist also: Gibt es ein “hygienisches” Makro, um den Missbrauch zu erkennen ARRAYSIZE Makro in C, vorzugsweise zur Kompilierzeit? In C++ würden wir einfach ein Template verwenden, das nur auf Array-Argumente spezialisiert ist; In C brauchen wir anscheinend eine Möglichkeit, Arrays und Zeiger zu unterscheiden. (Wenn ich zum Beispiel Arrays ablehnen wollte, würde ich einfach zB (arr=arr, ...) weil die Array-Zuweisung illegal ist).

  • Dies wird grob, da Arrays in praktisch allen Kontexten in Zeiger zerfallen.

    Benutzer395760

    18. Oktober 2013 um 15:13 Uhr


  • Warum sollte jemand ein solches Makro brauchen? Dies funktioniert nur mit Arrays, die im Code durch eine feste Größe definiert wurden. Warum sollten Sie berechnen, was Sie geschrieben haben? Wenn die Antwort lautet “Vielleicht befinden Sie sich in einem anderen Teil Ihres Codes und Sie haben diese Informationen nicht mehr”, lautet meine nachfolgende Frage: Wie ist das möglich, wenn das Array nicht zu einem Zeiger zerfällt, in einem nicht seltsamen, unspezifischen Fall -damit-dass-ein-Stück-Code-passiert-wurde?

    – Eregrith

    18. Oktober 2013 um 15:18 Uhr

  • @Eregrith Im weiteren Sinne kann dieser Standpunkt auch lauten: “Warum sollte jemand jemals eine Berechnung oder Metaprogrammierung zur Kompilierzeit benötigen”? Die Idee, dass “Sie wissen, was Sie geschrieben haben”, ist beides lächerlich und nutzlos. Kein Gesetz schreibt vor, dass Sie es überhaupt von Hand schreiben mussten.

    – Leuschenko

    18. Oktober 2013 um 15:48 Uhr


  • @Eregrith Ich würde absolut nichts falsch daran sehen, zu schreiben char a[MAGIC_STUFF(COMPLICATED(X, Z+FOO(G)))]; und das nicht weiter unten noch einmal abtippen wollen. Wenn die Informationen vorhanden sind und das Toolset vorhanden ist, verwenden Sie es.

    – Leuschenko

    18. Oktober 2013 um 15:55 Uhr


  • @Eregrith: Mir fallen mindestens zwei Situationen ein: (1) Die Arraygröße ist möglicherweise nicht angegeben, kann aber aus der Initialisierungsliste abgeleitet werden; (2) Es kann nützlich sein, ein Makro zu haben, wie #define SEND_FIXED_COMMAND(cmd) send_command((arr), sizeof (arr)) um zu vermeiden, dass Sie sowohl den Namen des Arrays als auch den Namen einer Konstante angeben müssen, die die Größe des Arrays angibt.

    – Superkatze

    1. Juni 2015 um 6:17 Uhr

Benutzeravatar von ouah
ouah

Der Linux-Kernel verwendet eine nette Implementierung von ARRAY_SIZE um dieses Problem zu lösen:

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

mit

#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))

und

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

Natürlich ist dies nur in GNU C portierbar, da es zwei Eigenheiten verwendet:
typeof Betreiber und __builtin_types_compatible_p Funktion. Auch verwendet es ihre “berühmten” BUILD_BUG_ON_ZERO Makro, das nur in GNU C gültig ist.

Unter der Annahme einer Kompilierzeitauswertungsanforderung (was wir wollen), kenne ich keine portable Implementierung dieses Makros.

Eine “semi-portable” Implementierung (und die nicht alle Fälle abdecken würde) ist:

#define ARRAY_SIZE(arr)  \
    (sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr)))

mit

#define IS_ARRAY(arr)  ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e)  \
    (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))

Mit gcc Dies gibt keine Warnung aus, wenn das Argument ein Array in ist -std=c99 -Wall aber -pedantic würde warnen. Der Grund ist IS_ARRAY Ausdruck ist kein ganzzahliger konstanter Ausdruck (Umwandlung in Zeigertypen und Indexoperatoren sind in ganzzahligen konstanten Ausdrücken nicht zulässig) und die Bitfeldbreite in STATIC_EXP erfordert einen ganzzahligen konstanten Ausdruck.

  • Oh, schön, das ist ein Juwel. Ich hätte denken sollen, dass die Linux-Kernel-Entwickler das herausfinden würden.

    – nneonneo

    18. Oktober 2013 um 20:23 Uhr

Benutzeravatar von David Ranieri
David Ranieri

Diese Version von ARRAYSIZE() kehrt zurück 0 Wenn arr ist ein Zeiger und die Größe, wenn es sich um ein reines Array handelt

#include <stdio.h>

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)

int main(void)
{
    int a[5];
    int *b = a;
    int n = 10;
    int c[n]; /* a VLA */

    printf("%zu\n", ARRAYSIZE(a));
    printf("%zu\n", ARRAYSIZE(b));
    printf("%zu\n", ARRAYSIZE(c));
    return 0;
}

Ausgabe:

5
0
10

Wie von Ben Jackson hervorgehoben, können Sie eine Laufzeitausnahme erzwingen (Dividieren durch 0)

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))

Leider können Sie einen Kompilierzeitfehler nicht erzwingen (die Adresse von arg muss zur Laufzeit verglichen werden)

  • Noch besser wäre es, wenn Sie im schlimmsten Fall einen Kompilierzeitfehler (durch 0 teilen?) erhalten könnten.

    – Ben Jackson

    18. Oktober 2013 um 15:58 Uhr

  • Was ist der Bedarf IS_INDEXABLE(arg)? Soweit ich das beurteilen kann, gibt dies immer ungleich Null zurück

    – Digitales Trauma

    18. Oktober 2013 um 17:23 Uhr

  • @DigitalTrauma, weil es einen Fehler auslöst, wenn das Argument kein Array (oder ein Zeiger) ist. error: subscripted value is neither array nor pointer nor vector

    – David Ranieri

    18. Oktober 2013 um 17:31 Uhr

  • @AlterMann – Danke – ja, das ist ein netter zusätzlicher Test zum Einfügen

    – Digitales Trauma

    18. Oktober 2013 um 17:36 Uhr

  • Ein Array 🙂 Die Adresse des Arrays ist die gleiche wie der Wert des Arrays, da der Wert in den Zeiger auf das erste Element zerfällt.

    – tangr

    19. Oktober 2013 um 4:55 Uhr

Mit C11 können wir Arrays und Zeiger mit unterscheiden _Genericaber ich habe nur einen Weg gefunden, dies zu tun, wenn Sie den Elementtyp angeben:

#define ARRAY_SIZE(A, T) \
    _Generic(&(A), \
            T **: (void)0, \
            default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0])))


int a[2];
printf("%zu\n", ARRAY_SIZE(a, int));

Das Makro prüft: 1) Zeiger auf A ist kein Zeiger auf Zeiger. 2) Zeiger auf Element ist Zeiger auf T. Es wertet zu (void)0 und schlägt statisch mit Zeigern fehl.

Es ist eine unvollkommene Antwort, aber vielleicht kann ein Leser sie verbessern und diesen Typparameter loswerden!

  • Anstatt zu überprüfen, ob “Zeiger auf A kein Zeiger auf Zeiger” ist, warum nicht direkt überprüfen, ob Zeiger auf A Zeiger auf Array ist? _Generic(&(A), T(*)[sizeof(A) / sizeof((A)[0])]: sizeof(A) / sizeof((A)[0])) Das macht den zweiten Test überflüssig und ich finde die Fehlermeldung error: '_Generic' selector of type 'int **' is not compatible with any association ist besser verständlich als error: invalid use of void expression. Leider habe ich immer noch keine Ahnung, wie ich diesen Typparameter loswerden kann. 🙁

    – T.S

    16. März 2021 um 20:50 Uhr

  • Wenn Sie den Elementtyp übergeben können, ist es eigentlich ganz einfach. #define ARRAYSIZE(arr, T) _Generic(&(arr), T(*)[sizeof(arr)/sizeof(arr[0])]: sizeof(arr)/sizeof(arr[0])) Dadurch wird ein Array-Zeiger auf ein Array des angegebenen Typs erstellt. Wenn der übergebene Parameter kein Array des richtigen Typs oder der richtigen Größe ist, erhalten Sie Compilerfehler. 100 % tragbarer Standard C.

    – Ludin

    1. Juli 2021 um 13:57 Uhr

Benutzeravatar von 4566976
4566976

Änderung der Antwort von bluss unter Verwendung von typeof anstelle eines Typparameters:

#define ARRAY_SIZE(A) \
    _Generic(&(A), \
    typeof((A)[0]) **: (void)0, \
    default: sizeof(A) / sizeof((A)[0]))

Benutzeravatar von Adam Rosenfield
Adam Rosenfeld

Hier ist eine mögliche Lösung mit einer GNU-Erweiterung namens Anweisungsausdrücke:

#define ARRAYSIZE(arr) \
    ({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \
     sizeof(arr) / sizeof((arr)[0]);})

Dies verwendet eine statische Assertion, um dies zu bestätigen sizeof(arr) != sizeof(void*). Dies hat eine offensichtliche Einschränkung – Sie können dieses Makro nicht auf Arrays verwenden, deren Größe zufällig genau ein Zeiger ist (z Plattform). Aber diese besonderen Fälle können leicht genug umgangen werden.

Diese Lösung ist nicht auf Plattformen portierbar, die diese GNU-Erweiterung nicht unterstützen. In diesen Fällen würde ich empfehlen, nur das Standardmakro zu verwenden und sich keine Sorgen zu machen, dass versehentlich Zeiger an das Makro übergeben werden.

Hier ist eine andere, die sich auf den gcc stützt Art der Erweiterung:

#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \
                         sizeof(arr) / sizeof(arr[0]);})

Dies funktioniert, indem versucht wird, ein identisches Objekt einzurichten und es mit einem als Initialisierer bezeichneten Array zu initialisieren. Wird ein Array übergeben, freut sich der Compiler. Wenn ein Zeiger übergeben wird, beschwert sich der Compiler mit:

arraysize.c: In function 'main':
arraysize.c:11: error: array index in non-array initializer
arraysize.c:11: error: (near initialization for 'p_is_a_pointer')

Benutzeravatar von not-a-user
kein Benutzer

mein persönlicher Favorit, probiert gcc 4.6.3 und 4.9.2:

#define STR_(tokens) # tokens

#define ARRAY_SIZE(array) \
    ({ \
        _Static_assert \
        ( \
            ! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), \
            "ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" \
        ); \
        sizeof(array) / sizeof((array)[0]); \
    })

/*
 * example
 */

#define not_an_array ((char const *) "not an array")

int main () {
    return ARRAY_SIZE(not_an_array);
}

Compiler druckt

x.c:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array"

  • Kleiner Haken: __builtin_types_compatible_p version schlägt für ein Array fehl, das sich hinter einem const-Zeiger befindet (weil const- und non-const-Typen nicht übereinstimmen)

    – MM

    31. Juli 2018 um 4:03 Uhr

1417020cookie-checkMakro in Array-Größe, das Zeiger ablehnt

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy