2013-03-07 10 views
14

Initialisierung eines Arrays die Anzahl der Array initializers überprüfen (in C++, aber jede Lösung, die für C wird wahrscheinlich arbeiten auch hier funktioniert) mit weniger initializers als Elemente hat, ist vollkommen legal:Haben Compiler

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

Dies kann jedoch eine Quelle von obskuren Fehlern sein. Gibt es eine Möglichkeit, dass der Compiler (gcc) die Anzahl der Initialisierer für ein bestimmtes Array überprüft und eine Warnung oder sogar einen Fehler ausgibt, wenn die deklarierte und die tatsächliche Größe nicht übereinstimmen?

Ich weiß, ich kann int array[] = { 1, 2, 3 }; verwenden und konnte dann statische Assertionen mit sizeof(array) verwenden, um meine Erwartung dort zu überprüfen. Aber ich verwende array in anderen Übersetzungseinheiten, also muss ich es mit einer expliziten Größe deklarieren. Also wird dieser Trick nicht für mich funktionieren.

+4

Ich bin mir nicht sicher über eine Warnung, aber jeder Compiler, der einen Fehler für diesen gab, wäre ein nichtkonformer Compiler. Ich kann mir nicht vorstellen, dass ein Compilerhersteller eine solche Option zu seinem Produkt hinzufügt, dafür sind statische Analysetools gedacht. – Jon

+2

GCC hat '-Wissing-field-initializers', aber es funktioniert nur für andere Aggregate, nicht für Arrays, wahrscheinlich weil die meisten Leute nicht wollen, dass es für Arrays warnt. Können Sie nicht Komponententests verwenden, um sicherzustellen, dass das Array die richtigen Werte enthält und die nachfolgenden Elemente nicht initialisiert wurden? –

+0

@ JonathanWakely Im Gegenzug ist 'std :: array' ein Aggregat! (Ich mag diese Warnung im Gegensatz dazu nicht.) –

Antwort

4

(aus einem Kommentar gefördert wie gewünscht)

Wenn die Werte im Array auf die korrekte Funktionalität des Systems wichtig sind, und verursachen Fehler am Ende Null initialisierten Werte aufweisen, dann würde ich nur hinzufügen, ein Unit-Test, um zu überprüfen, ob das Array die richtigen Daten enthält, anstatt es im Code zu erzwingen.

2

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

Output (ideone):

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: Die OP wurde gefunden eine kürzere C++ 11-Lösung an, die auf der gleichen Idee __VA_ARGS__ Verwendung und eine statische/Kompilierzeit-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 (ideone):

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] 
+2

+1 für die Verwendung einer Bazooka, um eine Fliege zu töten :) –

+0

@DavidRF LOL! :) –

+0

In meinem Fall ist 'COUNT' mehrere tausend, also wären die Zählmakros dafür ziemlich lang. Aber vielleicht gibt es einen C++ 11-Weg, um das zu funktionieren? Etwas in der Art von 'std :: tuple_size :: value' oder ähnlich. – MvG

3

Da Sie array in anderen Übersetzungseinheiten verwenden, hat es anscheinend externe Bindung. In diesem Fall dürfen Sie es zweimal deklarieren, sofern die Deklarationen denselben Typ haben. Also deklarieren Sie es einfach zweimal, nachdem Sie dem Compiler erlaubt haben, die Initialisierer zu zählen und die Größe einmal anzugeben. Setzen Sie diese Zeile in einer Quelldatei, bevor ein Header, die array erklärt:

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

Später in der gleichen Datei, legen eine #include Linie, die array erklärt, mit einer Linie wie:

extern int array[10]; 

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

+0

Eines dieser oft vernachlässigten Merkmale. :) Ist das in C und C++ gleich, übrigens? –

+0

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

+0

Schön, ich habe das versucht, aber ich habe die beiden Deklarationen umgekehrt, die keine Diagnose geben (aus offensichtlichen Gründen denke ich jetzt darüber) –