Initializing compound type containing opaque type

N

Nate Eldredge

Consider the following pseudo-code:

#include <opaque.h>

struct foo {
int a;
opaque_t op;
int b;
};

struct foo blah = { 17, /* ??? */ , 23 };

Here we suppose that `opaque_t' is defined in <opaque.h> as some type
not known to us, or which we cannot rely on. In particular, it may be
a scalar or compound type. The goal is to initialize `blah' in such a
way that `blah.a == 17' and `blah.b == 23'. We don't care about the
value of `blah.op'.

It seems to me that there is no way to do this in standard C. Is this
correct, or am I missing something?

A workaround would be to rearrange the members of `struct foo' as

struct foo2 {
int a;
int b;
opaque_t op;
};

struct foo2 blah2 = { 17, 23 };

I believe this is legal, but I could be wrong. My compiler warns
about a missing initializer, but accepts the code.

If `opaque_t' is known to be a compound type, we could write

struct foo blah3 = { 17, { }, 23 };

which again provokes a missing initializer warning but is accepted.
Again I am curious whether it is actually legal.

I thought of this issue after reading a post here a couple weeks ago,
"Exception handling crashes or exits" by Fabiano Sidler,
<[email protected]>, in which the poster wants a struct
with a jmp_buf as one member. He initializes that member with { 0 },
which is certainly non-portable. But I wondered if there is a
portable way to do the same thing.

If not, it seems like it might be useful for the authors of <opaque.h>
to provide a macro which is suitable as an initializer for opaque_t.
E.g. <opaque.h> might contain

typedef opaque_t struct {
int x,
double d;
};

#define OPAQUE_INITIALIZER { 0, 0.0 }

so that one could write

struct foo blah = { 17, OPAQUE_INITIALIZER, 23 };

In particular, this could be a useful extension for library authors to
provide for opaque library types (e.g. jmp_buf, FILE, etc).

Any thoughts?
 
V

vippstar

Consider the following pseudo-code:

#include <opaque.h>

struct foo {
int a;
opaque_t op;
int b;

};

struct foo blah = { 17, /* ??? */ , 23 };

Here we suppose that `opaque_t' is defined in <opaque.h> as some type
not known to us, or which we cannot rely on. In particular, it may be
a scalar or compound type. The goal is to initialize `blah' in such a
way that `blah.a == 17' and `blah.b == 23'. We don't care about the
value of `blah.op'.

It seems to me that there is no way to do this in standard C. Is this
correct, or am I missing something?

A workaround would be to rearrange the members of `struct foo' as

struct foo2 {
int a;
int b;
opaque_t op;

};

struct foo2 blah2 = { 17, 23 };

I believe this is legal, but I could be wrong. My compiler warns
about a missing initializer, but accepts the code.

Yes, it's legal.
(note: In POSIX it wouldn't be; *_t are reserved identifiers)
If `opaque_t' is known to be a compound type, we could write

struct foo blah3 = { 17, { }, 23 };

which again provokes a missing initializer warning but is accepted.
Again I am curious whether it is actually legal.

Also legal.
I thought of this issue after reading a post here a couple weeks ago,
"Exception handling crashes or exits" by Fabiano Sidler,
<[email protected]>, in which the poster wants a struct
with a jmp_buf as one member. He initializes that member with { 0 },
which is certainly non-portable. But I wondered if there is a
portable way to do the same thing.

Yes, jmp_buf foo = {0};

Though that'd initialize all members to 0, 0.0 or NULL depending on
type, recursively applied for any aggregate
If not, it seems like it might be useful for the authors of <opaque.h>
to provide a macro which is suitable as an initializer for opaque_t.

Well, it is legal, so it's not useful.
E.g. <opaque.h> might contain
<snip>
 
B

Barry Schwarz

Consider the following pseudo-code:

#include <opaque.h>

struct foo {
int a;
opaque_t op;
int b;
};

struct foo blah = { 17, /* ??? */ , 23 };

Here we suppose that `opaque_t' is defined in <opaque.h> as some type
not known to us, or which we cannot rely on. In particular, it may be
a scalar or compound type. The goal is to initialize `blah' in such a
way that `blah.a == 17' and `blah.b == 23'. We don't care about the
value of `blah.op'.

It seems to me that there is no way to do this in standard C. Is this
correct, or am I missing something?

A workaround would be to rearrange the members of `struct foo' as

struct foo2 {
int a;
int b;
opaque_t op;
};

struct foo2 blah2 = { 17, 23 };

I believe this is legal, but I could be wrong. My compiler warns
about a missing initializer, but accepts the code.

An insufficient quantity of initializers is specifically allowed
(6.7.8-19 and -21). Compilers are allowed to issue diagnostic
messages for correct code. Some do so when assigning an int to a char
or when using gets. These are not so much warnings (a term not
defined in the standard) but more like reminders to the programmer
that something is "unusual" and worth a second look. As long as the
code complies with the standard, the compiler is obligated to accept
it.
If `opaque_t' is known to be a compound type, we could write

struct foo blah3 = { 17, { }, 23 };

which again provokes a missing initializer warning but is accepted.
Again I am curious whether it is actually legal.

I thought of this issue after reading a post here a couple weeks ago,
"Exception handling crashes or exits" by Fabiano Sidler,
<[email protected]>, in which the poster wants a struct
with a jmp_buf as one member. He initializes that member with { 0 },
which is certainly non-portable. But I wondered if there is a

It is portable, for both aggregate and scalar objects. See 6.7.8-16,
-11, and -19.
portable way to do the same thing.

If not, it seems like it might be useful for the authors of <opaque.h>
to provide a macro which is suitable as an initializer for opaque_t.
E.g. <opaque.h> might contain

typedef opaque_t struct {
int x,
double d;
};

#define OPAQUE_INITIALIZER { 0, 0.0 }

so that one could write

struct foo blah = { 17, OPAQUE_INITIALIZER, 23 };

In particular, this could be a useful extension for library authors to
provide for opaque library types (e.g. jmp_buf, FILE, etc).

{0} should suffice. If the object requires non-zero initialization,
the library should provide an initialization function.
 
N

Nate Eldredge

Eric Sosman said:
In C99 you could use a "compound literal:"

struct foo blah = (struct foo){.a = 17, .b = 23};

(Double-check my syntax; I'm typing this hurriedly.)

My compiler says "error: initializer element is not constant". It
does work if it is moved inside a function, so that blah is auto, but
that's of course entirely different. (Anyway, in that case we could
just initialize blah.a and blah.b by hand.)
In C90 you could initialize the unknown member to "zero of
the appropriate type:"

struct foo blah = { 17, { 0 }, 23 };

Some compilers may emit warnings for too few initializers, but
the initialization is valid; if opaque_t is compound, its sub-
elements are initialized to appropriate zeroes. The warnings may
be a nuisance, but they do no actual harm.

Interesting, so the OP was correct after all. I never knew about
"zero of the appropriate type". I assumed that would be wrong if
opaque_t turned out to be a scalar type, or a compound whose first
member was also compound, but apparently not. Those clever language
designers, always one step ahead. :)

Thanks to all who replied.
 
V

vippstar

Consider the following pseudo-code:

#include <opaque.h>

struct foo {
int a;
opaque_t op;
int b;

};

struct foo blah = { 17, /* ??? */ , 23 };

Here we suppose that `opaque_t' is defined in <opaque.h> as some type
not known to us, or which we cannot rely on. In particular, it may be
a scalar or compound type. The goal is to initialize `blah' in such a
way that `blah.a == 17' and `blah.b == 23'. We don't care about the
value of `blah.op'.

It seems to me that there is no way to do this in standard C. Is this
correct, or am I missing something?

Whoops, I forgot to reply to this question at my post.
In C99 you can.

struct foo blah = { .a = 17, .b = 23 };

Mr Sossman mentioned compound literals, but I'm unsure why he didn't
mention this which is even simpler.

You can also do this for array elements

char foo[2] = { .[1] = 'a' };
 
V

vippstar

In C99 you could use a "compound literal:"

struct foo blah = (struct foo){.a = 17, .b = 23};

(Double-check my syntax; I'm typing this hurriedly.)

I think you mean

struct foo blah = *(struct foo[]){ { .a = 17, .b = 23 } };
 
B

Ben Bacarisse

Not allowed, I think, at file scope.
My compiler says "error: initializer element is not constant".

Just use the "initialiser list" rather than providing a compound
literal. I.e. write:

struct foo blah = {.a = 17, .b = 23};

This works for file-scope declarations as well as block-scope ones.
It
does work if it is moved inside a function, so that blah is auto, but
that's of course entirely different.

Yes. 6.7.8 paragraph 13 permits an expression of "compatible
structure or union type" when the object has automatic storage. You
have to read though to paragraph 16 to get to the text:

"Otherwise, the initializer for an object that has aggregate or
union type shall be a brace-enclosed list of initializers..."

to see that a compound literal is not allowed as the sole "top-level"
initialiser for an object with static storage duration. At least that
is my explanation of your error message.
 
N

Nate Eldredge

William Pursell said:
If opaque_t is truly opaque, and not specified
in opaque.h, then the compiler won't accept
this definition of struct foo. If the compiler
is accepting this, then the header is specifying
opaque_t completely, so (I would argue) has
misnamed it because it isn't opaque.

By "opaque" I mean an object whose format and contents are not
intended to be used by the programmer, and are subject to change.
This is how I've heard it used in the past.

jmp_buf is an example. Certainly you can open up <setjmp.h> and find
out how it is defined on your system; maybe it's a struct with a
member for each of your CPU's registers. But you can't use any of
that information in a portable program, since on another system, or a
later version of the same system, it may be defined differently, or
used differently by the library, so in a more general sense you don't
really "know" how it's defined. All you "know" about jmp_buf is that
you can create one using setjmp() and pass it to longjmp() later on.
From the programmer's point of view it's just a magic cookie.

Other examples in the standard library would be time_t and FILE.
div_t is a library type that I would not call opaque; it is documented
as being a struct with two members, `quot' and `rem', whose types and
meanings are well described and guaranteed to be the same in all
conforming implementations.
 
B

Ben Bacarisse

William Pursell said:
(e-mail address removed) writes:

You can also do this for array elements
char foo[2] = { .[1] = 'a' };

There's no dot.  I.e.:

  char foo[2] = { [1] = 'a' };

Is that C99? I thought that was a gnu extension.

Yes, it is C99:

designation:
designator-list =

designator-list:
designator
designator-list designator

designator:
[ constant-expression ]
. identifier
 
K

Keith Thompson

William Pursell said:
If opaque_t is truly opaque, and not specified
in opaque.h, then the compiler won't accept
this definition of struct foo. If the compiler
is accepting this, then the header is specifying
opaque_t completely, so (I would argue) has
misnamed it because it isn't opaque.

There are different kinds of opacity. Consider type FILE, defined in
<stdio.h>. That header must completely specify the type -- but code
that uses it is expected to depend only on the standard functions and
the fact that it's an object type.
I don't understand what you mean by "not known to us". If
the compiler can get the definition from opaque_t, then
you can, too!

Sure you *can* -- but then the next version of the library defines it
differently and your code breaks.

It's both common and reasonable to define an "opaque" type that's
completely defined in the header, just by *asking* authors of client
code not to cheat by peeking at its innards. (Which is not to say
that there aren't other reasonable approaches.)
 
N

Nick Keighley

There are different kinds of opacity.  Consider type FILE, defined in
<stdio.h>.  That header must completely specify the type

why? user code only needs a pointer
-- but code
that uses it is expected to depend only on the standard functions and
the fact that it's an object type.

<snip>
 
K

Keith Thompson

Nick Keighley said:
why? user code only needs a pointer


<snip>

The standard requires FILE to be

an object type capable of recording all the information needed to
control a stream, including its file position indicator, a pointer
to its associated buffer (if any), an _error indicator_ that
records whether a read/write error has occurred, and an
_end-of-file indicator_ that records whether the end of the file
has been reached

The declaration of FILE in <stdio.h> needn't indicate *how* it records
all that information. For example, it could be declared as

typedef unsigned char[_FILE_BYTES] FILE;

and the library code that uses it could cast the FILE* to, say,
_FILE_STRUCT* to access the information. Most implementations don't
do this; for one thing, the macro definitions for putc and getc are
going to expose some of the inner details anyway.

Since user code only needs to use FILE*, not type FILE itself, an
implementation that declares:

typedef void FILE;

would be perfectly sensible, except that it would violate the
standard's requirement. In my opinion, the standard over-specifies
this, but it's not a huge deal.
 
S

Szabolcs Nagy

Nate said:
Interesting, so the OP was correct after all. I never knew about
"zero of the appropriate type". I assumed that would be wrong if
opaque_t turned out to be a scalar type, or a compound whose first
member was also compound, but apparently not. Those clever language
designers, always one step ahead. :)

{0} initializer is wrong if the first member of opaque_t is an array
 
V

vippstar

Is it really? gcc -ansi -pedantic accepts it.


No it isn't, it's correct.

When not all of an object is initialized, the rest is initialized as
if it were 'static'.
 
N

Nate Eldredge

No it isn't, it's correct.

When not all of an object is initialized, the rest is initialized as
if it were 'static'.

Right, but in this example we have something like

typedef struct {
int arr[5];
double d;
} opaque_t;

struct foo {
int a;
opaque_t op;
int b;
};

struct foo blah = { 17, {0} , 23 };

which on the face of it would appear to initialize blah.op.arr with 0,
which doesn't make sense, instead of { 0 }, which would.

So if I'm understanding correctly, there is a feature in C whereby
{0}, "zero of appropriate type", is valid as an initializer for any
type, which for scalar types initializes to 0, and for aggregate types
initializes each element, recursively, to 0. Can someone provide a
pointer to the standard or another reference which describes this more
fully?

(Incidentally, I'm not sure if I'm correctly using the terms "compound
type" and "aggregate type". I intend them both to refer to a type
which is a structure, union or array.)
 
N

Nate Eldredge

Richard Heathfield said:
Nate Eldredge said:

It's covered fully in 3.5.7 Initialization (C89) and 6.7.8 Initialization
(C99).

Thanks. I bit the bullet and bought a copy of the standard, and now
it makes sense. I had been under the impression that when
initializing an aggregate which contains other aggregates, you had to
enclose the values for each sub-aggregate in its own set of braces.
But it appears that if you just supply a list of values, the compiler
will "flatten" the aggregate in order to assign them to members. I
didn't know about that. Thus given

struct bar {
struct {
int x;
} a;
struct {
struct {
int y;
} q;
} b;
};

you can write

struct bar argh = { { 1 }, { { 2 } } };

reflecting the recursive structure of struct bar, or you can write

struct bar argh = { 1, 2 };

which flattens it; both leave argh.a.x == 1 and argh.b.q.y == 2. I
has thought only the first form was legal.

And as an additional bit of syntactic sugar, when initializing a
non-aggregate type, you can enclose the value in braces, which I also
didn't know about. So

int foo = { 42 };

is legal.

Finally, 0 is a legal value for any non-aggregate type, be it integer,
floating point, or pointer. So combining all this, {0} is a legal
initializer for any type, and has the effect of initializing all
members to zero.

Have I understood it correctly? Thanks!
 
K

Keith Thompson

Nate Eldredge said:
Thanks. I bit the bullet and bought a copy of the standard, and now
it makes sense.
[...]

I hope you knew that a post-standard draft, which includes the C99
standard with the three Technical Corrigenda merged into it, is
available at no charge at
<http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf>.

I've paid for PDF copies of the C90 and C99 standards myself, but
n1256 is the one I use as a reference most often.
 

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,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top