function casts

B

BartC

Suppose I have a pointer to pointer to function like this:

void (*(*fnptr)) (void);

How do I apply this function signature as a cast to another type, say the
variable p here:

int *p;

and call the result? (So I want to pretend p is the same type as fnptr, and
I want to call it. I've already figured out that calling fnptr is done as
(*(*fnptr))().)

I'm getting lost in silly parentheses at the moment; my last attempt was:

((*(*()))())p();
 
A

Alan Curry

Suppose I have a pointer to pointer to function like this:

void (*(*fnptr)) (void);

The inner parentheses aren't doing anything, unless they are making the mess
more readable to you.
How do I apply this function signature as a cast to another type, say the
variable p here:

int *p;

and call the result? (So I want to pretend p is the same type as fnptr, and
I want to call it. I've already figured out that calling fnptr is done as
(*(*fnptr))().)

I'm getting lost in silly parentheses at the moment; my last attempt was:

((*(*()))())p();

Where did the voids go? You might be able to get away with leaving out the
function parameters, but when casting to something involving a function
pointer, the function return type has to be in there somewhere.

The cast itself is (void(**)(void))p

(Rule: take your declaration of fnptr, remove the word "fnptr" from it, and
stick it in an outer set of parentheses which are the cast syntax)

After that, dereference with * once to get the function pointer from the
pointer-to-function-pointer, and then you can call it, by parenthesizing the
whole thing for precedence purposes and adding the () containing the
arguments (in this case none).

Full example program:

#include <stdio.h>

static void f1(void)
{
printf("Hello, ");
}

static void f2(void)
{
printf("world!\n");
}

int main(void)
{
void (*fnp) (void); /* pointer to function */
void (**fnptr) (void); /* pointer to pointer to function */
int *p;

fnp = f1;
fnptr = &fnp;
p = (int *)fnptr;
(*(void(**)(void))p)();

fnp = f2;
/* fnptr = &fnp */ /* unchanged from before */
/* p = (int *)fnptr; */ /* unchanged from before */
(*(void(**)(void))p)();

return 0;
}
 
B

BartC

Even if you get the parens in all the right places, well, if it's hard to
write, it'll be hard to read.

That's not important; I'm using a code generator, and the result will only
be seen by a C compiler (unless it doesn't work).
In cases like this, typedef is your friend:
typedef void (*f_ptr) (void);

(*(f_ptr*)p)();

Thanks. I managed to incorporate that typedef into the cast, and it now
works!

(*(void(**)(void))p)();

(I should have noticed there were a couple of voids missing.) Now I just
have to persuade my code generator to do it automatically..
 
B

BartC

Where did the voids go? You might be able to get away with leaving out the
function parameters, but when casting to something involving a function
pointer, the function return type has to be in there somewhere.

I've no idea how I ended up with the above. It's possible I was just trying
things at random!
The cast itself is (void(**)(void))p

OK, thanks. I've now got that (and stored it somewhere safe too just in
case).
(Rule: take your declaration of fnptr, remove the word "fnptr" from it,
and
stick it in an outer set of parentheses which are the cast syntax)

Will do..
 
I

Ian Collins

That's not important; I'm using a code generator, and the result will only
be seen by a C compiler (unless it doesn't work).

I write a lot of code generators and I always try an generate code that
is close to indistinguishable from my had written code. I find it makes
me think more about what I write. Doing so would certainly have helped
you in this case!
 
B

BartC

Ian Collins said:
On 10/13/12 11:19, BartC wrote:

I write a lot of code generators and I always try an generate code that is
close to indistinguishable from my had written code. I find it makes me
think more about what I write. Doing so would certainly have helped you
in this case!

My case is probably a little different, as I'm starting from a somewhat
different language, and translating to C source code (this is for a compiler
targeting C).

It's been a struggle, as the two languages don't match exactly, neither is C
anywhere near the 'portable assembler' that everyone says it is. Nearly
every single aspect of C 'gets in the way'; the type declaration syntax is a
minor issue!

I can't make use of half the language (most of the control statements are
out for example), and the output is terrible-looking, completely
unstructured C. (Fortunately gcc seems to tidy up the result very well and
the eventual code is faster than if I'd generated ASM directly, but ASM is
anyway not practical at the minute.)
 
L

Les Cargill

BartC said:
My case is probably a little different, as I'm starting from a somewhat
different language, and translating to C source code (this is for a
compiler targeting C).

It's been a struggle, as the two languages don't match exactly, neither
is C anywhere near the 'portable assembler' that everyone says it is.

Dunno - 'C' makes a pretty good target language, but you have to Think
Stupid.
Nearly every single aspect of C 'gets in the way'; the type declaration
syntax is a minor issue!

I can't make use of half the language (most of the control statements
are out for example), and the output is terrible-looking, completely
unstructured C. (Fortunately gcc seems to tidy up the result very well
and the eventual code is faster than if I'd generated ASM directly, but
ASM is anyway not practical at the minute.)


The best way to generate 'C' is to
generate tables that hand-coded 'C' then operates on.
Tables can include callbacks...
 
B

BartC

Les Cargill said:
BartC wrote:

Dunno - 'C' makes a pretty good target language, but you have to Think
Stupid.

I can't; C insists on very strict type-checking, so I have to double guess
what C expects the type of any expression to be, and compare that with the
original types, and any coercions, of the source language.

Then you may have to apply extra coercions which work differently on
l-values, &-terms and *-terms. And there's the quirk where A usually means
the value of A, *unless* it's an array then it means &A[0] (in the source
language, arrays have values (as well as having arbitrary lower bounds for
good measure!)).

C is fussy about type-matching pointers (so T* and U* don't match); the
source language doesn't care.

C also does pointer arithmetic using object-sized offsets, while the source
language uses byte offsets! Generating dumb ASM code is actually far easier.
(What's much harder is generating efficient, optimised ASM.)

(There are many other issues: for example C is case-sensitive, the source
language isn't; trying to import a C function such as fopen() into the
source language, without also importing the FILE* type; words that are
identifiers in one language but reserved words in the other, etc etc.)

The best way to generate 'C' is to
generate tables that hand-coded 'C' then operates on.
Tables can include callbacks...

My starting point is intermediate code, a '3-address-code' kind of
representation. At this point everything is already linearised and
unstructured, as the usual target is assembly code. You then translate each
instruction into a simple C statement, typically an assignment of the form X
= Y op Z;

It sounds easy; it isn't!
 
B

Ben Bacarisse

BartC said:
Les Cargill said:
BartC wrote:

Dunno - 'C' makes a pretty good target language, but you have to Think
Stupid.

I can't; C insists on very strict type-checking, so I have to double guess
what C expects the type of any expression to be, and compare that with the
original types, and any coercions, of the source language.

Then you may have to apply extra coercions which work differently on
l-values, &-terms and *-terms. And there's the quirk where A usually means
the value of A, *unless* it's an array then it means &A[0] (in the source
language, arrays have values (as well as having arbitrary lower bounds for
good measure!)).

C is fussy about type-matching pointers (so T* and U* don't match); the
source language doesn't care.

C also does pointer arithmetic using object-sized offsets, while the source
language uses byte offsets! Generating dumb ASM code is actually far easier.
(What's much harder is generating efficient, optimised ASM.)

This point and the last one suggest that you are generating code at what
I'd be tempted to call the wrong level. I.e. rather than a compiler it
sounds like a translator. If your source language pointers are type
agnostic and use bytes offsets, I'd expect them all to appear as
unsigned char pointer in the C output. C's rules for pointer
compatibility and arithmetic won't then come into it.
(There are many other issues: for example C is case-sensitive, the source
language isn't; trying to import a C function such as fopen() into the
source language, without also importing the FILE* type; words that are
identifiers in one language but reserved words in the other, etc etc.)

Surely all your source-language names are kept in a separate space? You
can then use an "escape" convention to capture case: source name func
maps to srcfunc__ and Func to, say, src_func__. I can't see how a
reserved word can be a problem unless you are trying to some sort of
minimal source-level translation rather than what would be more normally
termed compilation.

If, on the other hand, you've decided that C functions should be
callable with no wrappers at all, then I think you've just made a rod
for your own back.

It sounds easy; it isn't!

Yes, I'm not saying that it is, but I think you may have designed things
to be harder than they need to be.
 
8

88888 Dihedral

Bartæ–¼ 2012å¹´10月13日星期六UTC+8上åˆ5時43分22秒寫é“:
Suppose I have a pointer to pointer to function like this:



void (*(*fnptr)) (void);



How do I apply this function signature as a cast to another type, say the

variable p here:



int *p;



and call the result? (So I want to pretend p is the same type as fnptr, and

I want to call it. I've already figured out that calling fnptr is done as

(*(*fnptr))().)



I'm getting lost in silly parentheses at the moment; my last attempt was:



((*(*()))())p();

I think the use of functon pointers are well documented in C90.

I use initializers for setting up funtors that could return handles
to emulate the functional programming in LISP 10 years ago.
 
B

BartC

Ben Bacarisse said:
This point and the last one suggest that you are generating code at what
I'd be tempted to call the wrong level. I.e. rather than a compiler it
sounds like a translator.

It originally targeted ASM, so I guess it's a compiler. Maybe I could
generate C from the AST instead of intermediate code, and it might be
possible to create more structured C that way than the 'flat' C I'm creating
now. But I don't think that would solve too many of my current problems, and
would probably create new ones!
If your source language pointers are type
agnostic and use bytes offsets, I'd expect them all to appear as
unsigned char pointer in the C output. C's rules for pointer
compatibility and arithmetic won't then come into it.

My pointers are typed, but I just use byte offsets which are more flexible.
As an example of the type issues I have to deal with in C, take this program
in my source language (to do with the function casts I was asking about).
Not C-like, but hopefully clear:

ref int pcptr

proc disploop=
ref ref proc() fnptr @ pcptr

do
fnptr^^()
od
end

This is the C code generated (in the above code, fnptr and pcptr are the
same memory location, that needs to be emulated in C):

static void disploop(void);
static int (*pcptr);

void disploop(void) {
/* void (*(*fnptr)) (void) @pcptr */

L2:
(*((void (*(*)) (void))pcptr))();
goto L2;
}

And this is the x86 code that I might generate instead, just the function
body:

disploop:
L3:
mov esi,[pcptr]
mov eax,[esi]
call eax
jmp L3
retn

Notice this doesn't involve asterisks, parentheses or voids that have to be
in exactly the right pattern! Although the generated C could have been
tidied up a bit, I don't see how this stuff can be avoided, no matter how
the C is generated.
Surely all your source-language names are kept in a separate space? You
can then use an "escape" convention to capture case: source name func
maps to srcfunc__ and Func to, say, src_func__. I can't see how a
reserved word can be a problem unless you are trying to some sort of
minimal source-level translation rather than what would be more normally
termed compilation.

If, on the other hand, you've decided that C functions should be
callable with no wrappers at all, then I think you've just made a rod
for your own back.

Maybe. Some problems can solved by writing wrapper functions *in C*. But I'm
trying to avoid that (you can't tell someone using the language and trying
to call an imported C function, to switch to another language!). As a silly
example, take this program in my source language:

stop

which is translated to a call to a runtime function, also in the source
language, which happens to call the imported C function exit(). However
'exit' is a reserved word in my language! So I call a wrapper function in C,
which calls exit() for me.
 
L

Les Cargill

BartC said:
Les Cargill said:
BartC wrote:

Dunno - 'C' makes a pretty good target language, but you have to Think
Stupid.

I can't; C insists on very strict type-checking, so I have to double guess
what C expects the type of any expression to be, and compare that with the
original types, and any coercions, of the source language.

Then you may have to apply extra coercions which work differently on
l-values, &-terms and *-terms. And there's the quirk where A usually means
the value of A, *unless* it's an array then it means &A[0] (in the source
language, arrays have values (as well as having arbitrary lower bounds for
good measure!)).

So that's not *NEARLY* stupid enough :) By "Think Stupid", I mean
vastly reduce the number of degrees of freedom for the generated code.
C is fussy about type-matching pointers (so T* and U* don't match); the
source language doesn't care.

Generally, I store (void *) pointers in a table, then have a column in
the table that suggests a type.
C also does pointer arithmetic using object-sized offsets, while the source
language uses byte offsets! Generating dumb ASM code is actually far
easier.
(What's much harder is generating efficient, optimised ASM.)

Could be, then. I mean no disrespect, but it sounds like you're
doing it the Hard Way, which is ... hard.
(There are many other issues: for example C is case-sensitive, the source
language isn't; trying to import a C function such as fopen() into the
source language, without also importing the FILE* type; words that are
identifiers in one language but reserved words in the other, etc etc.)




My starting point is intermediate code, a '3-address-code' kind of
representation. At this point everything is already linearised and
unstructured, as the usual target is assembly code. You then translate each
instruction into a simple C statement, typically an assignment of the
form X
= Y op Z;

It sounds easy; it isn't!

*Shrug?*

struct Thing[] = {
{ (void *)&X, (void *)&Y, "op", (void *)&Z },
....
};

void eval(Thing *t)
{
....
}

....

eval(&Thing[0]);

....
 
B

BartC

Les Cargill said:
BartC wrote:
Could be, then. I mean no disrespect, but it sounds like you're
doing it the Hard Way, which is ... hard.

What, always using byte offsets for pointers? It's very useful. What's hard
about it is that C works differently.

(Perhaps I should explain I'm using dynamic language A for this project,
which compiles source code of a static language B, into C source code. I
hardly write any actual C at all; mainly when I find myself editing the C
output files by mistake.)
struct Thing[] = {
{ (void *)&X, (void *)&Y, "op", (void *)&Z },
...
};

OK, that's a *bit* like my intermediate code (my operands can be more
elaborate and have type info attached to each. But it is a faithful
representation of what the source HLL is trying to do).
void eval(Thing *t)
{
...
}

...

eval(&Thing[0]);

Here I'm not sure what's happening. Does this code reside in a compiler (and
the Thing array has been filled in by the compiler)? Then the eval() routine
presumably writes out some C code into a separate file. In which case it's
not that different to what I'm actually doing. (And it's the tiny details
that are the problem: endless compile errors on the C output code, due to
some subtleties of the C type system that I have no interest in yet have to
understand and fix!)

Or is some kind of interpretation going on?
 
K

Keith Thompson

BartC said:
What, always using byte offsets for pointers? It's very useful. What's hard
about it is that C works differently.

C pointers of type `unsigned char*` work *exactly* like that.

[...]
 
B

BartC

Keith Thompson said:
C pointers of type `unsigned char*` work *exactly* like that.

Yes, I know, that's why I'm using lots of casts to (unsigned char*).

But in general, a pointer will have a stride of N bytes. It's not always
clear if that can be used directly (because the pointer might be unaligned,
and/or the offset could be odd).
 
L

Les Cargill

BartC said:
Les Cargill said:
BartC wrote:
Could be, then. I mean no disrespect, but it sounds like you're
doing it the Hard Way, which is ... hard.

What, always using byte offsets for pointers? It's very useful. What's hard
about it is that C works differently.

(Perhaps I should explain I'm using dynamic language A for this project,
which compiles source code of a static language B, into C source code. I
hardly write any actual C at all; mainly when I find myself editing the
C output files by mistake.)
struct Thing[] = {
{ (void *)&X, (void *)&Y, "op", (void *)&Z },
...
};

OK, that's a *bit* like my intermediate code (my operands can be more
elaborate and have type info attached to each. But it is a faithful
representation of what the source HLL is trying to do).
void eval(Thing *t)
{
...
}

...

eval(&Thing[0]);

Here I'm not sure what's happening. Does this code reside in a compiler
(and
the Thing array has been filled in by the compiler)?

The Thing array is what has been generated. eval is handwritten.
Then the eval()
routine
presumably writes out some C code into a separate file. In which case it's
not that different to what I'm actually doing. (And it's the tiny details
that are the problem: endless compile errors on the C output code, due to
some subtleties of the C type system that I have no interest in yet have to
understand and fix!)

Or is some kind of interpretation going on?

Basically - yes. You stick values into an array, then use an
interpreter ( eval(...) )to exploit the array.
 
K

Keith Thompson

BartC said:
Yes, I know, that's why I'm using lots of casts to (unsigned char*).

Why not just use pointers of type `unsigned char*` in the first place?

[...]
 
B

BartC

Keith Thompson said:
Why not just use pointers of type `unsigned char*` in the first place?

This subthread is about the use of C as target language for a translator
from another language.

Pointers to all kinds of types will crop up everywhere, but particularly
where a direct C idiom (a->b, c etc) can't be used. Then these casts are
necessary. (And once pointer expressions are used in some places, then it's
often easier to just use them everywhere rather than investigate which could
use a->b, c etc)

For example, the other language might have a C-like struct data-type, but
with the ability to have extra unofficial fields defined by a type and a
byte offset from the start:

typedef struct _s {
float a,b,c,d;
} S;

S s;

Suppose this struct has another unofficial field 'x' of type int at byte
offset 12. To access this field in s, the expression:

*(&s+12)

can't be used; &S is a pointer to a struct (of stride perhaps 16 bytes), and
will point far outside the struct. Instead, if the offset is a multiple of
sizeof(int), the following could be used:

*((int*)&s+3)

If the offset was 10 instead (sometimes unaligned accesses are fine; or
perhaps s could itself be misaligned by 2 bytes), then this is impossible in
C without using byte offsets somewhere:

*((int*)((char*)&s+10))

(Most uses of these 'unofficial' fields, ie. those which redefine, shadow or
alias existing fields, could be represented in C by anonymous unions and
structs. Such unions and structs are available using gcc. However they are
still implemented in the source language by byte offsets.

Where x in the above example is at offset 12, then an official (gcc)
definition might be:

typedef struct _s {
float a,b
union (float c; int x;};
float d;
} S;

Where x is at offset 10, then it's a bit more fiddly:

#pragma pack(1)
typedef struct _s {
union {
struct {float a,b,c,d;};
struct {char dummy[10]; int x};
};
} S;

You can see that just using a byte offset might be simplest!)
 
B

Ben Bacarisse

BartC said:
Keith Thompson said:
Why not just use pointers of type `unsigned char*` in the first place?

This subthread is about the use of C as target language for a translator
from another language.

Pointers to all kinds of types will crop up everywhere, but particularly
where a direct C idiom (a->b, c etc) can't be used. Then these casts are
necessary. (And once pointer expressions are used in some places, then it's
often easier to just use them everywhere rather than investigate which could
use a->b, c etc)

For example, the other language might have a C-like struct data-type, but
with the ability to have extra unofficial fields defined by a type and a
byte offset from the start:

typedef struct _s {
float a,b,c,d;
} S;

S s;

Suppose this struct has another unofficial field 'x' of type int at byte
offset 12. To access this field in s, the expression:

*(&s+12)

can't be used; &S is a pointer to a struct (of stride perhaps 16 bytes), and
will point far outside the struct.


Why are there structs here at all? I think that's the point that's been
made elsewhere ("be dumb, not smart") and what I was getting at by
saying that it looks more like a translator than a compiler.

In an assembler-generating compiler there wouldd be no structs and there
don't have to be in C either. _s could be a char (well, unsigned char)
array of the right size. Just as you have to tell the assembler what
size object to load from an address, you would have to do the same in
the generated C, so while _s + 12 is now the right address you still
have to say how much to fetch (*(int *)(s + 12)) but it's probably
closer to the model you compiler seems to have been using before.

I suspect that you want the benefit of some C typing (so you don't have
to worry about the size of objects for example) but that's causing
problems elsewhere because the compiler's model is still based round the
raw machine picture that assembler gives you.

You can see that just using a byte offset might be simplest!)

Yes, and byte pointers (unsigned char *s) to go with them!
 

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

Similar Threads

Casts 81
Union and pointer casts? 13
Pointer casts for OOP 2
Casts on lvalues 74
Incompatible type casts 8
void pointers & void function pointers 3
Array of structs function pointer 10
casts and pointers 0

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top