Discussion:
What meaning is ' .pinConfigs = '?
(too old to reply)
fl
2017-04-19 20:19:02 UTC
Permalink
Hello,

I see a puzzling '.' in below code snippet. Can you tell me what it is?

Thanks,



/////////////////
typedef struct GPIO_Config {
/*! Pointer to the board's PinConfig array */
GPIO_PinConfig *pinConfigs;

/*! Pointer to the board's callback array */
GPIO_CallbackFxn *callbacks;

/*! Number of pin configs defined */
uint32_t numberOfPinConfigs;

/*! Number of callbacks defined */
uint32_t numberOfCallbacks;

uint32_t intPriority;
} GPIOTiva_Config;

const GPIOTiva_Config GPIOTiva_config = {
.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
.callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
.numberOfCallbacks = sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
.intPriority = (~0)
};
Ian Collins
2017-04-19 20:25:51 UTC
Permalink
Post by fl
Hello,
I see a puzzling '.' in below code snippet. Can you tell me what it is?
Thanks,
/////////////////
typedef struct GPIO_Config {
/*! Pointer to the board's PinConfig array */
GPIO_PinConfig *pinConfigs;
/*! Pointer to the board's callback array */
GPIO_CallbackFxn *callbacks;
/*! Number of pin configs defined */
uint32_t numberOfPinConfigs;
/*! Number of callbacks defined */
uint32_t numberOfCallbacks;
uint32_t intPriority;
} GPIOTiva_Config;
const GPIOTiva_Config GPIOTiva_config = {
.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
.callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
.numberOfCallbacks = sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
.intPriority = (~0)
};
Look up designated initialisers.
--
Ian
James R. Kuyper
2017-04-19 20:37:27 UTC
Permalink
Post by fl
Hello,
I see a puzzling '.' in below code snippet. Can you tell me what it is?
Thanks,
/////////////////
typedef struct GPIO_Config {
/*! Pointer to the board's PinConfig array */
GPIO_PinConfig *pinConfigs;
/*! Pointer to the board's callback array */
GPIO_CallbackFxn *callbacks;
/*! Number of pin configs defined */
uint32_t numberOfPinConfigs;
/*! Number of callbacks defined */
uint32_t numberOfCallbacks;
uint32_t intPriority;
} GPIOTiva_Config;
const GPIOTiva_Config GPIOTiva_config = {
.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
.callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
.numberOfCallbacks = sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
.intPriority = (~0)
};
That is a feature that was added in C99, called "designated
initializers" 6.7.9). Instead of initializing the members of the struct
in the order that they were defined, each initializer is designated as
initializing a specific named member. All other members are
zero-initialized. In this particular case, the designated initializers
are in the same exact order as the corresponding members, and every
single member is initialized, so it doesn't make any difference.
Designated initializers become more useful when you only want to set a
small number of members of a struct to non-zero values.

They're also helpful when you don't know the exact order of the members.
Examples include struct tm and struct lconv: the standard specifies the
names of members that are required to exist, but doesn't specify the
order of those members, and allows for the existence of additional members.

This code is conceptually equivalent to

GPIOTiva_Config GPIOTiva_config = {0};
GPIOTiva_config.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs;
GPIOTiva_config.callbacks =
(GPIO_CallbackFxn *)gpioCallbackFunctions;
GPIOTiva_config.numberOfPinConfigs =
sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig);
GPIOTiva_config..numberOfCallbacks =
sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn);
GPIOTiva_config.intPriority = (~0);

except that the assignment statements above would not be allowed if
GPIOTiva_config were declared 'const'. Since this occurs in the
initializer of GPIOTiva_config, it is allowed even with 'const'.

There's a different kind of designator that is used for arrays:

const int identity[4][4] =
{[0][0] =1, [1][1] = 1, [2][2] = 1, [3][3] = 1};
David Brown
2017-04-20 10:51:59 UTC
Permalink
Post by James R. Kuyper
Post by fl
Hello,
I see a puzzling '.' in below code snippet. Can you tell me what it is?
Thanks,
/////////////////
typedef struct GPIO_Config {
/*! Pointer to the board's PinConfig array */
GPIO_PinConfig *pinConfigs;
/*! Pointer to the board's callback array */
GPIO_CallbackFxn *callbacks;
/*! Number of pin configs defined */
uint32_t numberOfPinConfigs;
/*! Number of callbacks defined */
uint32_t numberOfCallbacks;
uint32_t intPriority;
} GPIOTiva_Config;
const GPIOTiva_Config GPIOTiva_config = {
.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
.callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
.numberOfCallbacks =
sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
.intPriority = (~0)
};
That is a feature that was added in C99, called "designated
initializers" 6.7.9). Instead of initializing the members of the struct
in the order that they were defined, each initializer is designated as
initializing a specific named member. All other members are
zero-initialized. In this particular case, the designated initializers
are in the same exact order as the corresponding members, and every
single member is initialized, so it doesn't make any difference.
Designated initializers become more useful when you only want to set a
small number of members of a struct to non-zero values.
They're also helpful when you don't know the exact order of the members.
Examples include struct tm and struct lconv: the standard specifies the
names of members that are required to exist, but doesn't specify the
order of those members, and allows for the existence of additional members.
They are helpful even when you know (or can easily look up) the exact
order of the members, and when you are initialising all of them -
because they document exactly which members are being initialised to
which values. It makes it easier to write correct code, easier to read
what is going on, easier to check that the code is correct, and more
robust if something in the struct changes.
bartc
2017-04-20 11:35:36 UTC
Permalink
Post by David Brown
Post by James R. Kuyper
They're also helpful when you don't know the exact order of the members.
Examples include struct tm and struct lconv: the standard specifies the
names of members that are required to exist, but doesn't specify the
order of those members, and allows for the existence of additional members.
They are helpful even when you know (or can easily look up) the exact
order of the members, and when you are initialising all of them -
because they document exactly which members are being initialised to
which values.
How about commenting which one?


It makes it easier to write correct code, easier to read
Post by David Brown
what is going on,
Not when /everything/ is some some permutation of PinConfigs and gpio!

What is someone supposed to be make of this:

.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),

And would anyone notice if it was mistakenly written as:

.numberOfPinConfigs = sizeof(GPIO_PinConfig)/sizeof(gpioPinConfigs),

What is the significant of "gpio" versus "GPIO"?

Regarding the designated initialisers, the pattern here isn't that
different from a series of assignments. You just don't need to put the
name of the variable in front of each ".". Which might be a good idea
when the declaration is this:

GPIOTiva_Config GPIOTiva_config

Did it take anyone else an extra second or two to spot the difference?

More time spent on naming schemes I think would do more for readability
than features such as designated initialisers.
--
bartc
David Brown
2017-04-20 11:52:15 UTC
Permalink
Post by bartc
Post by David Brown
Post by James R. Kuyper
They're also helpful when you don't know the exact order of the members.
Examples include struct tm and struct lconv: the standard specifies the
names of members that are required to exist, but doesn't specify the
order of those members, and allows for the existence of additional members.
They are helpful even when you know (or can easily look up) the exact
order of the members, and when you are initialising all of them -
because they document exactly which members are being initialised to
which values.
How about commenting which one?
I thought it was obvious, but the point of using designated initialisers
rather than comments is that it is checked by the compiler and thus
always correct - even in the face of changes to the structure. A simple
comment can be wrong and misleading - either due to an error when
written, or due to other changes to the code.

Never use a plain comment for documentation if you can express the same
thing in real code.
Post by bartc
It makes it easier to write correct code, easier to read
Post by David Brown
what is going on,
Not when /everything/ is some some permutation of PinConfigs and gpio!
I said it makes it /easier/ to read - I did not say it makes it /easy/
to read.
Post by bartc
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
That seems pretty obvious to me. It means that the struct member that
is to hold the size of the pinConfigs array is set to the size of the
pinConfigs array.
Post by bartc
.numberOfPinConfigs = sizeof(GPIO_PinConfig)/sizeof(gpioPinConfigs),
I would. But then, I can read C code.

I might have been tempted to write:

.numberOfPinConfigs = sizeof(gpioPinConfigs) / sizeof(gpioPinConfigs[0])

since that is a common idiom for "size of an array".
Post by bartc
What is the significant of "gpio" versus "GPIO"?
I did not write the original code - you'd have to ask Texas Instruments
(I presume they are the author).
Post by bartc
Regarding the designated initialisers, the pattern here isn't that
different from a series of assignments.
That is hardly a coincidence.
Post by bartc
You just don't need to put the
name of the variable in front of each ".".
Designated initialisers are not essential - they are just a good idea
(for reasons already given).
Post by bartc
Which might be a good idea
GPIOTiva_Config GPIOTiva_config
Did it take anyone else an extra second or two to spot the difference?
No, I saw it immediately. But I don't think a difference of one
capitalisation is a good idea here.
Post by bartc
More time spent on naming schemes I think would do more for readability
than features such as designated initialisers.
I agree that their naming scheme could have been better. I totally
disagree that a good naming scheme makes dedicated initialisers
redundant - they are orthogonal concepts that both improve readability.
bartc
2017-04-20 13:37:04 UTC
Permalink
Post by David Brown
Post by fl
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
That seems pretty obvious to me. It means that the struct member that
is to hold the size of the pinConfigs array is set to the size of the
pinConfigs array.
That's the usual pattern when dividing one sizeof by other. But
something seems off here, not helped by the names being so easy to get
mixed up.

What's happening here is similar to this:

struct {
int *pins;
int npins;
}
...
.pins = (int*)A;
.npins = sizeof(A)/sizeof(int);

with the assumption that A is a flat array of some sort, although that's
not visible in the code fragment. But using the more common idiom would
have helped.

In any case I don't agree it is obvious.
Post by David Brown
Post by fl
.numberOfPinConfigs = sizeof(GPIO_PinConfig)/sizeof(gpioPinConfigs),
I would. But then, I can read C code.
It's not so obvious either whether sizeof(X)/sizeof(Y) is any more
correct than sizeof(Y)/sizeof(X), not when X and Y are both names.
Post by David Brown
Designated initialisers are not essential - they are just a good idea
(for reasons already given).
Yes, they match the similarly good idea of keyword arguments. Yet I
always seems to find them mildly annoying..

BTW is the following supposed to be legal? Some compilers say nothing
about it:

struct Pt {int x,y;};

struct Pt P={ 100, .x =200, .x =300, 400 };

//printf("x=%d, y=%d\n", P.x,P.y);
--
bartc
Keith Thompson
2017-04-20 16:33:20 UTC
Permalink
bartc <***@freeuk.com> writes:
[...]
Post by bartc
BTW is the following supposed to be legal? Some compilers say nothing
struct Pt {int x,y;};
struct Pt P={ 100, .x =200, .x =300, 400 };
Yes, it's legal. N1570 6.7.9p19 discusses initializers overriding
previous initializers for the same subobject. (I suggest, again,
that you look these things up before asking about them.)

Of course the fact that it's legal doesn't imply that it's good code.
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
bartc
2017-04-20 16:51:24 UTC
Permalink
Post by Keith Thompson
[...]
Post by bartc
BTW is the following supposed to be legal? Some compilers say nothing
struct Pt {int x,y;};
struct Pt P={ 100, .x =200, .x =300, 400 };
Yes, it's legal. N1570 6.7.9p19 discusses initializers overriding
previous initializers for the same subobject. (I suggest, again,
that you look these things up before asking about them.)
Maybe I didn't think it would allow something that could have been put
to better use in detecting errors in the code.

This was a new feature; they could have put in any restrictions they
liked. Or was it an existing feature that already operated in such a lax
manner?
Post by Keith Thompson
Of course the fact that it's legal doesn't imply that it's good code.
Some compilers will warn despite the fact that it is apparently
perfectly legal.
--
bartc
James R. Kuyper
2017-04-20 16:27:18 UTC
Permalink
...
Post by David Brown
Post by fl
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
That seems pretty obvious to me. It means that the struct member that
is to hold the size of the pinConfigs array is set to the size of the
pinConfigs array.
That's not particularly obvious to me. gpioPinConfigs is not visible in
the code fragment we were shown, and it's therefore not clear whether
GPIO_PinConfig is the correct type. As far as I'm concerned, in order to
make the meaning obvious (and it should have been made obvious), it
Post by David Brown
.numberOfPinConfigs = sizeof(gpioPinConfigs) / sizeof(gpioPinConfigs[0])
In my original response, I concentrated on explaining designated
initializers, but this series of comments make me look at the code more
Post by David Brown
.numberOfCallbacks = sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
Suffers from the same problem as numberOfPinConfigs, while the casts in
Post by David Brown
.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
.callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
are either unnecessary, if the pointed-at types are compatible, or just
plain wrong, if they aren't.

That's not necessarily true of .callbacks: any function pointer type can
serve the same role for function pointers that void* serves for object
pointers, except that the conversions much be specified explicitly.
However, if .callbacks were intended to be used as a generic function
pointer, I'd expect the struct to contain another member to indicate the
actual type of the callback function.
Post by David Brown
.intPriority = (~0)
.intPriority has the type uint32_t, while 0 has the type int. I suspect
that this line was written on the assumption that int is a 2's
complement type. If that assumption is are correct, then ~0 has a value
of -1, and intPriority ends up containing UINT32_MAX. That being the
case, I would prefer explicitly initializing it with UINT32_MAX, since
it's more expressive.

In the unlikely event that this code is ported to a system where int
isn't a 2's complement type, .intPriority's value will end up quite
different from UINT32_MAX. Therefore, unless producing a different
result on such systems is intentional, I wouldn't use (~0) for this
purpose. I also would use ~0 rather that (~0), but I know that many
people prefer unnecessary parentheses whenever they're unsure of the
precedence of the relevant operators.
s***@casperkitty.com
2017-04-20 16:59:49 UTC
Permalink
Post by James R. Kuyper
In the unlikely event that this code is ported to a system where int
isn't a 2's complement type, .intPriority's value will end up quite
different from UINT32_MAX. Therefore, unless producing a different
result on such systems is intentional, I wouldn't use (~0) for this
purpose. I also would use ~0 rather that (~0), but I know that many
people prefer unnecessary parentheses whenever they're unsure of the
precedence of the relevant operators.
I would think it more likely that Sus domesticus would evolve wings, than
that any processor with an I/O layout compatible with the aforementioned
chip would use anything other than two's-complement numeric representation.

I am also highly skeptical, btw, of the notion that *ANY* implementations
designed for actual non-two's-complement hardware will ever conform to
any standard C99 or later. I am aware of a one's-complement compiler that
supports what is described as a 71-bit signed type, but the longest
unsigned type is only 36 bits.

That having been said, I would favor either -1 or ~0u as notation for "all
bits set"--the former having the advantage that it does not depend upon the
size of "unsigned". Note that the behavior of converting -1 to *any*
unsigned type is a function of the destination type, rather than the type
used to represent -1, and all unsigned types define such conversion as
yielding the value which, when added to 1, will yield zero (i.e. the maximum
value for the type).
j***@verizon.net
2017-04-20 17:26:25 UTC
Permalink
Post by s***@casperkitty.com
Post by James R. Kuyper
In the unlikely event that this code is ported to a system where int
isn't a 2's complement type, .intPriority's value will end up quite
different from UINT32_MAX. Therefore, unless producing a different
result on such systems is intentional, I wouldn't use (~0) for this
purpose. I also would use ~0 rather that (~0), but I know that many
people prefer unnecessary parentheses whenever they're unsure of the
precedence of the relevant operators.
I would think it more likely that Sus domesticus would evolve wings, than
that any processor with an I/O layout compatible with the aforementioned
chip would use anything other than two's-complement numeric representation.
My comment was completely independent of how likely it is that this code will be ported to such a machine - unless producing a different result on such machines is intentional, that construct should not be used. I might let such probabilities influence my judgement if the alternative constructs were significantly more complicated or harder to understand, but you can't get much simpler than -1, and UINT16_MAX is pretty self-explanatory.

The general rule is that bitwise operations on signed values should be avoided unless the different behavior of those operations on non-2's complement machines is intended.
David Brown
2017-04-20 17:57:35 UTC
Permalink
Post by James R. Kuyper
...
Post by David Brown
Post by fl
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
That seems pretty obvious to me. It means that the struct member that
is to hold the size of the pinConfigs array is set to the size of the
pinConfigs array.
That's not particularly obvious to me. gpioPinConfigs is not visible in
the code fragment we were shown, and it's therefore not clear whether
GPIO_PinConfig is the correct type. As far as I'm concerned, in order to
make the meaning obvious (and it should have been made obvious), it
I have been making some assumptions about gpioPinConfigs and other
identifiers - but I also assume that the person seeing this code has
access to those definitions too.
Post by James R. Kuyper
Post by David Brown
.numberOfPinConfigs = sizeof(gpioPinConfigs) / sizeof(gpioPinConfigs[0])
In my original response, I concentrated on explaining designated
initializers, but this series of comments make me look at the code more
Post by David Brown
.numberOfCallbacks =
sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
Suffers from the same problem as numberOfPinConfigs, while the casts in
Post by David Brown
.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
.callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
are either unnecessary, if the pointed-at types are compatible, or just
plain wrong, if they aren't.
Agreed.
Post by James R. Kuyper
That's not necessarily true of .callbacks: any function pointer type can
serve the same role for function pointers that void* serves for object
pointers, except that the conversions much be specified explicitly.
However, if .callbacks were intended to be used as a generic function
pointer, I'd expect the struct to contain another member to indicate the
actual type of the callback function.
Post by David Brown
.intPriority = (~0)
.intPriority has the type uint32_t, while 0 has the type int. I suspect
that this line was written on the assumption that int is a 2's
complement type. If that assumption is are correct, then ~0 has a value
of -1, and intPriority ends up containing UINT32_MAX. That being the
case, I would prefer explicitly initializing it with UINT32_MAX, since
it's more expressive.
Code like this is inherently target specific - the target is a 32-bit
ARM, and its integers are two's complement. (I know this, because I
know that Tiva is a family of ARM microcontrollers.) The use of
uint32_t also guarantees that there is at least an int32_t type with 2's
complement. It would be a very hypothetical C implementation that had
2's complement int32_t, but "int" that is /not/ 2's complement.

Personally, however, I prefer to write such things as 0xffffffff rather
than ~0. And I would not include the extra brackets here.
Post by James R. Kuyper
In the unlikely event that this code is ported to a system where int
isn't a 2's complement type, .intPriority's value will end up quite
different from UINT32_MAX. Therefore, unless producing a different
result on such systems is intentional, I wouldn't use (~0) for this
purpose. I also would use ~0 rather that (~0), but I know that many
people prefer unnecessary parentheses whenever they're unsure of the
precedence of the relevant operators.
James R. Kuyper
2017-04-20 18:09:17 UTC
Permalink
On 04/20/2017 01:57 PM, David Brown wrote:
...
Post by David Brown
know that Tiva is a family of ARM microcontrollers.) The use of
uint32_t also guarantees that there is at least an int32_t type with 2's
complement. It would be a very hypothetical C implementation that had
2's complement int32_t, but "int" that is /not/ 2's complement.
If int is a 32-bit type, that would be a non-conforming implementation:

"For any two integer types with the same signedness and different
integer conversion rank (see 6.3.1.1), the range of values of the type
with smaller integer conversion rank is a subrange of the values of the
other type." (6.2.5p8)

"The rank of any standard integer type shall be greater than the rank of
any extended integer type with the same width." (6.3.1.1p1)

If int had some other size, such as 16 or 64 bits, then it would merely
be very unlikely.
Keith Thompson
2017-04-20 19:10:47 UTC
Permalink
Post by James R. Kuyper
...
Post by David Brown
know that Tiva is a family of ARM microcontrollers.) The use of
uint32_t also guarantees that there is at least an int32_t type with 2's
complement. It would be a very hypothetical C implementation that had
2's complement int32_t, but "int" that is /not/ 2's complement.
"For any two integer types with the same signedness and different
integer conversion rank (see 6.3.1.1), the range of values of the type
with smaller integer conversion rank is a subrange of the values of the
other type." (6.2.5p8)
"The rank of any standard integer type shall be greater than the rank of
any extended integer type with the same width." (6.3.1.1p1)
If int had some other size, such as 16 or 64 bits, then it would merely
be very unlikely.
A conforming implementation could have 32-bit 1's-complement int and
32-bit 2's-complement long int, with [u]int32_t as typedefs for
[unsigned] long int.
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Joe Pfeiffer
2017-04-20 13:43:33 UTC
Permalink
Post by bartc
Post by David Brown
Post by James R. Kuyper
They're also helpful when you don't know the exact order of the members.
Examples include struct tm and struct lconv: the standard specifies the
names of members that are required to exist, but doesn't specify the
order of those members, and allows for the existence of additional members.
They are helpful even when you know (or can easily look up) the exact
order of the members, and when you are initialising all of them -
because they document exactly which members are being initialised to
which values.
How about commenting which one?
It's easy to have an off-by-one error in the traditional initialization,
especially if a field is later added to the struct -- then, you've got
comments saying you got it right when you really got it wrong.
Post by bartc
It makes it easier to write correct code, easier to read
Post by David Brown
what is going on,
Not when /everything/ is some some permutation of PinConfigs and gpio!
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
.numberOfPinConfigs = sizeof(GPIO_PinConfig)/sizeof(gpioPinConfigs),
What is the significant of "gpio" versus "GPIO"?
It makes it easier to write good code. Nothing will ever make it
impossible to write bad code nor impossible to write bugs.
Post by bartc
Regarding the designated initialisers, the pattern here isn't that
different from a series of assignments. You just don't need to put the
name of the variable in front of each ".". Which might be a good idea
GPIOTiva_Config GPIOTiva_config
Did it take anyone else an extra second or two to spot the difference?
More time spent on naming schemes I think would do more for
readability than features such as designated initialisers.
Malcolm McLean
2017-04-20 16:55:47 UTC
Permalink
Post by David Brown
They are helpful even when you know (or can easily look up) the exact
order of the members, and when you are initialising all of them -
because they document exactly which members are being initialised to
which values. It makes it easier to write correct code, easier to read
what is going on, easier to check that the code is correct, and more
robust if something in the struct changes.
I agree with everything except the last.
If the struct changes, you want all the initialisations to break. The risk of
the code appearing to compile cleanly when in fact the change has invalidated
it is too great.
David Brown
2017-04-20 18:07:07 UTC
Permalink
Post by Malcolm McLean
Post by David Brown
They are helpful even when you know (or can easily look up) the exact
order of the members, and when you are initialising all of them -
because they document exactly which members are being initialised to
which values. It makes it easier to write correct code, easier to read
what is going on, easier to check that the code is correct, and more
robust if something in the struct changes.
I agree with everything except the last.
If the struct changes, you want all the initialisations to break. The risk of
the code appearing to compile cleanly when in fact the change has invalidated
it is too great.
If you know of a way to force the initialisation to break at /compile/
time when the struct is changed, then I'd love to know. Compiler
warnings on too few or too many initialisers will help if something is
added or removed, but not if it is re-ordered.

I agree that it would be good to have a way to force a compile time
error or warning on the initialiser when the struct it changed. And if
I were making a significant change to a struct, and did not already know
exactly where all its initialisation uses were, I would seriously
consider changing the name of the struct in order to get a compile time
error on code that needs to be re-checked.

But failing that, designated initialisers are the next best thing. And
for many changes to the struct, they are perfectly good - they are
robust in the case of changing the order of the members, or removing
members (you get a compile-time error if they have been set, which is
fine), or adding members for which a default of 0 is acceptable. That
is pretty good. The alternative, initialising by order of members, will
fail badly at /run/ time when the order of elements is changed. That is
the worst possible situation.
s***@casperkitty.com
2017-04-20 18:55:46 UTC
Permalink
Post by David Brown
If you know of a way to force the initialisation to break at /compile/
time when the struct is changed, then I'd love to know. Compiler
warnings on too few or too many initialisers will help if something is
added or removed, but not if it is re-ordered.
I always thought the prohibition against redefining identifiers in a way
that precisely matches the existing definition seemed counter-productive.
If it were legal to define a structure type as often as desired, provided
that all definitions matched, then a simple way to ensure a compiler squawk
if a structure doesn't match the expectations of a piece of code would be
to have that piece of code say what it thinks the structure should be. If
it matches, great. If not, squawk.

What downsides would there be to having compilers accept matching redefs?
Keith Thompson
2017-04-20 19:21:40 UTC
Permalink
Post by s***@casperkitty.com
Post by David Brown
If you know of a way to force the initialisation to break at /compile/
time when the struct is changed, then I'd love to know. Compiler
warnings on too few or too many initialisers will help if something is
added or removed, but not if it is re-ordered.
I always thought the prohibition against redefining identifiers in a way
that precisely matches the existing definition seemed counter-productive.
If it were legal to define a structure type as often as desired, provided
that all definitions matched, then a simple way to ensure a compiler squawk
if a structure doesn't match the expectations of a piece of code would be
to have that piece of code say what it thinks the structure should be. If
it matches, great. If not, squawk.
What downsides would there be to having compilers accept matching redefs?
What problem would it solve that isn't solved better by defining
the structure once in a header that can be #included by anything
that needs it?
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
s***@casperkitty.com
2017-04-20 21:03:51 UTC
Permalink
Post by Keith Thompson
Post by s***@casperkitty.com
I always thought the prohibition against redefining identifiers in a way
that precisely matches the existing definition seemed counter-productive.
If it were legal to define a structure type as often as desired, provided
that all definitions matched, then a simple way to ensure a compiler squawk
if a structure doesn't match the expectations of a piece of code would be
to have that piece of code say what it thinks the structure should be. If
it matches, great. If not, squawk.
What downsides would there be to having compilers accept matching redefs?
What problem would it solve that isn't solved better by defining
the structure once in a header that can be #included by anything
that needs it?
If a project contains two subsystems both of which #include a common file
and one of the subsystems get updated, it can be difficult to ensure that
the contents of that file remains "in sync" with both subsystems. If each
subsystem had its own copy of the expected structure definition, the two
copies could be validated against each other, or if both copies #included a
common file, each could validate that against its own copy of the structure.

It may be simpler for a compiler to reject duplicate definitions than to
accept a duplicate definition but squawk if anything doesn't match, but
that doesn't mean that a compiler that rejects such definitions should be
regarded as "better" than one that accepts them but validates them.
Keith Thompson
2017-04-20 19:17:16 UTC
Permalink
Post by David Brown
Post by Malcolm McLean
Post by David Brown
They are helpful even when you know (or can easily look up) the exact
order of the members, and when you are initialising all of them -
because they document exactly which members are being initialised to
which values. It makes it easier to write correct code, easier to read
what is going on, easier to check that the code is correct, and more
robust if something in the struct changes.
I agree with everything except the last.
If the struct changes, you want all the initialisations to break. The risk of
the code appearing to compile cleanly when in fact the change has invalidated
it is too great.
If you know of a way to force the initialisation to break at /compile/
time when the struct is changed, then I'd love to know. Compiler
warnings on too few or too many initialisers will help if something is
added or removed, but not if it is re-ordered.
I agree that it would be good to have a way to force a compile time
error or warning on the initialiser when the struct it changed. And if
I were making a significant change to a struct, and did not already know
exactly where all its initialisation uses were, I would seriously
consider changing the name of the struct in order to get a compile time
error on code that needs to be re-checked.
But failing that, designated initialisers are the next best thing. And
for many changes to the struct, they are perfectly good - they are
robust in the case of changing the order of the members, or removing
members (you get a compile-time error if they have been set, which is
fine), or adding members for which a default of 0 is acceptable. That
is pretty good. The alternative, initialising by order of members, will
fail badly at /run/ time when the order of elements is changed. That is
the worst possible situation.
For example, the type `struct tm` in `<time.h>` (N1570 7.27.1) is
required to have at least 9 specified members of type int, but they can
be in any order and there can be additional implementation-defined
members.

This:

struct tm t = { 9, 14, 12, 20, 3, 2017-1900 };

is not guaranteed to set tm_sec to 9, tm_min to 14, etc. But this:

struct tm t = { .tm_sec = 9, .tm_min = 14, .tm_hour = 12,
.tm_mday = 20, .tm_mon = 4, .tm_year = 2017-1900 };

will set the desired fields correctly regardless of what other fields
might be defined by the current implementation.
--
Keith Thompson (The_Other_Keith) kst-***@mib.org <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Richard Bos
2017-04-22 21:29:55 UTC
Permalink
Post by David Brown
But failing that, designated initialisers are the next best thing. And
for many changes to the struct, they are perfectly good - they are
robust in the case of changing the order of the members, or removing
members (you get a compile-time error if they have been set, which is
fine), or adding members for which a default of 0 is acceptable. That
is pretty good. The alternative, initialising by order of members, will
fail badly at /run/ time when the order of elements is changed. That is
the worst possible situation.
Quibble: it may also fail at compile time, if the types involved are
different enough. But it isn't guaranteed to. Which is, perhaps, even
more dangerous.

Richard
David Brown
2017-04-23 09:25:48 UTC
Permalink
Post by Richard Bos
Post by David Brown
But failing that, designated initialisers are the next best thing. And
for many changes to the struct, they are perfectly good - they are
robust in the case of changing the order of the members, or removing
members (you get a compile-time error if they have been set, which is
fine), or adding members for which a default of 0 is acceptable. That
is pretty good. The alternative, initialising by order of members, will
fail badly at /run/ time when the order of elements is changed. That is
the worst possible situation.
Quibble: it may also fail at compile time, if the types involved are
different enough. But it isn't guaranteed to. Which is, perhaps, even
more dangerous.
Agreed.
Post by Richard Bos
Richard
Noob
2017-04-20 07:17:51 UTC
Permalink
Post by fl
const GPIOTiva_Config GPIOTiva_config = {
.pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
.callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
.numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
.numberOfCallbacks = sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
.intPriority = (~0)
};
https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html
Loading...