wake-up-neo.com

Makro in Array-Größe, das Zeiger zurückweist

Das häufig gelehrte Standardmakro mit Makrogröße ist

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

oder eine gleichwertige Formation. Dies gelingt jedoch stillschweigend, wenn ein Zeiger übergeben wird, und liefert Ergebnisse, die zur Laufzeit plausibel erscheinen können, bis die Dinge auf mysteriöse Weise auseinanderfallen.

Es ist zu einfach, diesen Fehler zu begehen: Eine Funktion, die über eine lokale Array-Variable verfügt, wird umgestaltet, wodurch ein bisschen Array-Bearbeitung 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 des ARRAYSIZE-Makros in C zu ermitteln, vorzugsweise zur Kompilierzeit? In C++ verwenden wir einfach eine Vorlage, die nur für Array-Argumente spezialisiert ist. In C scheint es so, als müssten wir Arrays und Zeiger unterscheiden. (Wenn ich beispielsweise Arrays ablehnen wollte, würde ich beispielsweise (arr=arr, ...) verwenden, da die Arrayzuweisung unzulässig ist).

46
nneonneo

Der Linux-Kernel verwendet eine Nice-Implementierung von ARRAY_SIZE, um dieses Problem zu beheben:

#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 zwei Instrumente verwendet werden: typeof Operator und __builtin_types_compatible_p Funktion. Außerdem verwendet es ihr "berühmtes" BUILD_BUG_ON_ZERO-Makro, das nur in GNU C gültig ist. 

Unter der Annahme einer Kompilierzeit-Evaluierungsanforderung (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);}))

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

24
ouah

Diese Version von ARRAYSIZE() gibt 0 zurück, wenn arr ein Zeiger ist 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 ausgeführt, können Sie eine Laufzeitausnahme erzwingen (durch 0 teilen).

#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 keinen Kompilierungsfehler erzwingen (die Adresse von arg muss zur Laufzeit verglichen werden)

17
Keine Lust

Änderung der Antwort von bluss mit typeof anstelle eines Typparameters:

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

Mit C11 können wir Arrays und Zeiger mit _Generic unterscheiden, aber ich habe nur einen Weg gefunden, 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) Ein Zeiger auf A ist kein Zeiger auf Zeiger. 2) Zeiger-zu-Elem ist Zeiger-zu-T. Es wertet (void)0 aus und schlägt statisch mit Zeigern fehl.

Dies ist eine unvollkommene Antwort, aber vielleicht kann ein Leser diese Art von Parameter verbessern!

5
bluss

Hier ist eine andere, die auf den gccTyp der Erweiterung angewiesen ist:

#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. Wenn ein Array übergeben wird, ist der Compiler zufrieden. Wenn der 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')
2
Digital Trauma

Hier ist eine mögliche Lösung, die die Erweiterung GNU namens Anweisungsausdrücke verwendet:

#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 static-Assertion , um diese sizeof(arr) != sizeof(void*) zu bestätigen. Dies hat eine offensichtliche Einschränkung - Sie können dieses Makro nicht für Arrays verwenden, deren Größe genau ein Zeiger ist (z. B. ein Array mit einer Länge von Zeigern/Ganzzahlen oder möglicherweise ein Byte mit einer Länge von 4 Bytes auf einem 32-Bit-Bit) Plattform). Aber diese speziellen Fälle können leicht genug umgangen werden.

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

1
Adam Rosenfield

mein persönlicher Favorit, versucht 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-Drucke

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

Noch ein Beispiel zur Sammlung.

#define LENGTHOF(X) ({ \
    const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
    typeof(X[0]) (*should_be_an_array)[length] = &X; \
    length; })

Pros:

  1. Es funktioniert mit normalen Arrays, Arrays variabler Länge, mehrdimensionalen Arrays __. Arrays mit Strukturen der Größe Null
  2. Es erzeugt einen Kompilierungsfehler (keine Warnung), wenn Sie einen Zeiger, eine Struktur oder eine Union übergeben
  3. Es hängt nicht von den Funktionen von C11 ab
  4. Es gibt sehr lesbare Fehler

Nachteile:

  1. Es hängt von einigen der gcc-Erweiterungen ab: Typeof , Statement Exprs und (wenn es Ihnen gefällt) Conditionals
  2. Es hängt von der C99 VLA - Funktion ab
0
hurufu

Schrecklich, ja, aber das funktioniert und ist tragbar.

#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \
                       (sizeof(arr)/sizeof(*arr)) : \
                       -1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__))

Dies wird zum Zeitpunkt der Kompilierung nichts erkennen, es wird jedoch eine Fehlermeldung in stderr ausgegeben und -1 zurückgegeben, wenn es sich um einen Zeiger oder handelt, wenn das Array 1 ist.

==> DEMO <==

0
Michael M.