2

I want to be able to call a function to do something with a struct kind of like a class's methode with the self argument in python.
Something like this: struct.doSomethingWithStruct() where doSomethingWithStruct doesn't need to have &struct passed to it in order to do something with it.

I tried to make a function that returns another function with the struct build into it but every time i call it, it modifies struct1* S for all other structs too because functionBuilder returns a pointer to the same function every time.

#include <stdlib.h>
#include <stdio.h>

typedef struct {
    int x;
    void* (*print)(int x);
} struct1;

void* functionBuilder(struct1* s){
    void printStruct1(int a){
        struct1* S = s;
        printf("%d\n", a+S->x);
    }
    return printStruct1;
}

struct1* structBuilder(int x){
    struct1* temp = malloc(sizeof(struct1));
    temp->x = x;
    temp->print = functionBuilder(temp);
    return temp;
}

int main() {
    struct1 *s1 = structBuilder(10);
    struct1 *s2 = structBuilder(20);
    
    s1->print(1);
    s2->print(2);
    
    free(s1);
    free(s2);

    return 0;
}

This code prints:
21
instead of
11
22

I've tried to allocate memory for struct1* S but that also didnt work.
I've been trying to find a way for functionBuilder to return a unique function each time but i'm new to C and I havn't managed to find a way to do this yet.

If anyone knows how to do this or something else that also work please provide an explenation of how it works because im new to C and like to learn new things.

PS: I'm new to stackoverflow and if there is any way for me to improve this question please tell me how.

Benny
  • 21
  • 4
  • C doesn't allow nested functions. A compiler might add it as a non-portable and non-standard extension. – Some programmer dude Jun 10 '23 at 21:07
  • Also note that a pointer to a function can't really be converted to a `void*`. – Some programmer dude Jun 10 '23 at 21:11
  • @Someprogrammerdude what does non-portable and non-standard extention mean ? Also i just ran the program again on my pc and it only returned 21 and what is a function stored in ? – Benny Jun 10 '23 at 21:18
  • it's possible, obviously, the function does not need to be nested. – user1095108 Jun 10 '23 at 21:21
  • c++ makes this a lot easier with lambdas, maybe look there. – user1095108 Jun 10 '23 at 21:22
  • C doesn't have "closures", the impled `&self` in other languages is there but merely hidden from being passed on the stack, in C the point of C is to not hide such things. One is coming to C and saying "be less like C". – Motomotes Jun 10 '23 at 21:23
  • 1
    "what does non-portable and non-standard extention mean' It means that it is not guaranteed to work on a different computer with a different compiler, or even on your computer tomorrow when you do a software update. – n. m. could be an AI Jun 10 '23 at 21:24
  • I know this is dumb but i just want to know how to do it because it's interesting and also im too lazy to write &self every time. – Benny Jun 10 '23 at 21:26
  • 2
    The point of C is to be explicit and thorough, not lazy, that's for what we have JavaScript. Anyway, see here for some "workarounds", https://stackoverflow.com/questions/4393716/is-there-a-a-way-to-achieve-closures-in-c – Motomotes Jun 10 '23 at 21:30
  • "Non-standard" and "non-portable" means exactly what the words say. "Non-standard" means that it's not in the C standard. "Non-portable" means that it's not portable to any other environment (compiler, target, host, etc.). – Some programmer dude Jun 10 '23 at 21:36
  • @Benny "where doSomethingWithStruct doesn't need to have &struct passed to it in order to do something with it." --> `doSomethingWithStruct()` still has `&struct` passed to it. What you want is not not explicitly do so, yet it is still passed under the syntax. For C, this is an _anti-pattern_ and can be done with macros. Yet that leads to less clear code than `struct.doSomethingWithStruct(&struct, ...)`. – chux - Reinstate Monica Jun 11 '23 at 04:10

3 Answers3

2

ISO C offers no portable way to do this, but if you know what specific platform you're targeting, you can do it. For example, here's a solution that works on :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

typedef void (*printType)(int x);

typedef struct {
    int x;
    printType print;
} struct1;

__asm__(
    "printStruct1Template:\n\t"
    "endbr64\n\t"
    "mov printStruct1TemplateEnd(%rip), %rsi\n\t"
    "jmp *printStruct1TemplateEnd+8(%rip)\n"
    "printStruct1TemplateEnd:"
);
extern const char printStruct1Template[], printStruct1TemplateEnd[];

void printStruct1Inner(int a, struct1* S){
    printf("%d\n", a+S->x);
}

#define TEMPLATE_LEN (printStruct1TemplateEnd - printStruct1Template)
#define LEN (TEMPLATE_LEN + 16)

printType functionBuilder(struct1* s){
    char *mem = mmap(NULL, LEN, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy(mem, printStruct1Template, TEMPLATE_LEN);
    memcpy(mem + TEMPLATE_LEN, &s, 8);
    void *innerAddr = (void *)printStruct1Inner;
    memcpy(mem + TEMPLATE_LEN + 8, &innerAddr, 8);
    mprotect(mem, LEN, PROT_READ|PROT_EXEC);
    __builtin___clear_cache(mem, mem + LEN);
    return (printType)mem;
}

struct1* structBuilder(int x){
    struct1* temp = malloc(sizeof(struct1));
    temp->x = x;
    temp->print = functionBuilder(temp);
    return temp;
}

int main() {
    struct1 *s1 = structBuilder(10);
    struct1 *s2 = structBuilder(20);
    
    s1->print(1);
    s2->print(2);
    
    munmap((void *)s1->print, LEN);
    munmap((void *)s2->print, LEN);
    
    free(s1);
    free(s2);

    return 0;
}
  • thank you very much. Im on windows so what should I change ? also can you explane functionBuilder and the assembly code im new to C and I dont know assembly. – Benny Jun 10 '23 at 22:09
1

C does not provide anything like lambdas that allow you to capture values from the current scope and call the function later to access those values. Even C extensions that allow nested functions (like you use here) make those nested functions local to the enclosing function (like local vars), so when the outer function returns, the nested function (and any pointer to it you may have saved or returned) is no longer valid.

So to do this, you need to somehow create a "new" function at runtime to capture the extra information you need. You can do that by JIT creating new functions as Joseph Sible describes above, but that tends to be very target specific as well as error-prone and tricky.

An alternative is to "pre-create" at compile time a bunch of instances of a function and an array of the extra data, with each function hard-coded to access a specific array element. You can then dole these out as needed. Some code I have lying around to save extra arguments to a callback function (for use with a library that only has callbacks with no arguments):

#define REP10(P, M)  M(P##0) M(P##1) M(P##2) M(P##3) M(P##4) M(P##5) M(P##6) M(P##7) M(P##8) M(P##9)
#define REP100(M) REP10(,M) REP10(1,M) REP10(2,M) REP10(3,M) REP10(4,M) REP10(5,M) REP10(6,M) REP10(7,M) REP10(8,M) REP10(9,M)

typedef void (*callback_fn_t)(void);            // or whatever signature you need
typedef void (*callback_internal_t)(void *);    // with extra args so you can get what you need

struct callback_t {
    struct callback_t   *next;
    const callback_fn_t callback;
    callback_internal_t internal;
    void                *extra;
};
#define CB_FUNC_DECL(M)  static void cbfunc##M();
REP100(CB_FUNC_DECL)
#define CB_TABLE_INIT(M) { M ? callback_table+M-1 : 0, cbfunc##M },
static struct callback_t callback_table[100] = { REP100(CB_TABLE_INIT) };
static struct callback_t *callback_freelist = &callback_table[99];
#define CB_FUNC_DEFN(M)  static void cbfunc##M() { callback_table[M].internal(callback_table[M].extra); }
REP100(CB_FUNC_DEFN)

struct callback_t *alloc_callback(callback_internal_t fn, void *arg) {
    struct callback_t *rv = callback_freelist;
    if (rv) {
        callback_freelist = rv->next;
        rv->internal = fn;
        rv->extra = arg;
    }
    return rv;
}   

void free_callback(struct callback_t *cb) {
    cb->next = callback_freelist;
    callback_freelist = cb;
}

With this you can allocate callbacks by calling alloc_callback and then pass cb->callback as the callback argument to the external library. When it calls that callback, it will then call your callback with the extra argument.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
0

You are looking for a thing like callable objects that exist in C++ – unfortunately C doesn't provide support for, so you need a way to mimic such thing.

Classic callback mechanisms often provide means to pass custom objects through via a void pointer like:

void doSomething(void(*callback)(void*), void* userData)
                                         ^^^^^^^^^^^^^^
{
    // ...
    callback(userData);
}

We can base a solution on a struct containing a function pointer and the data object:

struct Functor
{
    void(*function)(void* /* additional parameters as needed... */);
    void* object;
};

Functor functionBuilder(/*...*/)
{
    struct Functor f = { /* set the members... */ };
    return f;
}

The user would then need to call it as:

struct Functor f = functionBuilder(/*...*/);
f.function(f.object);

Instead of or additionally to the void pointer you might include further data as needed; a variant could be a union containing data for different functions and an identifier for the active union member:

struct Functor
{
    void(*function)(void); // just ANY function pointer
    unsigned int type;     // even better: a dedicated ENUM!
    union
    {
        int n;
        char const* str;
        // ...
    } data;
};

Correct usage now is a bit challenging, so we might provide a helper function doing so:

void call(struct Functor f)
{
    switch(f.type)
    {
    case 0:
        ((void(*)(int))f.function)(f.data.n);
        break;
    case 1:
        ((void(*)(char const*))f.function)(f.data.str);
        break;
    default:
        // some appropriate error handling
        break;
    }
}

Usage is then simple:

struct Functor f = functionBuilder(/*...*/);
call(f);

or even as a one-liner:

call(functionBuilder(/*...*/));
Aconcagua
  • 24,880
  • 4
  • 34
  • 59