簡體   English   中英

通過void訪問結構的成員*

[英]Accessing members of the struct via void *

該解決方案包括兩個部分,一個是靜態庫,該靜態庫從庫的用戶那里接收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);

問:庫是否有一種方法可以知道如何迭代和遍歷成員字段,並查看是否有指向該函數的指針並將其調用。

庫是否有辦法知道如何迭代和遍歷成員字段,並查看是否有指向該函數的指針並將其調用。

不,那里沒有。

最好的選擇是在具有這些成員的庫中創建一個結構,然后傳遞該結構而不是void*

正如@immibis所說,如果編譯器不知道傳遞給函數的數據類型是什么,則無法進行此工作(即編譯器無法證明編譯此類代碼的合理性)。

由於您希望將對象傳遞到庫而不在庫中存儲有關每個對象的類型的信息,因此可以通過執行以下操作在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 

如果庫中有關於可能的結構類型的0信息,則您將無法執行。 庫必須以某種方式獲取信息或偏移量。

我能想到的唯一方法是:

  1. 所有register成員都具有相同的原型
  2. 將偏移量傳遞給函數。

我創建了一個例子

#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的手冊頁說:

曼達達

va_start()

 [...] 

由於此參數的地址可以在va_start()宏中使用, 因此不應將其聲明為寄存器變量,函數或數組類型。

除了指向該結構的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,因為它的功能與您嘗試執行的操作類似。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM