To coerce or not...? struct sockaddr vs struct sockaddr_in

J

James Harris

Having updated my Debian system it now complains that I am using
an incompatible pointer type in warnings such as "passing arg 2
of 'bind' from incompatible pointer type" from code,

struct sockaddr_in sockad1;
....
retval = bind (sock1, &sockad1, sockad1len);

I can coerce the pointer with

retval = bind (sock1, (struct sockaddr *) &sockad1, sockad1len);

but that seems to me to be bad style - doesn't it overrides the
compiler's checking? I cannot instead change the defintion to
struct sockaddr sockad1 because my code uses members of the
sockaddr_in structure. Any other resolution I can think of is
messy. Why cannot the compiler accept that a pointer to a struct
sockaddr_in IS compatible with a pointer to struct sockaddr?
Should I coerce to keep the compiler happy or is there a better
way to approch the problem?

TIA,
- James
 
K

Kevin Easton

James Harris said:
Having updated my Debian system it now complains that I am using
an incompatible pointer type in warnings such as "passing arg 2
of 'bind' from incompatible pointer type" from code,

struct sockaddr_in sockad1;
...
retval = bind (sock1, &sockad1, sockad1len);

I can coerce the pointer with

retval = bind (sock1, (struct sockaddr *) &sockad1, sockad1len);

but that seems to me to be bad style - doesn't it overrides the
compiler's checking?

Yes, it is. But the bad style is not yours; it is the sockets API
you're using that is the root cause (it should have used a pointer to
union instead).
I cannot instead change the defintion to
struct sockaddr sockad1 because my code uses members of the
sockaddr_in structure. Any other resolution I can think of is
messy. Why cannot the compiler accept that a pointer to a struct
sockaddr_in IS compatible with a pointer to struct sockaddr?

Because C doesn't work that way. Pointers to different structs are
never assignment-compatible.
Should I coerce to keep the compiler happy or is there a better
way to approch the problem?

Since you can't change the signature of the function, the cast is the
best solution. If you're concerned about the possibility of the cast
hiding errors, you can reduce it to one instance of the cast in your
entire code (I'm making an educated guess as to the signature of the
bind function you're calling):

int bind_in(int sockfd, struct sockaddr_in *my_addr, socklen_t addrlen)
{
return bind(sockfd, (struct sockaddr *)my_addr, addrlen);
}

Then just call bind_in() instead of bind() whenever you're using a
sockaddr_in. The only cast to get right is the one in bind_in() - the
compiler will warn you if you try to pass anything other than struct
sockaddr_in * as the second parameter of bind_in().

- Kevin.
 
J

James Harris

Kevin, Many thanks for your help. It all makes a bit more sense
now!

Because C doesn't work that way. Pointers to different structs are
never assignment-compatible.

Does this just happen for structs? The third parameter seems to
accept any of int, long etc and perversely char, float and
double, as well as any size_X parameter. How can bind be
expected to make sense of a float???

I notice from the headers that socklen_t is defined from
__socklen_t which is declared as typedef unsigned int. Would I
be right in assuming that the compiler generates code to convert
between float and unsigned int? The code works with this as a
float. Horrible.

....I have since been looking at the generated assembler and
compilation of the line
retval = bind (sock1, (struct sockaddr *) &mysock, mysocklen);
generates the following in gcc 2.95.4. The line with a - is the
line used when mysocklen is an integer, a long, or a socklen_t
(all compile identically). The lines with a + replace that line
when s1len is declared as a float. I think the fistpll writes
the float as an integer, which goes in to EDX before finding its
way to EAX and being pushed, thus converting before making the
call. Note to self: if you want fast code be careful to use
integer if that's what your library calls expect.(!)
addl $-4,%esp
- movl -152(%ebp),%eax
+ flds -152(%ebp)
+ fnstcw -670(%ebp)
+ movw -670(%ebp),%ax
+ orw $3072,%ax
+ movw %ax,-672(%ebp)
+ fldcw -672(%ebp)
+ fistpll -680(%ebp)
+ movl -680(%ebp),%edx
+ movl -676(%ebp),%ecx
+ fldcw -670(%ebp)
+ movl %edx,%eax
pushl %eax
leal -116(%ebp),%eax
pushl %eax
movl -512(%ebp),%eax
pushl %eax
call bind
addl $16,%esp
movl %eax,%eax
movl %eax,-56(%ebp)
 
C

Chris Torek

Does this just happen for structs?

Structures and unions (and "enum"s, to some extent). The reason
that "pointer to struct X" and "pointer to struct Y" are not
compatible is that the first occurrence of "struct X" creates a
new type -- the type named "struct X" -- and then "struct Y" creates
another new type, the one named "struct Y". Since these are two
new types, they must be different types, even if the contents are
the same.

As Kevin Easton said, the socket interface is not very well designed,
and as a result you *must* cast (or take a trip through "void *")
in at least one place. (The type-safety, or lack thereof, is just
one aspect of where the original BSD socket interface goes wrong.
If I could go back in time and fix it, I would try to convince
someone to replace the bind(), connect(), and listen() calls with
a single call. But this is different problem entirely, having
nothing to do with C per se.)
The third parameter seems to
accept any of int, long etc and perversely char, float and
double, as well as any size_X parameter. How can [function F] be
expected to make sense of a float???

The actual name of "function F" is not relevant here but its
prototype, if it has one -- and apparently it does on your system
-- is. That prototype is:

typedef unsigned int some_integral_type; /* as it happens */
int F(int, struct S *, some_integral_type);

In C, any call to a function that has a prototype in scope at
the point of the call makes use of the types in the prototype.
They cause each actual parameter in the call to be passed "as
if by ordinary assignment".

If you write:

int i;
float f = 3.14;

and then write:

i = f;

this instructs the compiler to truncate the ".14" part, leaving
just 3, and assigning 3 to i -- using whatever machine code sequence
is required to do that. (If the compiler can prove to itself that
this sets i to 3, this could be as simple as "move constant_3 =>
register holding i" or some such, rather than the 11 lines of
x86 assembly I will refrain from quoting. :) )

Since a prototyped call is "just like" assignment, a call of the
form:

extern void zog(int);
zog(f);

must likewise "assign" 3 to zog's formal parameter:

void zog(int i) { printf("zog got %d\n", i); }

On the other hand, if you omit the prototype, or fail to declare
zog() at all, then -- in C89 only -- no diagnostic is required and
the behavior is undefined. (In C99 "failure to declare zog()"
requires a diagnostic; declaring it without a prototype still
produces undefined behavior.) In practice, C compilers treat
this in the same way as calls to prototyped-but-variadic functions,
so that:

extern void zog(); /* note lack of prototype */
zog(f);

widens the value in f (still 3.14) to "double" -- changing its
representation in memory and/or registers if needed, as is the case
on the x86 -- and this works right only if zog() tries to access
the provided "double". Grabbing an "int", when the compiler passed
a "double", remains undefined, but does one of two different things
in practice: it either gets sizeof(int) bytes out of the sizeof(double)
bytes passed, or it gets an apparently-random value out of some
unset memory (typically, a "wrong register" or "wrong stack" --
e.g., an x86 compiler might pass the "double" on the FPU stack
instead of the stack at %esp, so that the "int" zog() reads at
(%esp+K) for some constant K has nothing to do with the 3.14 in
the FPU stack).

Ultimately, this boils down to a few simple rules C programmers
can follow.

- Always use complete prototypes whenever possible. Calls
to prototyped functions act like ordinary assignments,
in terms of the values passed in.

- Beware of variadic functions, which effectively have
no prototypes beyond the fixed-argument portion. The
varying arguments are "widened", so that float becomes
double, and char and short become int. The exact rules
get a bit complicated (does "unsigned short" widen to
signed int or unsigned int? this turns out to be
implementation-dependent!), and C99 introduces a new
concept of "type rank" along with its horde of new
types (long long, and all the complex types).

- Compile with the "warn me if I forgot to prototype"
option, so that you get A, instead of B. :)

- Define new types with "struct". The "typedef" keyword
is misleading: it means "do not define a new type". :)
(It defines aliases for *existing* types. In "typedef
struct S { ... } alias;", it is the "struct" part that
defines the new type.)
 
J

James Harris

Chris,
You've touched upon a number of issues which strike a chord but
I'll maybe come back and raise some questions on them another
time. Thanks for the help just now.
- James


Chris Torek said:
assignment-compatible.

Does this just happen for structs?

Structures and unions (and "enum"s, to some extent). The reason
that "pointer to struct X" and "pointer to struct Y" are not
compatible is that the first occurrence of "struct X" creates a
new type -- the type named "struct X" -- and then "struct Y" creates
another new type, the one named "struct Y". Since these are two
new types, they must be different types, even if the contents are
the same.

As Kevin Easton said, the socket interface is not very well designed,
and as a result you *must* cast (or take a trip through "void *")
in at least one place. (The type-safety, or lack thereof, is just
one aspect of where the original BSD socket interface goes wrong.
If I could go back in time and fix it, I would try to convince
someone to replace the bind(), connect(), and listen() calls with
a single call. But this is different problem entirely, having
nothing to do with C per se.)
The third parameter seems to
accept any of int, long etc and perversely char, float and
double, as well as any size_X parameter. How can [function F] be
expected to make sense of a float???

The actual name of "function F" is not relevant here but its
prototype, if it has one -- and apparently it does on your system
-- is. That prototype is:

typedef unsigned int some_integral_type; /* as it happens */
int F(int, struct S *, some_integral_type);

In C, any call to a function that has a prototype in scope at
the point of the call makes use of the types in the prototype.
They cause each actual parameter in the call to be passed "as
if by ordinary assignment".

If you write:

int i;
float f = 3.14;

and then write:

i = f;

this instructs the compiler to truncate the ".14" part, leaving
just 3, and assigning 3 to i -- using whatever machine code sequence
is required to do that. (If the compiler can prove to itself that
this sets i to 3, this could be as simple as "move constant_3 =>
register holding i" or some such, rather than the 11 lines of
x86 assembly I will refrain from quoting. :) )

Since a prototyped call is "just like" assignment, a call of the
form:

extern void zog(int);
zog(f);

must likewise "assign" 3 to zog's formal parameter:

void zog(int i) { printf("zog got %d\n", i); }

On the other hand, if you omit the prototype, or fail to declare
zog() at all, then -- in C89 only -- no diagnostic is required and
the behavior is undefined. (In C99 "failure to declare zog()"
requires a diagnostic; declaring it without a prototype still
produces undefined behavior.) In practice, C compilers treat
this in the same way as calls to prototyped-but-variadic functions,
so that:

extern void zog(); /* note lack of prototype */
zog(f);

widens the value in f (still 3.14) to "double" -- changing its
representation in memory and/or registers if needed, as is the case
on the x86 -- and this works right only if zog() tries to access
the provided "double". Grabbing an "int", when the compiler passed
a "double", remains undefined, but does one of two different things
in practice: it either gets sizeof(int) bytes out of the sizeof(double)
bytes passed, or it gets an apparently-random value out of some
unset memory (typically, a "wrong register" or "wrong stack" --
e.g., an x86 compiler might pass the "double" on the FPU stack
instead of the stack at %esp, so that the "int" zog() reads at
(%esp+K) for some constant K has nothing to do with the 3.14 in
the FPU stack).

Ultimately, this boils down to a few simple rules C programmers
can follow.

- Always use complete prototypes whenever possible. Calls
to prototyped functions act like ordinary assignments,
in terms of the values passed in.

- Beware of variadic functions, which effectively have
no prototypes beyond the fixed-argument portion. The
varying arguments are "widened", so that float becomes
double, and char and short become int. The exact rules
get a bit complicated (does "unsigned short" widen to
signed int or unsigned int? this turns out to be
implementation-dependent!), and C99 introduces a new
concept of "type rank" along with its horde of new
types (long long, and all the complex types).

- Compile with the "warn me if I forgot to prototype"
option, so that you get A, instead of B. :)

- Define new types with "struct". The "typedef" keyword
is misleading: it means "do not define a new type". :)
(It defines aliases for *existing* types. In "typedef
struct S { ... } alias;", it is the "struct" part that
defines the new type.)
--
In-Real-Life: Chris Torek, Wind River Systems (BSD engineering)
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it http://67.40.109.61/torek/index.html (for the moment)
Reading email is like searching for food in the garbage,
thanks to spammers.
 

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

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top