I played around with designated initializers a bit the other day and noticed, to my surprise, that it is valid to use the same index more than once. What's more, it didn't even produce a compiler warning, error, or even informational statement when I did so, and even PC-Lint didn't seem to care (which I think surprised me the most).
I'm wondering if there's a reason for compilers not even providing an information message in this case or if there are additional compiler/lint/etc. options that may be available to catch or flag this.
Tools used: Renesas RX Standard Toolchain v1.2.0.0 (C99), gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5.1) (in a VM), Lint-NT 9.00i
For example, some old code I'm working on #defines a bunch of commands and then creates an array of command structures (drastically simplified here) to be cycled through to find and use that particular command:
#define CMD_RMEM 0
#define CMD_WMEM 1
#define CMD_XCRC 2
#define CMD_NULL 3
typedef struct
{
const char cmdID;
const char* cmdStr;
} CMD;
const CMD commands[] = {
{CMD_RMEM,"RMEM"},
{CMD_WMEM,"WMEM"},
{CMD_XCRC,"XCRC"},
{CMD_NULL,"NULL"},
};
Then I remembered the designated initializer syntax I'd seen somewhere and thought it may provide more flexibility on the arrangement of items in the array in addition to detecting duplicate command indices, e.g.:
//(same #def's & typedef as above)
const CMD commands[] = {
[CMD_RMEM] = {CMD_RMEM,"RMEM"},
[CMD_NULL] = {CMD_NULL,"NULL"}, //different order in ititializer list,
// but designation keeps it in the same array/memory position, so
// this will still be the 'last' element
[CMD_CMEM] = {CMD_CMEM,"CMEM"},
[CMD_WMEM] = {CMD_WMEM,"WMEM"},
[CMD_XCRC] = {CMD_XCRC,"XCRC"},
};
would produce the same effect as the initial code above but with flexibility in arranging the items in the array declaration (which it does) and (I was thinking/hoping) that
#define CMD_RMEM 0
#define CMD_WMEM 1
#define CMD_XCRC 1 // obvious dupe in a short list, but not so obvious
// if part of a much longer list or if split among multiple files
#define CMD_NULL 2
// (Same designated initializer section as above)
would generate at least a warning since we're using the same designated index more than once (which it does not) (this example may easily result from adding a command without shifting the last NULL placeholder).
The GCC Designated Inits page notes a GNU extension which allows you to use ranges in the initializers, which I can see being useful with the ability to define the whole range and then override certain parts (e.g. int arr[] = {[0 ... 99] = -1, [42] = 1}
, but I don't understand why it's still not at least flagged at some level...
Farther down in the same GCC page, it does address the topic of multiple fields, but doesn't explain why it behaves as it does: "If the same field is initialized multiple times, it has the value from the last initialization. If any such overridden initialization has side-effect, it is unspecified whether the side-effect happens or not. Currently, GCC discards them and issues a warning."
This IBM page shows an interesting example as well, but still doesn't answer (at least for me) why it is not at least some sort of build message...
And finally, this gcc patch page from Dec 2000 indicates duplicate checks were explicitly removed, but doesn't (from what I read briefly) explain why.
So, why is this (seemingly) glossed over? And is there any way to have the compiler (or even lint) flag this (to provide more safety)(in c/c99; don't just say 'use c++' or something :p)?
I have no explanation as to why —perhaps simply because designated initializers are still new and little-used. Compiler makers have to consider the number of programmers who will benefit from new features.
Clang warns for your construct:
$ clang -std=c99 -Wall -c t.c
t.c:24:17: warning: initializer overrides prior initialization of this subobject [-Winitializer-overrides]
[CMD_XCRC] = {CMD_XCRC,"XCRC"},
^~~~~~~~
t.c:4:18: note: expanded from macro 'CMD_XCRC'
#define CMD_XCRC 1 // obvious dupe in a short list, but not so obvious
^
t.c:23:17: note: previous initialization is here
[CMD_WMEM] = {CMD_WMEM,"WMEM"},
^~~~~~~~
t.c:2:18: note: expanded from macro 'CMD_WMEM'
#define CMD_WMEM 1
^
(I had to make minor changes for it to compile but you get the idea.)
$ clang -v
Apple LLVM version 4.2 (clang-425.0.24) (based on LLVM 3.2svn)
Target: x86_64-apple-darwin12.3.0
Thread model: posix