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.
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 array
mit 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.
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]
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 */
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-braces
und es ist nicht standardmäßig für GCC 4.8 aktiviert, weilstd::array
– Jonathan Wakely
7. März 2013 um 12:33 Uhr