Casting jmp_buf to void *

N

Noob

Hello everyone,

I'm using a library which provides a void *user_data field
inside the struct used everywhere within the library.

I need to stuff a jmp_buf inside user_data.

I suppose I can't write

jmp_buf env;
ctx->user_data = env;

and then use ctx->user_data as setjmp's and longjmp's parameter?

Instead, I'd have to write

jmp_buf env;
ctx->user_data = &env;

and when I need the jmp_buf, I have to write
*(jmp_buf *)ctx->user_data
or
jmp_buf *envp = ctx->user_data;
*envp

Did I get it right?
Is there different way to do this?

Regards.
 
E

Eric Sosman

Hello everyone,

I'm using a library which provides a void *user_data field
inside the struct used everywhere within the library.

I need to stuff a jmp_buf inside user_data.

I suppose I can't write

jmp_buf env;
ctx->user_data = env;

and then use ctx->user_data as setjmp's and longjmp's parameter?

Yes, you can. For historical reasons jmp_buf is an array
type, and you know The Rule about arrays: In all but a few
contexts, mentioning the name of an array is the same as writing
a pointer to the array's first element. So

ctx->user_data = env;

is the same as

ctx->user_data = &env[0];
Instead, I'd have to write

jmp_buf env;
ctx->user_data = &env;

This would also work, but there's no pressing need to
write it this way.
and when I need the jmp_buf, I have to write
*(jmp_buf *)ctx->user_data
or
jmp_buf *envp = ctx->user_data;
*envp

Since the only (useful) thing you can do with a pointer
to the first element of a jmp_buf is call longjmp() with it,
and since longjmp() is an ordinary function call, a void*
argument will automatically convert to the proper type as
part of the call.

The situation with setjmp() is less clear. Since setjmp()
is a macro rather than a function (although its expansion may
call one or more functions), it might do things with its jmp_buf
argument that an ordinary function could not do via a pointer.
As far as I can tell, calling setjmp() with a pointer to the
start of a jmp_buf is *not* guaranteed to work.
Did I get it right?

Sort of, I guess.
Is there different way to do this?

It's not clear what you mean by "this." You can certainly
call setjmp() on a jmp_buf object, pass around a void* pointer
to that object, and eventually call longjmp() on the pointer.
 
B

Ben Bacarisse

Noob said:
I'm using a library which provides a void *user_data field
inside the struct used everywhere within the library.

I need to stuff a jmp_buf inside user_data.

I suppose I can't write

jmp_buf env;
ctx->user_data = env;

and then use ctx->user_data as setjmp's and longjmp's parameter?

In fact you can do this (but see below for some problems) because a
jmp_buf is an array type.
Instead, I'd have to write

jmp_buf env;
ctx->user_data = &env;

This is pretty much the same as the above. The main trouble is going
to be the lifetime of 'env'. All will be well provided 'env' still
exists when the point to it is used, but if the function containing
'env' might return before the pointer is used, you'll have to use
malloc to allocate storage for the jmp_buf.
and when I need the jmp_buf, I have to write
*(jmp_buf *)ctx->user_data
or
jmp_buf *envp = ctx->user_data;
*envp

You need to do this no matter how you get the pointer into user_data
because once it is a void * you can't do much with it.

Because jmp_buf is an array type, you might think that you can avoid
the cast and the dereference because the result of

*(jmp_buf *)ctx->user_data

is an array lvalue and thus gets converted to a pointer anyway.
Surely (you might think) the void * expression ctx->user_data is just
as good?

The problem with that is that setjmp may be implemented as a macro, so
you can't be sure that it does not use some construct (sizeof and &
being the most obvious) that distinguishes between an array and a
pointer to its first element.

longjmp, however, is a function so I think it is safe to use

longjmp(ctx->user_data, value);
Did I get it right?

Pretty much so, yes. I wonder if my answer fairs as well :)

<snip>
 
P

Peter Nilsson

Eric Sosman said:
... The situation with setjmp() is less clear.  Since
setjmp() is a macro rather than a function (although its
expansion may call one or more functions), it might do
things with its jmp_buf argument that an ordinary function
could not do via a pointer. As far as I can tell, calling
setjmp() with a pointer to the start of a jmp_buf is *not*
guaranteed to work.

There is nevertheless an implicit prototype for the macro.
Almost any function in the standard library may be implemented
as a macro.

Is pow(2, 0.5) undefined since pow may be implemented as a
macro and 2 is not of type double? What about atan2(1, 1),
time(0), fgets(line, sizeof line, stdin), strtol(s, 0, 10),
tolower((unsigned char) ch), ...
 
K

Keith Thompson

Peter Nilsson said:
There is nevertheless an implicit prototype for the macro.
Almost any function in the standard library may be implemented
as a macro.

Is pow(2, 0.5) undefined since pow may be implemented as a
macro and 2 is not of type double? What about atan2(1, 1),
time(0), fgets(line, sizeof line, stdin), strtol(s, 0, 10),
tolower((unsigned char) ch), ...

The standard doesn't explicitly address the question of implicit
conversions of macro arguments, but C99 7.1.4 does seem to imply
(or at least assume) that it's guaranteed to work correctly.

A macro implementation of pow() could just cast its arguments
to double.

There is one potential problem: a macro invocation might not contain
the same sequence points that a function call does. So, for example,
the behavior of ``sin(x) + cos(x)'' might be undefined because both
calls potentially update errno.
 
P

Phil Carmody

Peter Nilsson said:
There is nevertheless an implicit prototype for the macro.

What do you mean by "prototype"?
Almost any function in the standard library may be implemented
as a macro.

Without serially repeating, and thus evaluating, their arguments
more than once, I'd be surprised if more than a handful could be
so implemented. And the ones that do evaluate any argument more
than once cannot be valid implementations.
Is pow(2, 0.5) undefined since pow may be implemented as a
macro and 2 is not of type double?

In what context is the value 2 not convertable to a required double?
If pow() starts taking addresses, then you're screwed, but that
would be a strawman soaked in parafin.

Phil
 
M

Michael Foukarakis

Hello everyone,

I'm using a library which provides a void *user_data field
inside the struct used everywhere within the library.

I need to stuff a jmp_buf inside user_data.

I suppose I can't write

   jmp_buf env;
   ctx->user_data = env;

and then use ctx->user_data as setjmp's and longjmp's parameter?

As long as you make sure the pointer won't dangle, you can do this.
 
N

Noob

Eric said:
Yes, you can. For historical reasons jmp_buf is an array type,

I was not aware of that. Thank you for pointing it out to me.

<quote C89 draft>

The type declared is

jmp_buf

which is an array type suitable for holding the information needed to
restore a calling environment.

and you know The Rule about arrays: In all but a few
contexts, mentioning the name of an array is the same as writing
a pointer to the array's first element. So

ctx->user_data = env;

is the same as

ctx->user_data = &env[0];

On a moderately related note, I've been wondering...

Given int foo[42];

&foo[0] and &foo will not be compatible pointers, right?

Yet, would the two pointers have the same value?
i.e. (uintptr_t)&foo[0] ==(uintptr_t)&foo ???
This would also work, but there's no pressing need to
write it this way.
Cool.

Since the only (useful) thing you can do with a pointer
to the first element of a jmp_buf is call longjmp() with it,
and since longjmp() is an ordinary function call, a void*
argument will automatically convert to the proper type as
part of the call.

The situation with setjmp() is less clear. Since setjmp()
is a macro rather than a function (although its expansion may
call one or more functions), it might do things with its jmp_buf
argument that an ordinary function could not do via a pointer.
As far as I can tell, calling setjmp() with a pointer to the
start of a jmp_buf is *not* guaranteed to work.


Sort of, I guess.


It's not clear what you mean by "this." You can certainly
call setjmp() on a jmp_buf object, pass around a void* pointer
to that object, and eventually call longjmp() on the pointer.

What you describe is precisely what I did, based on your advice.

static void myexit(j_common_ptr cinfo)
{
longjmp(cinfo->client_data, 666);
}

static int decode_image(char *file, int *w, int *h)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
jmp_buf jmpbuf;
int err = 0;
cinfo.err = jpeg_std_error(&jerr);
cinfo.client_data = jmpbuf;
jerr.error_exit = myexit;
if (setjmp(jmpbuf) != 0)
{
err = FAIL;
goto unwind;
}
jpeg_create_decompress(&cinfo);
...
unwind:
jpeg_destroy_decompress(&cinfo);
vfs_fclose(infile);
return err;
}

(jerr.error_exit is called from within the library whenever a fatal
condition occurs, and the current operation cannot proceed.)

Regards.
 
N

Noob

Ben Bacarisse wrote: [snip]

Thanks to you, and to Eric, for your detailed and insightful answers.
 
E

Eric Sosman

I don't think so.
To me, implementing a function as a macro,
means that the macro must do what the function is supposed to do.

The problem here is the opposite one: setjmp() *is* a
macro, not a function that may also be masked by a macro.
On some platforms the setjmp() macro may simply expand to
a call on a setjmp() function, and the Standard reserves
that name for external linkage, but that's not required.
The possibility remains open that the setjmp() macro might
expand to some code that works only with an actual jmp_buf
array and not with a pointer thereto. Something like

#define setjmp(buf) ( \
(buf)[0]._secret_ = sizeof(buf) , \
(buf)[0]._arcane_ = &(buf) , \
setjmp(buf) \
)

would behave differently with a pointer than with an actual
jmp_buf.

The Rationale discusses some of the reasons why "setjmp()
should be usable as an ordinary function" is deliberately
not required by the Standard.
 
P

Peter Nilsson

Cross posted to comp.std.c.

pete said:
I don't think so.
To me, implementing a function as a macro,
means that the macro must do what the function is
supposed to do.

Indeed, setjmp is a pure macro and needn't be a function,
but there is a (notional) explicit prototype, unlike assert
whose argument is (now) simply <scalar type>.

However, thinking about it, I can't find any chapter and
verse that says a macro implementation of a standard
function must convert its arguments to the appropriate
type. The only real guarantees are...

"... Any invocation of a library function that is
implemented as a macro shall expand to code that
evaluates each of its arguments exactly once, fully
protected by parentheses where necessary, so it is
generally safe to use arbitrary expressions as
arguments. Likewise, those function-like macros
described in the following subclauses may be invoked
in an expression anywhere a function with a
compatible return type could be called. ..."

'Protected by parentheses' does not imply an appropriate
conversion where desirable.

So, not uncommon constructs remain potential UB [e.g
atan2(1,1), time(0), fgets(line, sizeof line, stdin),
strtol(s, 0, 10), tolower((unsigned char) ch), ...]

I think that's a bug in the standard.
 
N

Nobody

This is pretty much the same as the above. The main trouble is going
to be the lifetime of 'env'. All will be well provided 'env' still
exists when the point to it is used, but if the function containing
'env' might return before the pointer is used, you'll have to use
malloc to allocate storage for the jmp_buf.

Using malloc() isn't going to help. Once you leave the function in which
setjmp() was called, trying to longjmp() back to that point isn't likely
to work. So you may as well use an automatic variable.
 
B

Ben Bacarisse

Nobody said:
Using malloc() isn't going to help. Once you leave the function in which
setjmp() was called, trying to longjmp() back to that point isn't likely
to work. So you may as well use an automatic variable.

Good point. I was blindly giving general advice without thinking
about the particulars of the case.
 
T

Tim Rentsch

Noob said:
Hello everyone,

I'm using a library which provides a void *user_data field
inside the struct used everywhere within the library.

I need to stuff a jmp_buf inside user_data.

I suppose I can't write

jmp_buf env;
ctx->user_data = env;

and then use ctx->user_data as setjmp's and longjmp's parameter?

Right, doing that is needlessly dangerous.

Instead, I'd have to write

jmp_buf env;
ctx->user_data = &env;

and when I need the jmp_buf, I have to write
*(jmp_buf *)ctx->user_data
or
jmp_buf *envp = ctx->user_data;
*envp

Did I get it right?

Yes, this should work fine, and is better in terms of coding
style too. Notice that using the '&' as you have here,
and casting to a pointer type upon converting back, means this
technique will work whether or not jmp_buf is an array type.

It happens that the Standard requires that jmp_buf be an array
type, but it's better to write code that doesn't depend on that
requirement if that's not too hard (and here it isn't).

Is there different way to do this?

The way you explain in the second part is perfectly fine.
 
E

Eric Sosman

[...] Notice that using the '&' as you have here,
and casting to a pointer type upon converting back, means this
technique will work whether or not jmp_buf is an array type.

7.13p2: "The type declared is jmp_buf which is an array
type [...]"
 
T

Tim Rentsch

Eric Sosman said:
Hello everyone,

I'm using a library which provides a void *user_data field
inside the struct used everywhere within the library.

I need to stuff a jmp_buf inside user_data.

I suppose I can't write

jmp_buf env;
ctx->user_data = env;

and then use ctx->user_data as setjmp's and longjmp's parameter?

Yes, you can. For historical reasons jmp_buf is an array
type, and you know The Rule about arrays: In all but a few
contexts, mentioning the name of an array is the same as writing
a pointer to the array's first element. So

ctx->user_data = env;

is the same as

ctx->user_data = &env[0];
Instead, I'd have to write

jmp_buf env;
ctx->user_data = &env;

This would also work, but there's no pressing need to
write it this way.

I'm surprised to see this comment from you. Using the '&' is
clearly better style -- the reader doesn't have to know that
'jmp_buf' is an array type, and this code works whether
it's an array type or not.

Since the only (useful) thing you can do with a pointer
to the first element of a jmp_buf is call longjmp() with it,
and since longjmp() is an ordinary function call, a void*
argument will automatically convert to the proper type as
part of the call.

The situation with setjmp() is less clear. Since setjmp()
is a macro rather than a function (although its expansion may
call one or more functions), it might do things with its jmp_buf
argument that an ordinary function could not do via a pointer.
As far as I can tell, calling setjmp() with a pointer to the
start of a jmp_buf is *not* guaranteed to work.

I read the Standard as saying setjmp() has to work with any
expression (without side effects) that has type 'jmp_buf',
but there are no guarantees if called with an expression
that has type (void*) or the type of '&env[0]'. Is that
the same as your reading, or different?

Of course calling longjmp() treats its argument as any other
function call would, but there is no reason not to use
exactly the same expression in the longjmp() call as was
used in the setjmp() call.

Sort of, I guess.


It's not clear what you mean by "this." You can certainly
call setjmp() on a jmp_buf object, pass around a void* pointer
to that object, and eventually call longjmp() on the pointer.

Certainly the Standard means to allow that setjmp() can be
called with any expression (again, without side-effects) of
type 'jmp_buf', don't you think?
 
T

Tim Rentsch

Eric Sosman said:
I don't think so.
To me, implementing a function as a macro,
means that the macro must do what the function is supposed to do.

The problem here is the opposite one: setjmp() *is* a
macro, not a function that may also be masked by a macro.
On some platforms the setjmp() macro may simply expand to
a call on a setjmp() function, and the Standard reserves
that name for external linkage, but that's not required.
The possibility remains open that the setjmp() macro might
expand to some code that works only with an actual jmp_buf
array and not with a pointer thereto. Something like

#define setjmp(buf) ( \
(buf)[0]._secret_ = sizeof(buf) , \
(buf)[0]._arcane_ = &(buf) , \
setjmp(buf) \
)

would behave differently with a pointer than with an actual
jmp_buf.

Using these declarations

jmp_buf env;
jmp_buf several[10];
jmp_buf *x = &several[2];

the above macro would work just fine with any of

env
several[3]
*(several + 7)
*x

as arguments, would it not?

The Rationale discusses some of the reasons why "setjmp()
should be usable as an ordinary function" is deliberately
not required by the Standard.

It does, but that discussion seems largely orthogonal to the
question of what kinds of expressions are acceptable as its
argument.
 
T

Tim Rentsch

Eric Sosman said:
[...] Notice that using the '&' as you have here,
and casting to a pointer type upon converting back, means this
technique will work whether or not jmp_buf is an array type.

7.13p2: "The type declared is jmp_buf which is an array
type [...]"

Yes I know that. My point is that it's better as a general rule
not to write code that depends on that if we don't have to, and
we don't have to. And it's easier on the reader -- not everyone
knows the C Standard to the level of detail that clc'ers
generally do. And, what I think is most significant, the later
use of said pointer value being converted to type 'jmp_buf *',
using '&env' rather than just plain 'env' will cause less head
scratching in general -- even people who _know_ that jmp_buf is
an array type might take a moment or two to remember that. There
are plenty of things to think about when reading code more
important than this; when I write code I don't want readers of
that code to spend even one second thinking about stuff they
don't have to.
 

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,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top