我正在尝试创建一个系统调用处理程序,我不知道如何存储它。
我正在使用以下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多态的好参考吗?我看过很多书,但据我所见,多态性章节非常薄。
谢谢。
答案 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。