拥有以下代码:
文件:types.h
typedef struct Struct_A_T
{
int A;
char B;
float C;
}Struct_A;
文件:code.c
#include "types.h"
void Function(const void *const ptr)
{
Struct_A localStruct = *((Struct_A *)ptr);
localStruct.A = 1000;
localStruct.B = 250;
localStruct.C = 128.485;
}
文件:main.c
#include "types.h"
void Function(const void *const ptr);
int main(void)
{
Struct_A MyStruct1 = {2, 5, 2.8};
float local = 24.785;
/* Correct call */
Function(&MyStruct1);
/* Incorrect call!!! */
Function(&local);
}
知道指向void的指针可以用作“通用”指针。如何在“Function”中检测到void指针中传递的类型是否正确,以避免文件main.c中最后一次调用引起的运行时错误?
答案 0 :(得分:1)
使用语言功能无法做到这一点。它只能手动完成。
我可以在代码的调试版本中使用以下技术
typedef struct Struct_A_T
{
int A;
char B;
float C;
#ifdef DEBUG
unsigned signature;
#endif /* DEBUG */
}Struct_A;
即。在调试配置中,我在结构中引入了一个额外的字段。该结构类型的每个对象都必须使用一些特定于此类型的预定“不可预测”签名值初始化该字段,例如
#define STRUCT_A_SIGNATURE 0x12345678
如果所有结构都以某种集中方式创建(如动态分配或由专用函数初始化),则很容易实现。如果没有这样的集中位置,这可能会更麻烦。但是,我们有时必须为安全付出代价。例如,在您的示例中将是
Struct_A MyStruct1 = {2, 5, 2.8, 0x12345678 };
BTW,指定的初始化器可能会使这种初始化更稳定,更容易阅读。
然后,为了将指针从void *
转换为特定类型,我使用以下转换宏
#ifdef DEBUG
#define TO_STRUCT_A(p)\
(assert((p) == NULL || ((Struct_A *)(p))->signature == STRUCT_A_SIGNATURE),\
(Struct_A *)(p))
#else /* DEBUG */
#define TO_STRUCT_A(p) ((Struct_A *)(p))
#endif /* DEBUG */
意味着你Function
内有
Struct_A localStruct = *TO_STRUCT_A(ptr);
如果指向错误类型的指针传递给Function
,那么概率很高就会触发断言失败。
当然,这一切都可以(并且应该)使用更通用的宏集来实现。
显然,这仅适用于结构类型,您可以在其中注入其他签名字段。这种方法的另一个潜在问题是,通过在调试版本中向结构中引入额外的字段,可能会导致调试和发布版本的行为发生分歧。
答案 1 :(得分:0)
你不能,这是void*
的主要缺点,你无法确定指向的是什么。你只需知道。
答案 2 :(得分:0)
您可以使用sizeof
来比较结构的大小与void*
分指向的大小。这不是一个充分但必要的条件,指向结构类型的指针传递给Function
。 BTW语言的属性可以检查变量和更广泛的元数据(包括类型),称为 reflection 。它有许多现代语言版本,最近在C ++ 11中包含了一些内容,但C仍然缺乏它。