通过void *访问结构的成员

时间:2018-02-25 23:00:10

标签: c

该解决方案由两部分组成,一部分是静态库,它接收来自库用户的struct实例。库不知道结构类型是什么,它只知道将有两个具有特定名称的函数指针。

图书馆代码

预编译库无法知道用户结构的类型,因此通过void*接收

void save(void *data) {
    // library will save/cache user's object
    data->registered(); // if register successful
}

void remove(void *data) {
    // library will remove the object from memory
    data->remove(); // if removed successful
}

图书馆代码的用户

struct Temp { // random order of fields
   void (*custom1)();
   void (*registered)();
   void (*custom2)();
   void (*remove)();
   void (*custom3)();
}

void reg() {
    printf("registered");
}

void rem() {
    printf("removed");
}

void custom1() {}
void custom2() {}
void custom3() {}

var temp = malloc(struct Temp, sizeof(struct Temp));
temp->registered = reg;
temp->remove = rem;
temp->custom1 = custom1; // some custom functions
temp->custom2 = custom2; 
temp->custom3 = custom3;


// calling library code
save(temp);
remove(temp);

Q值。有没有办法让图书馆知道如何迭代和浏览成员字段,看看是否有指向这种功能的指针并将其称为可用。

4 个答案:

答案 0 :(得分:2)

  

库是否有办法知道如何迭代并浏览成员字段,看看是否有指向此类函数的指针并将其称为可用。

没有。

最好的办法是在库中创建一个包含这些成员的结构,并传递该结构而不是void*

答案 1 :(得分:1)

正如@immibis所说,如果编译器不知道传递给函数的数据类型是什么,那么就没有办法(即编译器没有办法证明编译这样的代码)。

由于您希望将对象传递到库而不存储有关库中每个对象类型的信息,因此您可以fake polymorphism in C执行以下操作:

callback.h

#ifndef _CALLBACK_H_
#define _CALLBACK_H_

typedef struct {
    void (*registered)();
    void (*removed)();
} ICallback;

#endif _CALLBACK_H_

pre_comp.h

#ifndef _PRE_COMP_H_
#define _PRE_COMP_H_

#include "callback.h"

void save(ICallback* data);
void remove(ICallback* data);

#endif /* _PRE_COMP_H_ */

precomp.c

#include <stdlib.h> /* NULL */

#include "callback.h"
#include "pre_comp.h"

void save(ICallback *data) {
    if (NULL != data && NULL != data->registered) {
        data->registered(); // if register successful
    }
}

void remove(ICallback *data) {
    if (NULL != data && NULL != data->removed) {
        data->removed(); // if removed successful
    }
}

的main.c

#include <stdio.h>

#include "pre_comp.h"
#include "callback.h"

struct Temp {
    ICallback base; // has to be defined first for this to work
    void (*custom1)();
    void (*custom2)();
    void (*custom3)();
};


// calling library code

void reg() {
    puts("registered");
}

void rem() {
    puts("removed");
}


int main() {
    struct Temp data = {{reg, rem}};
    save((ICallback*)&data);
    remove((ICallback*)&data);
}

编译

  

gcc pre_comp.c main.c

输出

registered
removed

答案 2 :(得分:1)

如果库有关于可能的结构类型的0信息,那么你 做不到。图书馆必须以某种方式得到信息或抵消。

我能想到的唯一方法是:

  1. 所有register成员都拥有相同的原型
  2. 将偏移量传递给函数。
  3. 我创建了一个这个

    的例子
    #include <stdio.h>
    #include <stddef.h>
    #include <stdint.h>
    
    // function that does not know anything about any struct
    void reg(void *data, size_t offset)
    {
        uintptr_t *p = (uintptr_t*) (((char*) data) + offset);
    
        void (*reg)() = (void(*)()) *p;
    
        reg();
    }
    
    
    struct A {
        int c;
        void (*reg)();
    };
    
    struct B {
        int b;
        int c;
        void (*reg)();
    };
    
    void reg_a()
    {
        printf("reg of A\n");
    }
    
    void reg_b()
    {
        printf("reg of B\n");
    }
    
    int main(void)
    {
        struct A a;
        struct B b;
    
        a.reg = reg_a;
        b.reg = reg_b;
    
    
        reg(&a, offsetof(struct A, reg));
        reg(&b, offsetof(struct B, reg));
        return 0;
    }
    

    打印:

    $ ./c 
    reg of A
    reg of B
    

    我用valgrind运行它,我没有得到任何错误或警告。我不确定是否 这违反了某种严格的别名规则或产生未定义的行为 因为uintptr_t*次转化,但至少它似乎有效。

    然而,我认为,更清洁的解决方案是重写register(顺便说一句。register 是C中的关键字,您不能将其用于函数名称)函数 接受函数指针和可能的参数,如下所示:

    #include <stdio.h>
    #include <stdarg.h>
    
    void reg(void (*func)(va_list), int dummy, ...)
    {
        if(func == NULL)
            return;
    
        va_list ap;
        va_start(ap, dummy);
        func(ap);
        va_end(ap);
    }
    
    
    void reg1(int a, int b)
    {
        printf("reg1, a=%d, b=%d\n", a, b);
    }
    
    void vreg1(va_list ap)
    {
        int a = va_arg(ap, int);
        int b = va_arg(ap, int);
        reg1(a, b);
    }
    
    void reg2(const char *text)
    {
        printf("reg2, %s\n", text);
    }
    
    void vreg2(va_list ap)
    {
        const char *text = va_arg(ap, const char*);
        reg2(text);
    }
    
    int main(void)
    {
        reg(vreg1, 0, 3, 4);
        reg(vreg2, 0, "Hello world");
        return 0;
    }
    

    这有输出:

    reg1, a=3, b=4
    reg2, Hello world
    

    请注意,reg有一个dummy参数。我之所以这样做是因为 stdarg说:

      

    man stdarg

         

    va_start()

    [...]
    
         

    因为可以在va_start()宏中使用此参数的地址,   不应将其声明为寄存器变量,也不应声明为   函数或数组类型。

答案 3 :(得分:0)

除了指向结构的void指针之外,您还可以采用类似于qsort的方法并传递函数指针。

这是qsort的函数原型,它是一个可用于对任何类型的数组进行排序的函数:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

它需要一个执行比较的函数指针,因为没有它,qsort就不会知道如何比较两个对象。

这可以通过这样的函数原型应用于您的任务:

int DoFoo(void *thing, void (*register)(void *), void (*remove)(void *))

此函数获取一个指向结构的void指针,然后在需要注册或删除该结构时调用它们可以调用的两个函数。将函数作为结构的成员不是必需的,我通常不推荐它。我建议阅读qsort,因为它的功能类似于你想要做的事情。