473,657 Members | 2,806 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Is this legal? Aesthetically acceptable?

I'm writing an abstraction layer over an OS's ability to do non-blocking
reads and writes on various things. The basic idea is that the caller
will be able to let the I/O happen in the background, and will poll
for completion using something along the lines of *nix's select() or
Win32's WaitForMultiple Objects, and when the OS signals that a read has
completed or a write will be accepted the abstraction layer will be called
to actually do the I/O call and call any code that's interested in it.

The read and write handling have some basic asymmetry, so the abstraction
layer's data and the interface to the code that requests reads and writes
will be different depending on which direction the data is going, but
the basic poll-and-call-handler pattern in the main loop will be the
same either way.

What I'd like to do is something like this:
--------
/*in overlapped.h*/
struct overlapped_cook ie
{
sync_type need_attention;
struct overlapped_data *internal;
};
--------
/*in overlapped-read.c*/
struct overlapped_data
{
/*Internal data for read bookkeeping*/
};

struct overlapped_cook ie *setup_read(/*args*/)
{
struct overlapped_cook ie *ret=malloc(siz eof *ret);
if(!ret)
return NULL;
ret->internal=mallo c(sizeof ret->internal);
if(!ret->internal)
{
free(ret);
return NULL;
}

/*Continue with initialization*/
}
--------
/*in overlapped-write.c*/
struct overlapped_data
{
/*Internal data for write bookkeeping*/
};

struct overlapped_cook ie *setup_write(/*args*/)
{
struct overlapped_cook ie *ret=malloc(siz eof *ret);
if(!ret)
return NULL;
ret->internal=mallo c(sizeof ret->internal);
if(!ret->internal)
{
free(ret);
return NULL;
}

/*Continue with initialization*/
}
--------

The idea is that the main loop can do something like:
--------
/*These arrays are populated by other code to keep an up-to-date list of
reads and writes (possibly to be extended to other events) that are
being waited for
*/
sync_type sync[N];
int (*handler[N])(struct overlapped_cook ie *);
struct overlapped_cook ie *all_overlapped[N];

/*main loop, minus error handling:*/
while(!are_we_d one_yet())
{
int which_one=do_wa it(sync,num_fil led);
handler[which_one](all_overlapped[which_one]);
}
--------
I have two questions about this:

(1) Is it legal and well-defined for external code to use a pointer to
the same incomplete struct type for what is actually different types
in different translation units, as long as no read struct ever gets
passed to the write functions and no write struct ever gets passed to
the read functions?
(I'm fairly sure, but not certain, that the struct pointer representation
requirements make this valid.)

(2) Is this considered acceptable, maintainable, and not aesthetically
unpleasant by other C programmers? Or, at least, no worse than the
alternative of forcing the main poll loop to handle reads and writes
separately?
(This one I'm not so sure about.)

(2b) Is there a better way to do this?
dave

--
Dave Vandervies dj******@csclub .uwaterloo.ca
I _am_ consistent - if one of those other pointer guide writers came
here and asked for comments, they'd get chewed out just as badly.
--Richard Bos in comp.lang.c
May 29 '07 #1
6 1540
Dave Vandervies wrote On 05/29/07 13:33,:
[code and exposition snipped for brevity; see up-thread]
I have two questions about this:

(1) Is it legal and well-defined for external code to use a pointer to
the same incomplete struct type for what is actually different types
in different translation units, as long as no read struct ever gets
passed to the write functions and no write struct ever gets passed to
the read functions?
(I'm fairly sure, but not certain, that the struct pointer representation
requirements make this valid.)
As far as I can see, it's legal. The incomplete type is
completed differently in the read and write implementations ,
but as long as the barrier between them remains unbroken I
don't see how any trouble can arise.
(2) Is this considered acceptable, maintainable, and not aesthetically
unpleasant by other C programmers? Or, at least, no worse than the
alternative of forcing the main poll loop to handle reads and writes
separately?
(This one I'm not so sure about.)
It's perhaps a matter of taste, but I'd consider the
"identifier overloading" to be Asking For Trouble, hence
UNmaintainable and UNacceptable. A programmer encounters
a `struct overlapped_data ', educates himself about what it
is and how it's used, and then runs into another `struct
overlapped_data ' that looks and behaves differently. That
would be unremarkable if the two divergent struct types
came from independent libraries being integrated into the
same program, but to find the conflict within one single
package of related functions ... Ugh. Double ugh.
(2b) Is there a better way to do this?
One possibility would be to use a `void*' as the
"externally visible" pointer to the internal struct, and
to convert it to a `struct overlapped_read _data*' or to a
`struct overlapped_writ e_data*' internally. Completely
gives up on type safety, but does little or no violence to
your framework as it stands.

Another approach might be to rearrange things a bit.
Instead of having the overlapped_cook ie struct point at
different varieties of overlapped_data structures, make
the overlapped_cook ie be the first element of both flavors
of overlapped_data :

struct overlapped_cook ie {
sync_type need_attention;
/* any other externally-visible stuff
* that you always want
*/
};

In the internals of the read and write implementations :

struct overlapped_read _data {
struct overlapped_cook ie cookie;
/* internal stuff specific to read */
};

and

struct overlapped_writ e_data {
struct overlapped_cook ie cookie;
/* internal stuff specific to write */
};

Your external interface would traffic in pointers to
struct overlapped_cook ie objects, which are elements of
the struct overlapped_*_da ta objects allocated internally.
Thanks to 6.7.2.1p13 it is permissible to convert a struct
pointer to a pointer to its first element and vice versa,
so an internal function can convert a cookie pointer to
a pointer to the struct that contains it.

It's still a matter of taste, but I find the second
approach far more palatable than the first.

--
Er*********@sun .com
May 29 '07 #2
Eric Sosman writes:
>Dave Vandervies wrote On 05/29/07 13:33,:
>(1) Is it legal and well-defined for external code to use a pointer to
the same incomplete struct type for what is actually different types
in different translation units, as long as no read struct ever gets
passed to the write functions and no write struct ever gets passed to
the read functions?
(I'm fairly sure, but not certain, that the struct pointer representation
requirements make this valid.)

As far as I can see, it's legal. The incomplete type is
completed differently in the read and write implementations ,
but as long as the barrier between them remains unbroken I
don't see how any trouble can arise.
What barrier? Google "link time optimization".
I don't really expect trouble in this case either, but...
(...)
>(2b) Is there a better way to do this?

One possibility would be to use a `void*' as the
"externally visible" pointer to the internal struct, and
to convert it to a `struct overlapped_read _data*' or to a
`struct overlapped_writ e_data*' internally. Completely
gives up on type safety, but does little or no violence to
your framework as it stands.
Or point to a union which contains both structs. Wastes
memory for the smallest struct though.
Another approach might be to rearrange things a bit.
Instead of having the overlapped_cook ie struct point at
different varieties of overlapped_data structures, make
the overlapped_cook ie be the first element of both flavors
of overlapped_data :
(...)
Thanks to 6.7.2.1p13 it is permissible to convert a struct
pointer to a pointer to its first element and vice versa,
so an internal function can convert a cookie pointer to
a pointer to the struct that contains it.
Alternatively, declare both structs in a header file and
make them members of a union in that header. Then thanks
to C99 6.5.2.3p5 it's OK to use the common initial members
of either of the struct types - as long as the union is
in scope (even if you do not use it).

--
Regards,
Hallvard
May 30 '07 #3
Hallvard B Furuseth wrote On 05/30/07 14:55,:
Eric Sosman writes:
>>Dave Vandervies wrote On 05/29/07 13:33,:
>>>(2b) Is there a better way to do this?

One possibility would be to use a `void*' as the
"externally visible" pointer to the internal struct, and
to convert it to a `struct overlapped_read _data*' or to a
`struct overlapped_writ e_data*' internally. Completely
gives up on type safety, but does little or no violence to
your framework as it stands.

Or point to a union which contains both structs. Wastes
memory for the smallest struct though.
It also exposes the details of the no-longer-opaque
structs, making it difficult to change those details at
a later date. (The O.P. did not say in so many words that
opacity was a goal, so I may be reading more than is there.)
> Another approach might be to rearrange things a bit.
Instead of having the overlapped_cook ie struct point at
different varieties of overlapped_data structures, make
the overlapped_cook ie be the first element of both flavors
of overlapped_data :
(...)
Thanks to 6.7.2.1p13 it is permissible to convert a struct
pointer to a pointer to its first element and vice versa,
so an internal function can convert a cookie pointer to
a pointer to the struct that contains it.

Alternatively, declare both structs in a header file and
make them members of a union in that header. Then thanks
to C99 6.5.2.3p5 it's OK to use the common initial members
of either of the struct types - as long as the union is
in scope (even if you do not use it).
Again, opacity is lost. Also, I don't think this quite
obeys the letter of the law: 6.5.2.3p5 specifically talks
about accessing struct elements inside union objects, not
about free-standing structs of the same types.

I once fixed a bug that had to do with exactly this
distinction. As in 6.5.2.3, we had a bunch of structs with
a common initial subsequence, and a union containing all of
them. Vastly simplified and violently paraphrased:

struct One {
int type; /* always equal to 1 */
int payload;
};

struct Two {
int type; /* always equal to 2 */
double data;
};

union All {
struct One eins;
struct Two zwei;
};

The bug arose when a pointer to a free-standing struct One
instance was passed to a function expecting a union pointer:

void printValue(cons t union All *ptr) {
switch (ptr->eins.type) {
case 1:
printf ("one: %d\n", ptr->eins.payload );
break;
case 2:
printf ("two: %g\n", ptr->zwei.data);
break;
...
}
}

...

struct One instance = { 1, 42 };
printValue ((union All*) &instance);

At first glance this might look all right, but what
it actually produced was the platform's equivalent of a
bus error. The problem was that the alignment requirement
for a struct Two was stricter than that for a struct One,
so the alignment for a union All was also stricter than
for a struct One. The free-standing struct One instance
was properly aligned for a struct One (of course), but
happened not satisfy the stricter union All requirement.
Unfortunately, the compiler "knew" that its argument would
point to strictly-aligned memory, and generated instructions
that faulted when the actual argument turned out to be too
leniently aligned.

To put it another way: every valid union All pointer
was a valid struct One pointer, but the converse didn't
hold.

Now, this might not be an issue in the O.P.'s case,
because he's using malloc() to obtain memory for instances
of his "variant" structs and malloc()'s alignment suffices
for anything at all. But that doesn't strike me as a safe
practice for the long haul: Someday, somebody may embed a
struct One in a struct BigBox and not put it at the very
start, or somebody may mallocate an array of struct One
instances -- the [0] element will be splendidly aligned,
but what about the [1] element?

From 6.5.2.3p5 and from other parts of the Standard,
we can deduce that the common initial subsequences of
the various structs that appear together in unions must
be arranged identically: You could walk through them with
offsetof() and get the same answers from all variants.
But identical layout isn't in itself enough to avoid
undefined behavior in type punning. And when there's a
perfectly clean (and opacity-preserving) method available,
there seems little incentive to juggle hand grenades.

--
Er*********@sun .com

May 30 '07 #4
Eric Sosman writes:
Hallvard B Furuseth wrote On 05/30/07 14:55,:
>Alternativel y, declare both structs in a header file and
make them members of a union in that header. Then thanks
to C99 6.5.2.3p5 it's OK to use the common initial members
of either of the struct types - as long as the union is
in scope (even if you do not use it).

Again, opacity is lost. Also, I don't think this quite
obeys the letter of the law: 6.5.2.3p5 specifically talks
about accessing struct elements inside union objects, not
about free-standing structs of the same types.
Oops, you are right.

--
Regards,
Hallvard
May 31 '07 #5
In article <1180559022.950 171@news1nwk>,
Eric Sosman <Er*********@Su n.COMwrote:
>Hallvard B Furuseth wrote On 05/30/07 14:55,:
>Or point to a union which contains both structs. Wastes
memory for the smallest struct though.

It also exposes the details of the no-longer-opaque
structs, making it difficult to change those details at
a later date. (The O.P. did not say in so many words that
opacity was a goal, so I may be reading more than is there.)
It was, but more as a matter of good habits than for any specific reason.
(I've seen too much code that was made into a crawling horror by intricate
cross-linking of dependencies between different parts, so I like to make
it as difficult as possible to introduce dependencies on anything other
than a published interface.)

After some further thought, hiding the opaque structs behind a void
pointer seems to have been what I was after. (I was clearly too close
to the initial code that I was trying to abstract to actually see that;
it's probably what I would have suggested had somebody else asked the
question.)
I ended up deciding to make my abstraction layer even more abstract as
well, and having done that it makes even more sense to hide as many of
the details as possible behind a void *.
dave
--
Dave Vandervies dj******@csclub .uwaterloo.ca
I sense some Perl coming on. --Mike Andrews and Steve VanDevender
Be sure to [...] have someone nearby if a particularly in the scary
severe attack requires restraint or sedation. devil monastery
Jun 5 '07 #6
Dave Vandervies wrote:
In article <1180559022.950 171@news1nwk>,
Eric Sosman <Er*********@Su n.COMwrote:
>Hallvard B Furuseth wrote On 05/30/07 14:55,:
>>Or point to a union which contains both structs. Wastes
memory for the smallest struct though.
It also exposes the details of the no-longer-opaque
structs, making it difficult to change those details at
a later date. (The O.P. did not say in so many words that
opacity was a goal, so I may be reading more than is there.)

It was, but more as a matter of good habits than for any specific reason.
(I've seen too much code that was made into a crawling horror by intricate
cross-linking of dependencies between different parts, so I like to make
it as difficult as possible to introduce dependencies on anything other
than a published interface.)

After some further thought, hiding the opaque structs behind a void
pointer seems to have been what I was after. (I was clearly too close
to the initial code that I was trying to abstract to actually see that;
it's probably what I would have suggested had somebody else asked the
question.)
I ended up deciding to make my abstraction layer even more abstract as
well, and having done that it makes even more sense to hide as many of
the details as possible behind a void *.
An advantage of using a pointer to an incomplete struct
type instead of a pointer to void is that you retain a little
bit of type safety. Consider the scenarios:

/* in the header: */
void doSomething(voi d *); /* pass a Gizmo pointer */
/* stupid caller: */
doSomething ("Gizmo"); /* no murmur from compiler */

vs.

/* in the header: */
void doSomething(str uct gizmo*);
/* stupid caller: */
doSomething ("Gizmo"); /* compile-time diagnostic */

Now, it's clear from the outset that users of your software
are of above average intelligence, nowhere near stupid enough to
pass a string when something else is required. But if there are
a lot of different but related types floating about -- struct
gizmo, struct pseudoGizmo, struct gizmoExtended -- even an elite
audience may suffer occasional confusion. If that should happen,
it's usually better to rescue them with the compiler than with
the debugger ...

The cost of providing such extra safety is usually small:
One new identifier pollutes the user's name space for each type
you need to invent. That cost is usually tolerable, considering
that doSomething() and its brethren are even more polluting (they
probably have external linkage). A handful of no-linkage type
names will not usually inconvenience the user who's already made
up his mind to accept your package's more numerous (usually)
external identifiers.

Treasure my free advice: It's worth every cent you pay ...

--
Eric Sosman
es*****@acm-dot-org.invalid
Jun 5 '07 #7

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

1
4280
by: pakkocool | last post by:
¿Como ganar dinero en internet? Lee atentamente el siguiente texto, es super interesante y te hara ganar muchos dolares si sigues las instrucciones y le pones empeño: Hace unos días que estoy buscando la forma de ganar algún dinero más extra, a parte del que gano por navegar en Internet. Para ello recorro grupos de noticias, tablones, avisos clasificados, buscadores, etc. En todos estos lugares encuentro mensajes como el que...
3
2205
by: Chris Johnson | last post by:
Greetings all: I come across an interesting question (to me anyway) and I do not know the answer. Code/Questions follow: #include <iostream> #if 0 // uncommenting *should* make call ambigous because of // odering deduction of the function signature,
0
1265
by: mykidisgollum | last post by:
Greetings, I have code which prints a document who's attributes are saved as flags in a database. One of the those attributes is letter or legal. When I am printing, I use the following code, which is the ONLY way I could come up with to reliably print to legal paper, assuming a default of letter. There HAS to be a better way. I'm having to loop through all paper options on the remote server's printer, until I
7
4348
by: __PPS__ | last post by:
Actually what I mean is that - if I have some memory buffer, lets say char a; and then I do like this: DWORD num = 0x1234; *(DWORD*)a = num; (1) *(DWORD*)(a+1) = num; (2) either (1) or (2) will assign dword value to not dword aligned address. The question is - will this code be fatal on some systems??
2
3711
by: Thomas Paul Diffenbach | last post by:
I'm trying to write a space efficient string (nul terminated array of char), storing the characters directly unless the number of characters is too large to be so stored, and storing a pointer to other storage otherwise. Rather than lose space to padding by just using a sentinal bool to indicate which member of the union has been written to, I want to overload the last character of the directly stored string as the sentinal.
11
2037
by: Alberto Giménez | last post by:
Hi, I've seen some object oriented programming bits out there and i'm not sure if they're legal. For example: struct Object { int field1; int field2; }; struct SubObject { int field1; /* the same as Object */
2
1675
by: Army1987 | last post by:
Is this program legal C89? /* no headers included */ int main(void) { if (sizeof (exit(0), 0), ((void (* )(int))&exit)( (puts((const char *)"hello, world"), 0) ), 0) {
4
2161
by: none | last post by:
I have an ASP.NET application, hosted on two web servers. I am looking for advice on what should be an acceptable level of page faults on these production servers. If the acceptable level is zero, then where should I begin reducing page faults in code? What should I look for in code to reduce page faults? Having to reduce page faults is something I have not had been tasked with before, so I am not sure what is acceptable and where to...
36
1925
by: Max | last post by:
This was asked in a C test. Is the following code legal C? Is it legal in C90? C99? #define main() int main #define mainbody () { return 0; } mainbody
0
8403
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
8316
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
8833
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
8737
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
0
7345
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
6174
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
4168
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
4327
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
2
1967
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.