C function pointers and UB

N

nroberts

I'm from C++ and having to write some C. Could someone tell me if the
following would illicit UB? I have a feeling that it will work OK but
I hate generating UB without knowing it.

#include <stdlib.h>

typedef struct object {} Object;

Object * createObject(void) { return
(Object*)malloc(sizeof(Object)); }
void freeObject(Object* obj) { free(obj); }

typedef void (*deleter)(void*);

int main()
{
Object * obj = createObject();
delete del = (deleter)&freeObject;

del(obj);

return 0;
}
 
H

Harald van Dijk

I'm from C++ and having to write some C.  Could someone tell me if the
following would illicit UB?  I have a feeling that it will work OK but
I hate generating UB without knowing it.
[...]
void freeObject(Object* obj) { free(obj); }
[...]
typedef void (*deleter)(void*);
[...]
  delete del = (deleter)&freeObject;

Typo: deleter del

No, that isn't valid. It cannot work on the (admittedly rare but
valid) systems where Object* and void* are represented differently
(say, sizeof(void*) > sizeof(Object*)), it cannot work on the (equally
rare but valid) systems where parameters of type Object* are passed to
functions differently from void* (say, passed in a different
register), and so it cannot be allowed in standard C. And because it
is not allowed in standard C, even on systems where it could work,
compilers may and do perform optimisations that are only valid if you
do not cast function pointers to a different type.
 
J

James Kuyper

I'm from C++ and having to write some C. Could someone tell me if the
following would illicit UB? I have a feeling that it will work OK but

Your feeling is wrong; your worries are well justified. It won't compile
without a diagnostic message. If it does compile, it's behavior is not
defined by the C standard, for two different reasons.
I hate generating UB without knowing it.

#include <stdlib.h>

typedef struct object {} Object;

This is a syntax error. The struct-declaration-list in the declaration
of a struct type is not allowed to be empty (and there is absolutely
nothing useful you could do with such at type even if it weren't a
syntax error).

An implementation of C is required to issue at least one diagnostic
message when it processes a translation unit containing a syntax error.
It is not required to accept such a program. If it chooses to accept it
anyway, the behavior of the resulting program is undefined, by reason of
the lack of a definition in the standard.

You could change it to
typedef struct object Object;

But then struct object would be an incomplete type, and the sizeof()
expression down below would be a constraint violation, so that change
wouldn't help you any.
Object * createObject(void) { return
(Object*)malloc(sizeof(Object)); }
void freeObject(Object* obj) { free(obj); }
typedef void (*deleter)(void*);

int main()
{
Object * obj = createObject();

I hope that, in real code, you would check whether or not 'obj' is null?
There's no problem in this case, because the only thing you try to do
with obj if free() it. However, if it were in fact null, just about
anything else you might want to do with it would have undefined behavior.
delete del = (deleter)&freeObject;

You've provide no definition for the identifier 'delete' in this code. I
assume it's a typo for 'deleter'?

The '&' is unnecessary. A name of a function is always implicitly
converted to a pointer to the function. There's a special rule that '&'
applied to a function pointer value always returns an equivalent pointer
of the same type. Therefore, freeObject, &freeObject, and &&freeObject
are all equivalent pointer values.

C allows you to explicitly convert a pointer to a function to a pointer
to any other function type. However, the converted pointer cannot be
used to call that function unless the target function type is compatible
with the source function type. Object* is not compatible with void*, so
the function types are not compatible either. Therefore, the following
statement:
del(obj);

has undefined behavior. In practice, this might actually work - on many
systems Object* and void* might have identical representations. However,
they're not required to do so.

This rule is not specific to C; C++ has a similar rule - you should not
be doing something like this in either language. In C, this is
completely unnecessary, because unlike C++, it allows implicit
conversion of Object* to void*. Note: the fact that type A is implicitly
convertible to type B does NOT mean that type A is compatible with type B.
 
N

nroberts

I'm from C++ and having to write some C.  Could someone tell me ifthe
following would illicit UB?  I have a feeling that it will work OKbut
I hate generating UB without knowing it.
[...]
void freeObject(Object* obj) { free(obj); }
[...]
typedef void (*deleter)(void*);
[...]
  delete del = (deleter)&freeObject;

Typo: deleter del

No, that isn't valid. It cannot work on the (admittedly rare but
valid) systems where Object* and void* are represented differently
(say, sizeof(void*) > sizeof(Object*)), it cannot work on the (equally
rare but valid) systems where parameters of type Object* are passed to
functions differently from void* (say, passed in a different
register), and so it cannot be allowed in standard C. And because it
is not allowed in standard C, even on systems where it could work,
compilers may and do perform optimisations that are only valid if you
do not cast function pointers to a different type.

Thanks. It's UB in C++ too.

And thanks for not replying to email. Part of being stuck with google
groups is not being able to post with a bounceback and usenet newbs
inundate my email with their replies.
 
K

Keith Thompson

James Kuyper said:
On 08/23/2011 12:48 PM, nroberts wrote: [...]
typedef struct object {} Object;

This is a syntax error. The struct-declaration-list in the declaration
of a struct type is not allowed to be empty
True.

(and there is absolutely
nothing useful you could do with such at type even if it weren't a
syntax error).

Not necessarily true. There are languages that support the equivalent
of empty structs, and they can be useful in some circumstances.

[...]
You've provide no definition for the identifier 'delete' in this code. I
assume it's a typo for 'deleter'?

The '&' is unnecessary. A name of a function is always implicitly
converted to a pointer to the function. There's a special rule that '&'
applied to a function pointer value always returns an equivalent pointer
of the same type. Therefore, freeObject, &freeObject, and &&freeObject
are all equivalent pointer values.

freeObject and &freeObject are equivalent. &&freeObject, due to
the maximal munch rule, is an incorrect use of the "&&" operator.
Fixing that, &(&freeObject) is a constraint violation because
(&freeObject) is not an lvalue. Perhaps you were thinking of
*freeObject and **freeObject (the latter works because there's no
"**" operator).

[...]
 
J

James Kuyper

James Kuyper said:
On 08/23/2011 12:48 PM, nroberts wrote: [...]
typedef struct object {} Object;

This is a syntax error. The struct-declaration-list in the declaration
of a struct type is not allowed to be empty
True.

(and there is absolutely
nothing useful you could do with such at type even if it weren't a
syntax error).

Not necessarily true. There are languages that support the equivalent
of empty structs, and they can be useful in some circumstances.

Can you provide an example? I know languages where everything is derived
from a single "Object" base class, and which allow creation of an empty
object to which attributes can be later attached. But that involves a
more complicated object model than C has, so I wouldn't consider an
empty Object to be comparable to an empty struct.
freeObject and &freeObject are equivalent. &&freeObject, due to
the maximal munch rule, is an incorrect use of the "&&" operator.
Fixing that, &(&freeObject) is a constraint violation because
(&freeObject) is not an lvalue. Perhaps you were thinking of
*freeObject and **freeObject (the latter works because there's no
"**" operator).

You're right. My mistake.
 
K

Keith Thompson

James Kuyper said:
James Kuyper said:
On 08/23/2011 12:48 PM, nroberts wrote: [...]
typedef struct object {} Object;

This is a syntax error. The struct-declaration-list in the declaration
of a struct type is not allowed to be empty
True.

(and there is absolutely
nothing useful you could do with such at type even if it weren't a
syntax error).

Not necessarily true. There are languages that support the equivalent
of empty structs, and they can be useful in some circumstances.

Can you provide an example? I know languages where everything is derived
from a single "Object" base class, and which allow creation of an empty
object to which attributes can be later attached. But that involves a
more complicated object model than C has, so I wouldn't consider an
empty Object to be comparable to an empty struct.

I was afraid you might ask that. :cool:}

I suppose it gives you a way of having multiple unique objects (with
unique addresses *if* the language guarantees that) minimal overhead.

Ada even has a specific syntax for this:
type Empty is null record;
but that's mostly used when it's going to be expanded later.

[...]
 
I

Ian Collins

James Kuyper said:
On 08/23/2011 12:48 PM, nroberts wrote: [...]
typedef struct object {} Object;

This is a syntax error. The struct-declaration-list in the declaration
of a struct type is not allowed to be empty
True.

(and there is absolutely
nothing useful you could do with such at type even if it weren't a
syntax error).

Not necessarily true. There are languages that support the equivalent
of empty structs, and they can be useful in some circumstances.

Can you provide an example? I know languages where everything is derived
from a single "Object" base class, and which allow creation of an empty
object to which attributes can be later attached. But that involves a
more complicated object model than C has, so I wouldn't consider an
empty Object to be comparable to an empty struct.

C++.

An empty struct can be used to create a unique type with templates.
Another, more common, use is to hold typedefs which are used as traits
in class templates. For example given a class that manipulates
character data, the character type (say char or wchar_t) can be specified:

template <typename Traits>
struct DoSomething
{
typename Traits::char_type c;

// do stuff with c.
};

struct Normal { typedef char char_type; };
struct Wide { typedef wchar_t char_type; };

int main ()
{
DoSomething<Normal> d;
return 0;
}
 
J

James Kuyper

On 08/23/2011 12:48 PM, nroberts wrote:
[...]
typedef struct object {} Object;

This is a syntax error. The struct-declaration-list in the declaration
of a struct type is not allowed to be empty

True.

(and there is absolutely
nothing useful you could do with such at type even if it weren't a
syntax error).

Not necessarily true. There are languages that support the equivalent
of empty structs, and they can be useful in some circumstances.

Can you provide an example? I know languages where everything is derived
from a single "Object" base class, and which allow creation of an empty
object to which attributes can be later attached. But that involves a
more complicated object model than C has, so I wouldn't consider an
empty Object to be comparable to an empty struct.

C++.

An empty struct can be used to create a unique type with templates.
Another, more common, use is to hold typedefs which are used as traits
in class templates.

I actually knew about those uses, but forgot about them. Since C lacks
templates and overloading, such uses don't apply in C. However, they
might be useful in connection with the new _Generic() feature proposed
for the next version of the C standard.
 
N

Niklas Holsti

Keith said:
James Kuyper said:
On 08/23/2011 12:48 PM, nroberts wrote:
[...]
typedef struct object {} Object;
This is a syntax error. The struct-declaration-list in the declaration
of a struct type is not allowed to be empty
True.

(and there is absolutely
nothing useful you could do with such at type even if it weren't a
syntax error).
Not necessarily true. There are languages that support the equivalent
of empty structs, and they can be useful in some circumstances.
Can you provide an example? I know languages where everything is derived
from a single "Object" base class, and which allow creation of an empty
object to which attributes can be later attached. But that involves a
more complicated object model than C has, so I wouldn't consider an
empty Object to be comparable to an empty struct.

I was afraid you might ask that. :cool:}

I suppose it gives you a way of having multiple unique objects (with
unique addresses *if* the language guarantees that) minimal overhead.

Ada even has a specific syntax for this:
type Empty is null record;
but that's mostly used when it's going to be expanded later.

In some Ada designs, a general, reusable framework makes use of various
application-specific types, defined in application modules. Values of
these types are created and processed in application-specific code but
stored and passed along by the framework code. In a simple application,
some of these types may be logically unnecessary, in which case the null
record type is appropriate: it satisfies the framework's compile-time
need for a type, while avoiding the existence of any dummy, unused
actual data at run time.

My applications have several cases of such null record types.

I'm not sure if different objects of a null record type are required to
have different addresses in Ada. I have not needed that.
 
K

Keith Thompson

Niklas Holsti said:
I'm not sure if different objects of a null record type are required to
have different addresses in Ada. I have not needed that.

<OT>
I don't think that's required. Experimentally, two declared null record
objects have distinct addresses, but array elements have the same
address. (Note that Ada doesn't define array indexing in terms of
addresses.)
</OT>
 
B

Ben Bacarisse

James Kuyper said:
[...] In C, this is
completely unnecessary, because unlike C++, it allows implicit
conversion of Object* to void*.

C++ allows that implicit conversion too -- it's the reverse that is not
done implicitly.

<snip>
 
J

James Kuyper

James Kuyper said:
[...] In C, this is
completely unnecessary, because unlike C++, it allows implicit
conversion of Object* to void*.

C++ allows that implicit conversion too -- it's the reverse that is not
done implicitly.

You're right - which makes this whole "deleter" typedef even more puzzling.
 
E

Eric Sosman

I'm from C++ and having to write some C. Could someone tell me if the
following would illicit UB? [...]

Others have commented on the specifics of your question, and
even on some of its far-fetched generalities, but no one (as far
as I've seen) has yet mentioned the other issue: "Illicit" doesn't
mean what you think it means. It's illegal. Try "elicit."
 
N

Nobody

I'm from C++ and having to write some C. Could someone tell me if the
following would illicit UB? I have a feeling that it will work OK but
I hate generating UB without knowing it.

It would work on "common" platforms, but it's not guaranteed by the
standard.

For compatibility, you would need to either change the type of freeObject
to take a void*:

void freeObject(void* obj) { free(obj); }

or wrap it with a function that does:

void objectDeleter(Object* obj) { freeObject(obj); }

BTW: providing a dedicated "free" function is a wise move even if it
simply calls free(), as it ensures that the *correct* free() is used.

This is a very real issue on Windows, as a process can end up using
multiple distinct versions of MSVCRT, e.g. due to the program linking
against one version and also against a DLL which is linked against a
different version.

Anything allocated by one version's malloc() must be freed using the same
version's free(). To ensure that this happens, a library which returns
pointers to dynamically allocated memory must provide its own deallocation
routine(s). If the main program simply calls free(), it may well end up
calling the wrong free().
 

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,764
Messages
2,569,564
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top