Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Any recommendation for this struct declaration?

37 views
Skip to first unread message

Thiago Adams

unread,
Aug 22, 2018, 9:21:25 AM8/22/18
to

I have a struct to represent a "closuse" or
lambda with capture.

The first member is a function pointer.
and the second is some memory. (I could change the order if necessary)

struct Task
{
void (*func)(void*);
char memory[sizeof(void*)*4];
}

Then I will allocate an array of Tasks on the heap.

struct Tasks *pTasks = malloc(sizeof(struct Task) * N);

Now instead of doing a second malloc a want to use this
memory I have inside Task.

struct X *pX = (struct X*) pTask[2].memory;

static_assert(sizeof(struct X) <= sizeof(char memory[sizeof(void*)*4]), "");

X_Init(pX);
...

My question is if you have some recommendation how
to declare this struct.

I think because the first member is a pointer I will not
have problem on alignments.
I want to have some memory dependent of the size of
a pointer then I can capture, let's say 4 pointers in x86 or
x64.

I don't know if union makes some difference.
for instance:

struct Task
{
void (*func)(void*);
union
{
void* p[4];
char s[sizeof(void*) * 4);
} data;
}







james...@alumni.caltech.edu

unread,
Aug 22, 2018, 10:49:06 AM8/22/18
to
On Wednesday, August 22, 2018 at 9:21:25 AM UTC-4, Thiago Adams wrote:
> I have a struct to represent a "closuse" or
> lambda with capture.
>
> The first member is a function pointer.
> and the second is some memory. (I could change the order if necessary)
>
> struct Task
> {
> void (*func)(void*);
> char memory[sizeof(void*)*4];
> }

I strongly recommend changing that to

void *memory[4];

Not only is that simpler, but using it to store void* objects is much
simpler, and memory is guaranteed to be correctly aligned to store them.

> Then I will allocate an array of Tasks on the heap.
>
> struct Tasks *pTasks = malloc(sizeof(struct Task) * N);
>
> Now instead of doing a second malloc a want to use this
> memory I have inside Task.
>
> struct X *pX = (struct X*) pTask[2].memory;

In that case, I'd recommend requiring the use of C2011, adding #include
<stddef.h>, and adding _Alignas(_max_align_t) to the declaration of
memory, regardless of how you choose to declare it.

> static_assert(sizeof(struct X) <= sizeof(char memory[sizeof(void*)*4]), "");
>
> X_Init(pX);
> ...
>
> My question is if you have some recommendation how
> to declare this struct.
>
> I think because the first member is a pointer I will not
> have problem on alignments.

_Alignof(void *(*)(void*)) is not guaranteed to be >=
_Alignof(struct X). Declaring memory as void*[4] helps, but the
_Alignas() specification clinches the deal. Why assume it will work,
rather than mandating that it will work?

> I want to have some memory dependent of the size of
> a pointer then I can capture, let's say 4 pointers in x86 or
> x64.
>
> I don't know if union makes some difference.
> for instance:
>
> struct Task
> {
> void (*func)(void*);
> union
> {
> void* p[4];
> char s[sizeof(void*) * 4);
> } data;
> }

123456789012345678901234567890123456789012345678901234567890123456789012
The s member of that union doesn't get you anything that you can't get
more by using (char*)p. Use whichever of those two approaches actually
simplifies your code.

Tim Rentsch

unread,
Aug 22, 2018, 1:24:31 PM8/22/18
to
Thiago Adams <thiago...@gmail.com> writes:

> I have a struct to represent a "closuse" or
> lambda with capture. [...]

There is a well-known, if perhaps not too widely known, pattern
for doing such things. Suppose we have a function that we would
like to capture-lambda-ize that looks like this:

return-type F( private-data-pointer, ...other arguments... );

In fact let's suppose we have several similar functions, with the
same return type and the same set of "other arguments", but with
different kinds of private data, something like this:

return-type G( G-private-data-pointer, ...other arguments... );
return-type H( H-private-data-pointer, ...other arguments... );
...

To handle these, first we declare a structure type for the
function pointer, and give a typedef for the function type so
things don't get too confusing (disclaimer: none of what looks
like code below has been anywhere near a compiler):

struct FGH_lambda;
typedef return-type FGH_f( struct FGH_lambda *, ..other args.. );

Now define the structure type. It has only one member, a pointer
to function

struct FGH_lambda {
FGH_f *f;
};

Then, for each FGH function we define a struct that will hold the
data for that function. Each of these structs will have as its
first member a struct FGH_lambda. Thus for function F we might
have

struct F_capture_data {
struct FGH_lambda callback[1];
int x;
char *p;
... etc ...
};

Having done this, to create a lambda for F, we create an instance
of its captured data struct:

struct F_capture_data {
{{ F_lambda }},
.x = 3, .p = 0, ...
} closure_1;

To pass this closure to another function, use its 'callback'
member:

function_needing_callback( closure_1.callback );

(Note that 'closure_1.callback' is actually a pointer to the
member, because that member is an array of length 1.)

That function can invoke the callback by using the 'f' member,
and giving the closure as the first argument value:

void
function_needing_callback( struct FGH_lambda *closure ){
closure->f( closure, .. other arguments .. );
}

For the function F_lambda, we have a pointer to the closure data
in the value of the first argument. That pointer needs casting
to get to an appropriate type, but after that one cast everything
is type safe:

return-type
F_lambda( struct FGH_lambda *closure, .. other arguments .. ){
struct F_capture_data *my_data = (void*) closure;
.. now 'my_data' can be used to access the captured data ..
}

Follow the same pattern for functions G and H, etc. If there is
a callback function that doesn't need any private data, it can
use a struct FGH_lambda structure directly, without any enclosing
struct:

struct FGH_lambda simple = { simple_callback_f };
function_needing_callback( &simple );

I may have gone through this outline a bit quickly. Were you
able to follow everything or was there something confusing?

(I hope I didn't make any mistakes in the code. I am keeping
true to my word that the code hasn't been near a compiler. :)

Thiago Adams

unread,
Aug 22, 2018, 1:29:40 PM8/22/18
to
I think _Alignas(_max_align_t) is not implemeted
at Microsoft c compiler. Microsoft C++ compiler has and it should
be the same C header but I think they "fix" only <cstddef> and
not stddef.h.

from <cstddef> (C++ header)
typedef double max_align_t; // most aligned type

---

To use this using microsoft C compiler I think I will have
to use: where 8 is the sizeof double.

struct Task
{
TaskFunction pTaskFunction;
__declspec (align(8)) void* pCapture[4];
};


and to see if everything is working I should store
some double values at that memory.

Thiago Adams

unread,
Aug 22, 2018, 1:49:57 PM8/22/18
to
On Wednesday, August 22, 2018 at 2:24:31 PM UTC-3, Tim Rentsch wrote:
I am passing the function pointer plus void* data.

I don't know if your suggestion works for function pointer with
no capture. like casting function pointer to your struct FGH_lambda
and make it works in this case as well.

The pattern I use is:

struct Capture {
struct X* arg1;
const char* arg2;
...
};

then at stack

struct Capture capture = { arg1, arg2 ...};

then I copy the capture to the destination that is
this memory inside task.

Post(function_pointer, &capture, sizeof(capture));

---


I was also wondering if I could copy the stack to the heap
without create the struct.

Like copy some begin-to-end part of the stack to the heap.
Then instead of creating the struct I could copy the stack
and them latter return the heap-to-stack again.

Something I could do is similar of scanf and printf.
But I would like to have scanf and printf to work with binary
data.

Capture(pMemory, "%d%s", a1, "text");

This would serialize the data to pMemory that is on heap.

int a1;
char s[1000];
DecodeCapture(pMemory, "%d%s", &a1, s);

this would copy to stack again.

I think copy stack to heap and heap to stack again
is something too crazy (dangerous) to do?

Tim Rentsch

unread,
Aug 22, 2018, 10:54:37 PM8/22/18
to
Thiago Adams <thiago...@gmail.com> writes:

> On Wednesday, August 22, 2018 at 2:24:31 PM UTC-3, Tim Rentsch wrote:
>
>> Thiago Adams writes:
>>
>>> I have a struct to represent a "closuse" or
>>> lambda with capture. [...]
>>
>> There is a well-known, if perhaps not too widely known, pattern
>> for doing such things. [...]
>
> I am passing the function pointer plus void* data.

I understand that. I was explaining a way of getting
comparable functionality while passing only one pointer
instead of two. If that capability doesn't suit your
application then don't use it.
0 new messages