compound literals

M

Mantorok Redgormor

What is the point of them?

When should I use them?

They seem to be rather useless but hopefully someone can prove me
wrong. I think they are just additions to c99 but didn't exist in
c89/90.


Also, when is it appropriate to cast? Casting seems to be frowned up
on so it makes me wonder when it is appropriate to ever cast.
 
N

Nick Austin

What is the point of them?

When should I use them?

Passing an argument to a function without using a temporary:

foo( (struct bar){ 1, 2 } );

Adding storage qualifiers:

(const char[]){ "foo" }

Nick.
 
K

Kevin Bracey

What is the point of them?

When should I use them?

They seem to be rather useless but hopefully someone can prove me
wrong. I think they are just additions to c99 but didn't exist in
c89/90.

They are new in C99 (and hence don't exist in C++ either).

They're not a massively useful construct, but when you do find an application
for them, they can help tidy up code quite well. Here are two real-life
examples, from some SCSI software:

/* Array literal constants defining well-known SCSI commands */
#define CDB_TEST_READY ((const uint8_t[6]) { 0x00 })
#define CDB_INQUIRY_L ((const uint8_t[6]) { 0x12, 0, 0, 0, 36, 0 })
#define CDB_CAPACITY ((const uint8_t[10]) { 0x25 })

With these defines, a whole SCSI command can be simply defined and passed
to an API by giving the macro name, without having to manually declare and
initialise a variable.

Now a more complex example, which will lead in nicely to your cast question.
The software internally uses error codes (from 0-255), but has to supply the
outside world with a pointer to an error structure (consisting of a code,
followed by a variable length string). A simple (!) form of knocking up a
mapping table, ignoring internationalisation issues, can work as follows:

--- errors.h ---

// typedef struct { int errnum; char errmess[252]; } oserror;

#define SCSI_ErrorBase 0x20100

enum
{
SCSI_NoRoom = 0x00,
SCSI_SWIunkn,
SCSI_RCunkn,
...
};

oserror *scsi_error(unsigned err);

--- errors.c ---

#define ErrType(str) const struct { int errnum; char errmess[sizeof str]; }
#define ErrBlock(num, str) (ErrType(str)) { num, str }
#define ErrEntry(num, str) [num] = (oserror *) &ErrBlock(SCSI_ErrorBase+num,str)

static oserror * const errtable[256] =
{
ErrEntry(SCSI_NoRoom, "No room for SCSI driver workspace"),
ErrEntry(SCSI_SWIunkn, "Unknown SCSI SWI number"),
ErrEntry(SCSI_RCunkn, "Unknown reason code for SCSI SWI"),
...
};

oserror *scsi_error(unsigned err)
{
if (err >= 256) return (oserror *) err;

if (errtable[err]) return errtable[err];

return scsi_error(SCSI_InvalidParms);
}

If you pick through the macro definitions, you'll note that this also uses
designated initialisers, another C99 feature, to match the errtable array
entries to the enum. Also be aware that the code is not portable for other
reasons (mentioned below).
Also, when is it appropriate to cast? Casting seems to be frowned up
on so it makes me wonder when it is appropriate to ever cast.

Casting is generally frowned upon, yes, as it is usually unnecessary - if you
find yourself needing to cast you're probably doing something non-portable or
you're patching over an error. Here are some examples, in ascending order of
frown-factor.

It can be used straightforwardly to perform a conversion (eg manually
converting a float to an int), but this is usually not required, as implicit
conversions happen on assignment and when passing values to prototyped
functions. You might need casts in expressions; one valid example might be:

int m = <whatever>, n = <whatever>;
float ratio = (float) m / (float) n;

Without the casts, the division would be performed as an integer division.

It can be used to fudge qualifiers like "const" - for example a definition of
strchr (from <string.h>) would have to have a cast to coerce the "const char
*" passed in into a returned "char *".

It can be used to change the interpretation of a pointer (eg the cast of the
arbitrary sized compound literal to oserror * in the ErrEntry macro above) -
this sort of casting is only valid if you know that the thing being pointed
to can be accessed through the new type of pointer; C gives some assurances
about when this works, eg when structures have common initial members, but
otherwise you have to be careful when writing portable code.

Most dubiously, you can convert between fundamentally different types - eg
the cast of an unsigned to an oserror * in the scsi_error function. That is
definitely non-portable. Don't do it.
 
M

Mantorok Redgormor

Kevin Bracey said:
In message <[email protected]>
What is the point of them?

When should I use them?

They seem to be rather useless but hopefully someone can prove me
wrong. I think they are just additions to c99 but didn't exist in
c89/90.

They are new in C99 (and hence don't exist in C++ either).

They're not a massively useful construct, but when you do find an application
for them, they can help tidy up code quite well. Here are two real-life
examples, from some SCSI software:

/* Array literal constants defining well-known SCSI commands */
#define CDB_TEST_READY ((const uint8_t[6]) { 0x00 })
#define CDB_INQUIRY_L ((const uint8_t[6]) { 0x12, 0, 0, 0, 36, 0 })
#define CDB_CAPACITY ((const uint8_t[10]) { 0x25 })

With these defines, a whole SCSI command can be simply defined and passed
to an API by giving the macro name, without having to manually declare and
initialise a variable.

Now a more complex example, which will lead in nicely to your cast question.
The software internally uses error codes (from 0-255), but has to supply the
outside world with a pointer to an error structure (consisting of a code,
followed by a variable length string). A simple (!) form of knocking up a
mapping table, ignoring internationalisation issues, can work as follows:

--- errors.h ---

// typedef struct { int errnum; char errmess[252]; } oserror;

#define SCSI_ErrorBase 0x20100

enum
{
SCSI_NoRoom = 0x00,
SCSI_SWIunkn,
SCSI_RCunkn,
...
};

oserror *scsi_error(unsigned err);

--- errors.c ---

#define ErrType(str) const struct { int errnum; char errmess[sizeof str]; }
#define ErrBlock(num, str) (ErrType(str)) { num, str }
#define ErrEntry(num, str) [num] = (oserror *) &ErrBlock(SCSI_ErrorBase+num,str)

static oserror * const errtable[256] =
{
ErrEntry(SCSI_NoRoom, "No room for SCSI driver workspace"),
ErrEntry(SCSI_SWIunkn, "Unknown SCSI SWI number"),
ErrEntry(SCSI_RCunkn, "Unknown reason code for SCSI SWI"),
...
};

oserror *scsi_error(unsigned err)
{
if (err >= 256) return (oserror *) err;

if (errtable[err]) return errtable[err];

return scsi_error(SCSI_InvalidParms);
}

If you pick through the macro definitions, you'll note that this also uses
designated initialisers, another C99 feature, to match the errtable array
entries to the enum. Also be aware that the code is not portable for other
reasons (mentioned below).
Also, when is it appropriate to cast? Casting seems to be frowned up
on so it makes me wonder when it is appropriate to ever cast.

Casting is generally frowned upon, yes, as it is usually unnecessary - if you
find yourself needing to cast you're probably doing something non-portable or
you're patching over an error. Here are some examples, in ascending order of
frown-factor.

It can be used straightforwardly to perform a conversion (eg manually
converting a float to an int), but this is usually not required, as implicit
conversions happen on assignment and when passing values to prototyped
functions. You might need casts in expressions; one valid example might be:

int m = <whatever>, n = <whatever>;
float ratio = (float) m / (float) n;

Without the casts, the division would be performed as an integer division.

It can be used to fudge qualifiers like "const" - for example a definition of
strchr (from <string.h>) would have to have a cast to coerce the "const char
*" passed in into a returned "char *".

It can be used to change the interpretation of a pointer (eg the cast of the
arbitrary sized compound literal to oserror * in the ErrEntry macro above) -
this sort of casting is only valid if you know that the thing being pointed
to can be accessed through the new type of pointer; C gives some assurances
about when this works, eg when structures have common initial members, but
otherwise you have to be careful when writing portable code.

Most dubiously, you can convert between fundamentally different types - eg
the cast of an unsigned to an oserror * in the scsi_error function. That is
definitely non-portable. Don't do it.

Thanks for the great post! Very informative.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top