Lassen Sie den Compiler die Anzahl der Array-Initialisierer überprüfen

Lesezeit: 9 Minuten

Das Initialisieren eines Arrays (in C++, aber jede Lösung, die für C funktioniert, wird wahrscheinlich auch hier funktionieren) mit weniger Initialisierern als es Elemente hat, ist vollkommen legal:

int array[10] = { 1, 2, 3 };

Dies kann jedoch eine Quelle für obskure Fehler sein. Gibt es eine Möglichkeit, den Compiler (gcc) die Anzahl der Initialisierer für ein bestimmtes Array überprüfen zu lassen und eine Warnung oder sogar einen Fehler auszugeben, wenn deklarierte und tatsächliche Größe nicht übereinstimmen?

Ich weiß, dass ich es gebrauchen kann int array[] = { 1, 2, 3 }; und könnte dann statische Zusicherungen verwenden sizeof(array) meine Erwartung dort zu überprüfen. Aber ich benutze array in anderen Übersetzungseinheiten, also muss ich es mit einer expliziten Größe deklarieren. Also dieser Trick funktioniert bei mir nicht.

  • Ich bin mir bei einer Warnung nicht sicher, aber jeder Compiler, der einen Fehler dafür ausgibt, wäre ein nicht konformer Compiler. Ich kann mir nicht vorstellen, dass ein Compiler-Anbieter eine solche Option zu seinem Produkt hinzufügt, dafür sind statische Analysetools da.

    – Jon

    7. März 2013 um 11:11 Uhr

  • GCC hat -Wmissing-field-initializers aber es funktioniert nur für andere Aggregate, nicht für Arrays, wahrscheinlich weil die meisten Leute nicht wollen, dass es vor Arrays warnt. Können Sie nicht mithilfe von Komponententests sicherstellen, dass das Array die richtigen Werte enthält und nachfolgende Elemente nicht mit Null initialisiert wurden?

    – Jonathan Wakely

    7. März 2013 um 11:14 Uhr

  • @JonathanWakely Im Gegenzug, std::array ist ein Aggregat! (Ich mag diese Warnung zusammen mit ihr tatsächlich nicht.)

    – Luc Danton

    7. März 2013 um 11:46 Uhr

  • @Luc Danton Abgesehen davon bin ich mir sicher, dass viele der OP-Probleme mit C-Arrays einfach verschwinden würden std::array. Sie können sogar mit initialisieren {} jetzt rechts?

    – Bingo

    7. März 2013 um 12:25 Uhr

  • @LucDanton, das ist eine andere Warnung, -Wmissing-bracesund es ist nicht standardmäßig für GCC 4.8 aktiviert, weil std::array

    – Jonathan Wakely

    7. März 2013 um 12:33 Uhr


(auf Wunsch aus einem Kommentar hochgestuft)

Wenn die Werte im Array für die korrekte Funktionalität des Systems wichtig sind und Null-initialisierte Werte am Ende Fehler verursachen, würde ich einfach einen Komponententest hinzufügen, um zu überprüfen, ob das Array die richtigen Daten enthält, anstatt zu versuchen, dies zu erzwingen es im Code.

  • Ich stimme zu, dass ein Komponententest dennoch eine gute Idee wäre, Sie scheinen zu implizieren, dass statische Codeprüfungen keinen Mehrwert bringen. Ich glaube, das Hinzufügen eines statischen Asserts hat einige Vorteile gegenüber einem Komponententest (muss aber den Komponententest nicht ersetzen): – Wenn der Code geändert oder gelesen wird, sind die beim Schreiben des Codes getroffenen Annahmen klar (Design by Contract) – Statisch Behauptungen sind eine Form des Komponententests durch den Compiler, sie laufen automatisch und sind daher eine großartige Form der kontinuierlichen Integration und des Tests.

    – Maarten Arits

    23. Juli 2018 um 12:11 Uhr


  • “Sie scheinen zu implizieren, dass statische Codeprüfungen keinen Mehrwert bringen”. Nein, das habe ich nicht gesagt. “Ich würde diese spezielle Einschränkung mit einem Test überprüfen” bedeutet nicht “statische Behauptungen sind nutzlos, machen Sie sich nicht die Mühe, sie zu verwenden”.

    – Jonathan Wakely

    23. Juli 2018 um 12:55 Uhr

  • Warum bevorzugen Sie einen Komponententest, anstatt zu versuchen, ihn im Code durchzusetzen?

    – Maarten Arits

    23. Juli 2018 um 13:40 Uhr

  • Weil die vorgeschlagenen Lösungen für die Durchsetzung im Code fragil sind und ein falsches Sicherheitsgefühl vermitteln könnten. Ein Komponententest, der die Anforderung explizit überprüft, ist einfach zu schreiben und schwer zu verfehlen. Hinzufügen statischer Prüfungen zur Kompilierzeit auch ist in Ordnung, aber “Wenn die Werte im Array für die korrekte Funktionalität des Systems wichtig sind und Null-initialisierte Werte am Ende Fehler verursachen, würde ich einfach einen Komponententest hinzufügen, um zu überprüfen, ob das Array die richtigen Daten enthält.” .

    – Jonathan Wakely

    23. Juli 2018 um 14:39 Uhr

Benutzer-Avatar
Eric Postpischil

Da Sie verwenden array in anderen Übersetzungseinheiten hat es anscheinend eine externe Verknüpfung. In diesem Fall dürfen Sie es zweimal deklarieren, solange die Deklarationen ihm den gleichen Typ geben. Deklarieren Sie es also einfach zweimal, erlauben Sie dem Compiler einmal, die Initialisierer zu zählen, und geben Sie einmal die Größe an. Fügen Sie diese Zeile in eine Quelldatei ein, vor jedem Header, der deklariert array:

int array[] = { 1, 2, 3 };

Fügen Sie später in derselben Datei eine ein #include Zeile, die erklärt arraymit einer Zeile wie:

extern int array[10];

Wenn sich die Array-Größen in den beiden Deklarationen unterscheiden, meldet der Compiler einen Fehler. Wenn sie gleich sind, akzeptiert der Compiler sie.

  • Eine dieser oft vernachlässigten Funktionen. 🙂 Ist das übrigens in C und C++ dasselbe?

    – Alexey Frunze

    7. März 2013 um 12:32 Uhr

  • @AlexeyFrunze: Nicht ganz dasselbe; C++ behandelt beide Zeilen als Definitionen, aber das Einfügen extern am Anfang der zweiten Zeile macht es zu einer Deklaration, die keine Definition ist.

    – Eric Postpischil

    7. März 2013 um 12:35 Uhr


  • Schön, ich habe das versucht, aber ich habe die beiden Deklarationen umgekehrt gesetzt, was nicht eine Diagnose geben (aus offensichtlichen Gründen denke ich jetzt darüber nach)

    – Jonathan Wakely

    7. März 2013 um 12:37 Uhr


  • Das Problem bei diesem Ansatz besteht darin, dass die Quelldatei, die das Array definiert, die Header-Datei mit der exportierten Deklaration enthält. Also mit der Zeit int array[] verarbeitet wird, hat der Compiler bereits eine gesehen extern int array[10] und wird daher stillschweigend wieder 10 als Größe annehmen.

    – MVG

    7. März 2013 um 12:38 Uhr


  • Sie könnten die Definition in den Header vor der Deklaration einfügen, aber durch ein Makro wie z DEFINE_ARRAYdas wird nur durch die Quelldatei festgelegt .. ein bisschen hässlich und zerbrechlich

    – Jonathan Wakely

    7. März 2013 um 12:39 Uhr


Benutzer-Avatar
Alexej Frunze

Ich habe eine Idee.

#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]

#define NUM_ARGS__(X, \
                      N64,N63,N62,N61,N60, \
  N59,N58,N57,N56,N55,N54,N53,N52,N51,N50, \
  N49,N48,N47,N46,N45,N44,N43,N42,N41,N40, \
  N39,N38,N37,N36,N35,N34,N33,N32,N31,N30, \
  N29,N28,N27,N26,N25,N24,N23,N22,N21,N20, \
  N19,N18,N17,N16,N15,N14,N13,N12,N11,N10, \
  N09,N08,N07,N06,N05,N04,N03,N02,N01,  N, ...) N

#define NUM_ARGS(...) \
  NUM_ARGS__(0, __VA_ARGS__, \
                 64,63,62,61,60, \
  59,58,57,56,55,54,53,52,51,50, \
  49,48,47,46,45,44,43,42,41,40, \
  39,38,37,36,35,34,33,32,31,30, \
  29,28,27,26,25,24,23,22,21,20, \
  19,18,17,16,15,14,13,12,11,10, \
   9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

#define DECL_INIT_ARRAYN(TYPE, NAME, COUNT, N, ...) \
  C_ASSERT(COUNT == N); \
  TYPE NAME[COUNT] = { __VA_ARGS__ }

#define DECL_INIT_ARRAY(TYPE, NAME, COUNT, ...) \
  DECL_INIT_ARRAYN(TYPE, NAME, COUNT, NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

DECL_INIT_ARRAY(const int, array3_3, 3, 1, 2, 3);

int main(void)
{
  DECL_INIT_ARRAY(const int, array5_4, 5, 1, 2, 3, 4);
  DECL_INIT_ARRAY(const int, array5_6, 5, 1, 2, 3, 4, 5, 6);
  return 0;
}

Ausgabe (Idee):

prog.c: In function ‘main’:
prog.c:33:3: error: size of array ‘CAssertExtern’ is negative
prog.c:34:3: error: size of array ‘CAssertExtern’ is negative
prog.c:34:3: error: excess elements in array initializer [-Werror]
prog.c:34:3: error: (near initialization for ‘array5_6’) [-Werror]
prog.c:34:3: error: unused variable ‘array5_6’ [-Werror=unused-variable]
prog.c:33:3: error: unused variable ‘array5_4’ [-Werror=unused-variable]
prog.c:34:3: error: unused variable ‘CAssertExtern’ [-Werror=unused-variable]
cc1: all warnings being treated as errors

UPD: Das OP hat eine kürzere C++11-Lösung gefunden, die auf der gleichen Idee der Verwendung aufbaut __VA_ARGS__ und eine Static/Compile-Time-Assertion:

#include <tuple>

#define DECL_INIT_ARRAY(TYPE, NAME, COUNT, ...)                         \
  static_assert(COUNT ==                                                \
    std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value,     \
    "Array " #NAME " should have exactly " #COUNT " initializers");     \
  TYPE NAME[COUNT] = { __VA_ARGS__ }

DECL_INIT_ARRAY(const int, array3_3, 3, 1, 2, 3);

int main(void)
{
  DECL_INIT_ARRAY(const int, array5_4, 5, 1, 2, 3, 4);
  DECL_INIT_ARRAY(const int, array5_6, 5, 1, 2, 3, 4, 5, 6);
  return 0;
}

Ausgabe (Idee):

prog.cpp: In function ‘int main()’:
prog.cpp:13:3: error: static assertion failed: Array array5_4 should have exactly 5 initializers
prog.cpp:14:3: error: static assertion failed: Array array5_6 should have exactly 5 initializers
prog.cpp:14:3: error: too many initializers for ‘const int [5]’
prog.cpp:13:3: warning: unused variable ‘array5_4’ [-Wunused-variable]
prog.cpp:14:3: warning: unused variable ‘array5_6’ [-Wunused-variable]

  • In meinem Fall, COUNT mehrere tausend, also wären die Zählmakros dafür ziemlich lang. Aber vielleicht gibt es einen C++11-Weg, um dies zum Laufen zu bringen? Etwas in der Art von std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value oder ähnliches.

    – MVG

    7. März 2013 um 12:01 Uhr

  • Ja, mein Vorschlag funktioniert. Möchten Sie Ihre Antwort aktualisieren, darf ich sie bearbeiten oder sollte ich sie lieber als separate Antwort posten?

    – MVG

    7. März 2013 um 12:10 Uhr

  • Kühl! Ich werde Ihren Teil in die Antwort einbeziehen.

    – Alexey Frunze

    7. März 2013 um 12:20 Uhr

  • Ich habe in die gleiche Richtung gedacht, z. B. ein temporäres Array deklariert und seine Größe überprüft, aber festgestellt, dass dieses Array möglicherweise nicht optimiert wird, wenn es global ist. Vorlagenmagie und die neuen C++-Features scheinen sehr hilfreich zu sein.

    – Alexey Frunze

    7. März 2013 um 12:28 Uhr

Benutzer-Avatar
Maarten Arits

Ich habe mich in C99 nach einer konkreten Antwort darauf umgesehen und hier eine Antwort gefunden: Wie kann ich „sizeof“ in einem Präprozessormakro verwenden?

Wenn Sie die Größe Ihres Arrays nicht definieren und verwenden:

int test[] = {1,2} 
STATIC_ASSERT(sizeof(test)/sizeof(test[0]) == 3)
/* with C11 support: */
_Static_assert(sizeof(test)/sizeof(test[0]) == 3)
/* or more easily */
ASSERT_ARRAY_LENGTH(test, 3);

Sie können leicht feststellen, ob die Größe des Arrays Ihren Erwartungen entspricht. Ein Kompilierungsfehler wird ausgelöst, wenn die statische Bestätigung fehlschlägt. Es gibt keinen Laufzeit-Overhead. Eine sehr solide Implementierung des statischen Assert finden Sie hier:
Statische Assert-Implementierung C

Für Ihren Komfort:

#define ASSERT_CONCAT_(a, b) a##b
#define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b)
/* These can't be used after statements in c89. */
#ifdef __COUNTER__
#define STATIC_ASSERT(e,m) \
;enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1/(int)(!!(e)) }
#else
/* This can't be used twice on the same line so ensure if using in headers
* that the headers are not included twice (by wrapping in #ifndef...#endif)
* Note it doesn't cause an issue when used on same line of separate modules
* compiled with gcc -combine -fwhole-program.  */
#define STATIC_ASSERT(e,m) \
;enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(int)(!! (e)) }
#endif

Ich habe zusätzlich zu diesem Makro ein Makro speziell für die Validierung der Größe eines Arrays hinzugefügt. Die Anzahl der Elemente muss genau der angegebenen Länge entsprechen:

#define ASSERT_ARRAY_LENGTH(array, length)\
STATIC_ASSERT(sizeof(array)/sizeof(array[0]) == length,\
    "Array is not of expected length")

Wenn Sie C99 nicht unterstützen müssen, können Sie das neue C11-Feature _Static_assert verwenden. Mehr Info hier. Wenn Sie keine C-Unterstützung benötigen, können Sie sich auch auf die statische Zusicherung von c++ verlassen:

std::size(test) == 3; /* C++ 17 */
(std::end(test) - std::begin(end)) == 3; /* C++ 14 */

1159070cookie-checkLassen Sie den Compiler die Anzahl der Array-Initialisierer überprüfen

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

Privacy policy