C 检查是否为';将函数指针强制转换为另一个指针是安全的
在我的代码中,我试图使用虚拟对象来执行C中的模块化 目前,我通过函数指针指定了对每个对象都有用的重要函数,如析构函数、C 检查是否为';将函数指针强制转换为另一个指针是安全的,c,function-pointers,c99,C,Function Pointers,C99,在我的代码中,我试图使用虚拟对象来执行C中的模块化 目前,我通过函数指针指定了对每个对象都有用的重要函数,如析构函数、toString、equals,如下所示: typedef void (*destructor)(const void* obj); typedef void (*to_string)(void* obj, int bufferSize, const char* buffer); typedef bool (*equals)(void* obj, const void* cont
toString
、equals
,如下所示:
typedef void (*destructor)(const void* obj);
typedef void (*to_string)(void* obj, int bufferSize, const char* buffer);
typedef bool (*equals)(void* obj, const void* context);
destructor d = (destructor)destroyFoo1;
在我的代码库中,我使用与给定的typedef
兼容的函数指针抽象地处理对象,例如:
struct Foo {
int a;
} Foo;
void destroyFoo1(const Foo* p) {
free((void*)p);
}
int main() {
//...
Foo* object_to_remove_from_heap = //instance of foo
destructor d = destroyFoo1;
//somewhere else
d(object_to_remove_from_heap, context);
}
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context);
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context)
{
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p)
{
free((void*)p);
}
int main(void)
{
//this will work:
//FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr
//macro exist and is equal to 1
destructor destructor1 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo1);
//this raise a compile error:
//FUNCTION_POINTER_destructor_void_destroyFoo2_voidConstPtr_voidConstPtr
//does not exist (or exists but its value is not 1)
destructor destructor2 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo2);
}
代码编译后通常只会生成一个警告(析构函数的第一个参数应该是const void*
,但它是const Foo*
)
但是,,
由于我启用了-Werror
,因此“无效指针强制转换”被视为错误。
要解决此问题,我需要强制转换函数指针,如下所示:
typedef void (*destructor)(const void* obj);
typedef void (*to_string)(void* obj, int bufferSize, const char* buffer);
typedef bool (*equals)(void* obj, const void* context);
destructor d = (destructor)destroyFoo1;
我知道每个标准const void*
和const Foo*
可能具有不同的内存大小,但我假设部署代码的平台const void*
和const Foo*
分配在相同的内存空间中,并且具有相同的大小。一般来说,我假设函数指针的强制转换(其中至少有一个指针参数更改为其他指针)是安全的强制转换
这很好,但是当我需要更改析构函数
类型的签名时(例如,通过添加新的const void*context
参数),这种方法显示了它的弱点。现在有趣的警告被静音,函数指针调用中的参数数量不匹配:
//now destructor is
typedef void (*destructor)(const void* obj, const void* context);
void destroyFoo1(const Foo* p) {
free((void*)p);
}
destructor d = (destructor)destroyFoo1; //SILCENCED ERROR!!destroyFoo1 has invalid parameters number!!!!
//somewhere else
d(object_to_remove_from_heap, context); //may mess the stack
我的问题是:有没有办法检查一个函数指针是否确实可以安全地强制转换到另一个函数指针中(如果不能,则生成编译错误)?,类似于:
destructor d = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo1);
如果我们通过destroyFoo1
一切正常,但是如果我们通过destroyFoo2
编译器会抱怨
下面是总结问题的代码
typedef void (*destructor)(const void* obj, const void* context);
typedef struct Foo {
int a;
} Foo;
void destroyFoo1(const Foo* p, const void* context) {
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p) {
free((void*)p);
}
int main() {
//this is(in my case) safe
destructor destructor = (destructor) destroyFoo1;
//this is really a severe error!
//destructor destructor = (destructor) destroyFoo2;
Foo* a = (Foo*) malloc(sizeof(Foo));
a->a = 3;
int context = 5;
if (a != NULL) {
//call a destructor: if destructor is destroyFoo2 this is a SEVERE ERROR!
//calling a function accepting a single parameter with 2 parameters!
destructor(a, &context);
}
}
感谢您的回复,已经有一段时间了,但是函数指针赋值的代码不应该是:
//this is okay
destructor destructor1 = &destructorFoo1;
//this should throw a compilation error!
destructor destructor2 = &destructorFoo2;
编辑:
好吧,我走了,仔细看看这个
如果我将函数指针的声明更改为使用const Foo*p
而不是const void*obj
,这样我们就不会依赖强制转换来隐藏void*
和Foo*
之间的不兼容,那么我会得到一个带有默认编译器设置的警告
然后将destroyFoo2强制转换为(析构函数),通过强制编译器将函数视为该类型来隐藏此警告
我想这突出了铸造的陷阱
我使用以下代码对此进行了检查:
typedef struct Foo
{
int a;
} Foo;
typedef void (*destructor)(const Foo* p, const void* context);
void destroyFoo1(const Foo* p, const void* context);
void destroyFoo1(const Foo* p, const void* context)
{
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p);
void destroyFoo2(const Foo* p)
{
free((void*)p);
}
int main(void)
{
//this is okay
destructor destructor1 = destroyFoo1;
//this triggers a warning
destructor destructor2 = destroyFoo2;
//This doesn't generate a warning
destructor destructor3 = (destructor)destroyFoo2;
}
好吧,我想我已经弄明白了,但这并不简单 首先,问题是
检查\u如果函数返回\u VOID\u和\u REQUIRE\u VOID\u指针
需要编译时比较两个签名:一个输入签名(从输入函数指针给出,例如destroyFoo1
)和一个基签名(即析构函数
类型的签名):如果我们实现这样做的方法,我们可以检查两个签名是否“符合”
我们通过利用C预处理器来实现这一点。其主要思想是,我们希望用作析构函数的每个函数都定义了一个宏CHECK_IF_FUNCTION_返回_VOID_和_REQUIRE_2_VOID_指针
也将是一个宏,它仅根据析构函数
的类型签名生成宏名称:如果CHECK_IF_FUNCTION_返回_VOID_和_REQUIRE_2_VOID_指针
中生成的宏名称存在,然后我们假设functionPointer与析构函数兼容,并对其进行强制转换。否则会抛出编译错误。因为我们需要一个宏定义来定义每个我们想用作析构函数的函数,这在庞大的代码库中可能是一个代价高昂的解决方案
注意:这个实现依赖于GCC(它使用了#
和#u Pragma
的变体,但我认为它也可以很容易地移植到其他一些编译器)
例如:
struct Foo {
int a;
} Foo;
void destroyFoo1(const Foo* p) {
free((void*)p);
}
int main() {
//...
Foo* object_to_remove_from_heap = //instance of foo
destructor d = destroyFoo1;
//somewhere else
d(object_to_remove_from_heap, context);
}
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context);
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context)
{
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p)
{
free((void*)p);
}
int main(void)
{
//this will work:
//FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr
//macro exist and is equal to 1
destructor destructor1 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo1);
//this raise a compile error:
//FUNCTION_POINTER_destructor_void_destroyFoo2_voidConstPtr_voidConstPtr
//does not exist (or exists but its value is not 1)
destructor destructor2 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo2);
}
宏值只是一个常量。重要的是具有定义良好的结构的宏的名称。你使用的习惯是不相关的,只要选择并坚持一个。在这里,我使用了以下约定:
//macro (1)
"FUNCTION_POINTER_" typdefName "_" returnType "_" functionName "_" typeparam1 "_" typeparam2 ...
现在,我们将定义一个宏来检查两个签名是否相同。为了帮助我们,我们正在使用。我们将使用项目中的一些宏,因此如果您不想依赖它,您可以自己实现这些宏:
#define CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(functionName) \
_ENSURE_FUNCTION_POINTER(1, destructor, void, functionName, voidConstPtr, voidConstPtr)
#define _ENSURE_FUNCTION_POINTER(valueToCheck, castTo, expectedReturnValue, functionName, ...) \
P99_IF_EQ(valueToCheck, _GET_FUNCTION_POINTER_MACRO(castTo, expectedReturnValue, functionName, ## __VA_ARGS__)) \
((castTo)(functionName)) \
(COMPILE_ERROR())
#define COMPILE_ERROR() _Pragma("GCC error \"function pointer casting error!\"")
宏的输入是要检查的(1)的宏值(即本例中的1
,函数宏的值),我们要检查的typedef
(castTo
),我们希望functionName
具有的返回类型,以及用户传递给的functionName
检查\u函数是否返回\u VOID\u和\u是否需要\u 2\u VOID\u指针
(例如,destroyFoo1
或destroyFoo2
)。变量是每个参数的类型。这些参数需要与(1)中的相同:我们编写voidConstPtr
,因为我们不能在宏名称中包含const void*
\u GET\u FUNCTION\u POINTER\u MACRO
生成与签名相关联的宏,我们希望functionName
具有:
#define _DEFINE_FUNCTION_POINTER_OP(CONTEXT, INDEX, CURRENT, NEXT) P99_PASTE(CURRENT, NEXT)
#define _DEFINE_FUNCTION_POINTER_FUNC(CONTEXT, CURRENT, INDEX) P99_PASTE(_, CURRENT)
#define _GET_FUNCTION_POINTER_MACRO(functionPointerType, returnValue, functionName, ...) \
P99_PASTE(FUNCTION_POINTER, _, functionPointerType, _, returnValue, _, functionName, P99_FOR(, P99_NARG(__VA_ARGS__), _DEFINE_FUNCTION_POINTER_OP, _DEFINE_FUNCTION_POINTER_FUNC, ## __VA_ARGS__))
//example
_GET_FUNCTION_POINTER_MACRO(destructor, void, destroyFoo2, voidConstPtr, voidConstPtr)
//it generates
FUNCTION_POINTER_destructor_void_destroyFoo2_voidConstPtr_voidConstPtr
例如:
struct Foo {
int a;
} Foo;
void destroyFoo1(const Foo* p) {
free((void*)p);
}
int main() {
//...
Foo* object_to_remove_from_heap = //instance of foo
destructor d = destroyFoo1;
//somewhere else
d(object_to_remove_from_heap, context);
}
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context);
#define FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr 1
void destroyFoo1(const Foo* p, const void* context)
{
free((void*)p);
if (*((int*)context) == 0) {
printf("hello world\n");
}
}
void destroyFoo2(const Foo* p)
{
free((void*)p);
}
int main(void)
{
//this will work:
//FUNCTION_POINTER_destructor_void_destroyFoo1_voidConstPtr_voidConstPtr
//macro exist and is equal to 1
destructor destructor1 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo1);
//this raise a compile error:
//FUNCTION_POINTER_destructor_void_destroyFoo2_voidConstPtr_voidConstPtr
//does not exist (or exists but its value is not 1)
destructor destructor2 = CHECK_IF_FUNCTION_RETURNS_VOID_AND_REQUIRE_2_VOID_POINTERS(destroyFoo2);
}
重要注意事项
实际上,宏名称中的voidConstPtr
甚至void
都只是字符串。即使将void
替换为helloWorld
,一切都会正常工作。他们只是按照惯例行事
理解的最后一点是中的P99\IF\u EQ
实现的条件:如果\u GET\u FUNCTION\u POINTER\u宏的输出是现有的宏,预处理器将自动用其值替换它,否则宏名称将保持不变;如果宏被替换为1