C(void *)用作多态函数指针

时间:2014-03-07 15:07:42

标签: c pointers polymorphism void-pointers

我正在尝试创建一个系统调用处理程序,我不知道如何存储它。

我正在使用以下typedef来存储(void *)指针,该指针应该接收函数的地址和表示参数数量的整数arg_no。然后,我创建了一个这种类型的数组。

typedef struct
{
  void *foo;
  int arg_no;
}td_sys_call_handler;

td_sys_call_handler ish[SYSCALL_HANDLER_NUM];

我正在尝试按以下方式初始化数组。

  ish[0].foo  = void     (*halt) (void);                  ish[0].arg_no  = 0;
  ish[1].foo  = void     (*exit) (int status) NO_RETURN;  ish[1].arg_no  = 1;
  ish[2].foo  = pid_t    (*exec) (const char *file);      ish[2].arg_no  = 1;
  ish[3].foo  = int      (*wait) (pid_t);                 ish[3].arg_no  = 1;
  ish[4].foo  = bool     (*create) (const char *file, unsigned initial_size);
                                                          ish[4].arg_no  = 2;
  ish[5].foo  = bool     (*remove) (const char *file);    ish[5].arg_no  = 1;
  ish[6].foo  = int      (*open) (const char *file);      ish[6].arg_no  = 1;
  ish[7].foo  = int      (*filesize) (int fd);            ish[7].arg_no  = 1;
  ish[8].foo  = int      (*read) (int fd, void *buffer, unsigned length);
                                                          ish[8].arg_no  = 3;
  ish[9].foo  = int      (*write) (int fd, const void *buffer, unsigned length);
                                                          ish[9].arg_no  = 3;
  ish[10].foo = void     (*seek) (int fd, unsigned position);
                                                          ish[10].arg_no = 2;
  ish[11].foo = unsigned (*tell) (int fd);                ish[11].arg_no = 1;

但是从函数指针到void指针的所有赋值都会产生以下错误:

../../userprog/syscall.c: In function ‘syscall_init’:
../../userprog/syscall.c:76:17: error: expected expression before ‘void’
../../userprog/syscall.c:77:17: error: expected expression before ‘void’
../../userprog/syscall.c:78:17: error: expected expression before ‘pid_t’
../../userprog/syscall.c:79:17: error: expected expression before ‘int’
../../userprog/syscall.c:80:17: error: expected expression before ‘_Bool’
../../userprog/syscall.c:82:17: error: expected expression before ‘_Bool’
../../userprog/syscall.c:83:17: error: expected expression before ‘int’
../../userprog/syscall.c:84:17: error: expected expression before ‘int’
../../userprog/syscall.c:85:17: error: expected expression before ‘int’
../../userprog/syscall.c:87:17: error: expected expression before ‘int’
../../userprog/syscall.c:89:17: error: expected expression before ‘void’
../../userprog/syscall.c:91:17: error: expected expression before ‘unsigned’

我的印象是void*是该语言中唯一的多态现象,它可以指向任何东西。 但是,看来我错了。

那么哪个指针的类型可以存储任何函数类型的地址?

另外,你能给我一个关于C多态的好参考吗?我看过很多书,但据我所见,多态性章节非常薄。

谢谢。

6 个答案:

答案 0 :(得分:7)

是的,你错了。

void *指针可以指向任何类型的数据,但在C代码(函数)中不是数据。

即使在void *和函数指针之间进行转换也是无效的:即使在大多数现代计算机上它都会按预期工作,但语言不能保证。

我不明白你的代码中你打算如何在实践中使用“重载”,你期望如何调用foo指针?仅仅具有预期数量的参数是不够的,参数具有类型,因此在函数调用中处理不同。

答案 1 :(得分:5)

您需要的表示法将系统调用函数指针强制转换为void *

ish[0].foo  = (void *)halt;

C标准不保证指向函数的指针适合指向void *等数据的指针;幸运的是,POSIX介入并确保指向函数的指针与指向数据的指针大小相同。

答案 2 :(得分:2)

你的语法错了。您应该首先声明您的函数指针。然后,您可以使用函数指针的地址来指定指针。

void (*halt) (void) = halt_sys_call_function;
ish[0].foo  = &halt; ish[0].arg_no  = 0;

C不直接支持传统的继承关系,但它确实保证了结构的地址也是结构的第一个成员的地址。这可以用来模拟C中的多态性。我在我写的关于dynamic dispatch in C的答案中描述了一种类似的方法。

答案 3 :(得分:1)

考虑一个格式化的结构,专门用于保存每个函数:

typedef struct 
{

  void     (*halt) (void);                  
  void     (*exit) (int status);  
  pid_t    (*exec) (const char *file);      
  int      (*wait) (pid_t);                 
  bool     (*create) (const char *file, unsigned initial_size);
  bool     (*remove) (const char *file);    
  int      (*open) (const char *file);      
  int      (*filesize) (int fd);            
  int      (*read) (int fd, void *buffer, unsigned length);
  int      (*write) (int fd, const void *buffer, unsigned length);  
  void     (*seek) (int fd, unsigned position);   
  unsigned (*tell) (int fd);                

} myFuncs;

OR

这很乱并且非常不可用,但是如果你使用void*将每个指针强制转换为void *addressOfWait = (void*)&wait;,那么你可以在调用之前重新转换为正确的函数指针类型:

int (*waitFunctionPointer)(pid_t) = addressOfWait;

然后你可以调用那个指针:

waitFunctionPointer((pid_t) 1111); //wait for process with pid of 1111

答案 4 :(得分:1)

我会要求@ problemPotato原谅他的结构定义:

typedef struct 
{
   void     (*halt) (void);                  
   void     (*exit) (int status);  
   pid_t    (*exec) (const char *file);      
   int      (*wait) (pid_t);                 
   bool     (*create) (const char *file, unsigned initial_size);
   bool     (*remove) (const char *file);    
   int      (*open) (const char *file);      
   int      (*filesize) (int fd);            
   int      (*read) (int fd, void *buffer, unsigned length);
   int      (*write) (int fd, const void *buffer, unsigned length);  
   void     (*seek) (int fd, unsigned position);   
   unsigned (*tell) (int fd);                
} fs_ops;

假设你有匹配的功能,声明如下:

int      ext5_open(const char * file);
unsigned ext5_tell (int fd);

然后你可以定义和初始化一个变量(函数的裸名称是指向它的指针):

fs_ops ext5_ops = {
   .open = ext5_open,
   .tell = ext5_tell,
};

未初始化的字段获取NULL(即,指向无函数的指针)。您可以更改字段的值,询问是否已设置(if(ext5_ops.seek == NULL) ...),并调用该函数:

retval = ext5_ops.(*ext5_open)("/tmp/junk");

(*ext5_open)周围的parenteses是因为*(指针间接)比函数调用的绑定强。)

答案 5 :(得分:1)

函数指针可以转换为void *,但将它转换回正确的函数指针类型以便调用它会有点棘手。应该可以使用union。对于要存储的函数类型,您需要一个正确类型的单独union-member。并且,正如用户4815162342在评论中注明的那样,您需要管理所有各种组合,可能需要enum

typedef struct
{
  union {
    void *vp;
    void (*v__v)(void);
    void (*v__i)(int);
    pid_t (*pid__ccp)(const char *);
    int (*i__pid)(pid_t);
    bool (*b__ccp_u)(const char *, unsigned);
    bool (*b__ccp)(const char *);
    int (*i__ccp)(const char *);
    int (*i__i)(int);
    int (*i__i_vp_u)(int, void *, unsigned);
    int (*i__i_cvp_u)(int, const void *, unsigned);
    void (*v__i_u)(int, unsigned);
    unsigned (*u__i)(int);
  } fp;
  int arg_no;
}td_sys_call_handler;

这里的想法是尝试将类型编码为标识符,作为一种" apps-Hungarian"。这样,任何这些标识符的含义都可以直接看到。

可能更容易同时生成这些指针和关联的枚举。我认为管理这部分的最简单方法是使用我最喜欢的技巧X-Macros。警告:它变得越来越奇怪。

#define function_types(_) \
    _(v__v, void, void) \
    _(v__i, void, int) \
    _(pid_ccp, pid_t, const char *) \
    _(i__pid, int, pid_t) \
    _(b__ccp_u, const char *, unsigned) \
    _(b__ccp, const char *) \
    _(i__ccp, const char *) \
    _(i__i, int) \
    _(i__i_vp_u, int, void *, unsigned) \
    _(i__i_cvp_u, int, const void *, unsigned) \
    _(v__i_u, int, unsigned) \
    _(u__i, unsigned, int) \
    /* end function_types */

这个" master" -macro是一个以逗号分隔的标记表,它逐行传递给传入的_下划线宏。

现在可以通过编写其他宏来构造结构类型来使用行,这些宏作为_传递给表宏来实例化模板:

#define create_function_pointer(id, ret, ...) \
    ret (*id)(__VA_ARGS__);

#define create_function_type_id(id, ret, ...) \
    f__ ## id

typedef struct {
    union {
        void *vp;
        function_types(create_function_pointer)
    } fp;
    int arg_no;
    enum {
        function_types(create_function_type_id)
    } type;
} td_sys_call_handler;

现在可以填充这些结构的数组:

td_sys_call_handler ish[SYSCALL_HANDLER_NUM];
int i=0;

ish[i++]  = (td_sys_call_handler){ halt,     0, f__v__v };
ish[i++]  = (td_sys_call_handler){ exit,     1, f__v__i };
ish[i++]  = (td_sys_call_handler){ exec,     1, f__pid__ccp };
ish[i++]  = (td_sys_call_handler){ wait,     1, f__i__pid };
ish[i++]  = (td_sys_call_handler){ create,   2, f__b__ccp_u };
ish[i++]  = (td_sys_call_handler){ remove,   1, f__b__ccp };
ish[i++]  = (td_sys_call_handler){ open,     1, f__i__ccp };
ish[i++]  = (td_sys_call_handler){ filesize, 1, f__i__i };
ish[i++]  = (td_sys_call_handler){ read,     3, f__i__i_vp_u };
ish[i++]  = (td_sys_call_handler){ write,    3, f__i__i_cvp_u };
ish[i++]  = (td_sys_call_handler){ seek,     2, f__v__i_u };
ish[i++]  = (td_sys_call_handler){ tell,     1, f__u__i };

现在,调用给定其中一个结构的函数将需要(如您所推测的)switch,每个签名都有一个单独的案例。它需要使用stdarg和使用适当的union成员函数指针调用来破解参数。

void make_sys_call(td_sys_call_handler ish, ...){
    va_list ap;
    int i;
    const char *ccp;
    pid_t pid;
    bool b;
    void *vp;
    unsigned u;
    const void *cvp;
    va_start(ap, ish);
    switch(ish.type) {
    case f__v__f: ish.fp.v__v();
                  break;
    case f__v__i: i = va_arg(int);
                  ish.fp.v__i(i);
                  break;
    case f__pid__ccp: ccp = va_arg(const char *);
                      ish.fp.pid__ccp(ccp);
                      break;
    // etc.
    }
    va_end(ap);
}

无法直接返回不同的类型。您将需要分配一个联合类型变量来保存返回值并返回该值,或者更疯狂的东西。外部堆栈数据类型可以包含各种返回类型的联合。根据分析结果,可能适合考虑这一点而不是返回联合。

HTH。