【问题标题】:How to verify if a void pointer (void *) is one of two data types?如何验证 void 指针 (void *) 是否是两种数据类型之一?
【发布时间】:2020-02-05 09:32:32
【问题描述】:

我正在编写一个函数,我想接受 2 个types 参数。

  • string (char *)
  • structure,其中将有 n 个元素。

为了实现这一点,我正在考虑使用简单的void * 作为参数类型。但是我不知道如何安全地验证参数是一种类型还是另一种类型。

【问题讨论】:

  • 你不能!至少,您需要向函数添加第二个参数,它指示void* 指向的内容。
  • ...如果您无论如何都必须添加第二个参数,您也可以编写两个单独的函数func_strfunc_struct 并在编译时进行类型检查。
  • 是的,这就是为什么我在想是否可以仅在一个功能中使用
  • 你不能以一种安全便携的方式。如果您足够勇敢,您可以尝试使用启发式方法来尝试猜测内存的第一个字节是否看起来像您对字符的预期,但我不会称之为安全
  • 如果您只想为字符串和结构函数命名,您可以使用_Generic 宏。您还可以创建自我识别类型,例如使用tagged unions,这意味着您不能传递原始char *string。所有这些可能比它的价值更麻烦。

标签: c gcc types clang


【解决方案1】:

我建议您使用以下内容来包装您的论点:

#include <stdio.h>

struct WrappedArg{
    int Type;
    void* Pointer;
};
typedef struct WrappedArg WrappedArg;


struct Other_Structure{
    int x, y, z;
};
typedef struct Other_Structure Other_Structure;


void Our_Function(void* data){
    WrappedArg* translated_data = (WrappedArg*) data;

    if(translated_data->Type == 0){

        // print srting, if string passed
        printf("You passed string: %s\n", (char*) translated_data->Pointer);
        

    } else {

        // recreate structure_data from pointer
        Other_Structure* structure_data = (Other_Structure*)translated_data->Pointer;
        printf("You passed sructure with x, y, z: %d, %d, %d\n",
            structure_data->x, structure_data->y, structure_data->z);
        
    }
}


int main(){
    Other_Structure structure_data = {1,2,3};
    char* string_data = "Hi there!";

    WrappedArg arguments = {
        0, // type
        string_data
    };
    Our_Function((void*)&arguments);

    // OR

    arguments.Type = 1;
    arguments.Pointer = &structure_data;

    Our_Function((void*)&arguments);

    return 0;
}

【讨论】:

    【解决方案2】:

    void* 对于泛型编程有点不推荐使用,如今您应该使用它们的情况并不多。它们很危险,因为它们导致不存在的类型安全。正如您所指出的,您还会丢失类型信息,这意味着您必须在 enumvoid* 周围拖动一些麻烦。

    相反,您应该使用 C11 _Generic,它可以在编译时检查类型并添加类型安全性。示例:

    #include <stdio.h>
    
    typedef struct
    {
      int n;
    } s_t; // some struct
    
    void func_str (const char* str)
    {
      printf("Doing string stuff: %s\n", str);
    }
    
    void func_s (const s_t* s)
    {
      printf("Doing struct stuff: %d\n", s->n);
    }
    
    #define func(x) _Generic((x),              \
      char*: func_str, const char*: func_str,  \
      s_t*:  func_s,   const s_t*:  func_s)(x) \
    
    
    int main()
    {
      char str[] = "I'm a string";
      s_t s = { .n = 123 };
    
      func(str);
      func(&s); 
    }
    

    记得提供您希望支持的所有类型的合格 (const) 版本。


    如果您希望在调用者传递错误类型时出现更好的编译器错误,您可以添加静态断言:

    #define type_check(x) _Static_assert(_Generic((x), \
      char*:   1,  const char*: 1,  \
      s_t*:    1,  const s_t*:  1,  \
      default: 0), #x": incorrect type.")
    
    #define func(x) do{ type_check(x); _Generic((x),     \
      char*: func_str, const char*: func_str,            \
      s_t*:  func_s,   const s_t*:  func_s)(x); }while(0) 
    

    如果您尝试int x; func(x); 之类的操作,您将收到编译器消息"x: incorrect type"

    【讨论】:

      【解决方案3】:

      void*的翻译是
      “亲爱的编译器,这是一个指针,对此没有额外的信息。”。

      通常编译器比你(程序员)更了解,因为他获得的信息更早并且仍然记得,而你可能已经忘记了。
      但在这种特殊情况下,你知道得更好或需要知道得更好。在void* 的所有情况下,信息都可以通过其他方式获得,但仅限于“碰巧知道”的程序员。因此,程序员必须将信息提供给编译器——或者更好地提供给正在运行的程序,因为void* 的一个优点是信息可以在运行时更改。
      通常这是通过将信息通过附加参数提供给函数来完成的,有时是通过上下文,即程序“碰巧知道”(例如,对于每种可能的类型,都有一个单独的函数,无论调用哪个函数都暗示了该类型)。

      所以最后void* 不包含类型信息。
      许多程序员将此误解为“我不需要知道类型信息”。
      但恰恰相反,使用void*增加了程序员跟踪类型信息并将其适当地提供给程序/编译器的责任。

      【讨论】:

      • 另外,编译器实际上知道指向的数据类型是什么。因此,如果您跳入某个 void* 函数,强制转换为错误的类型,然后取消引用数据……那么所有未定义的行为都会被调用。
      猜你喜欢
      • 1970-01-01
      • 2013-05-14
      • 2016-11-23
      • 2021-08-23
      • 1970-01-01
      • 1970-01-01
      • 2015-03-09
      • 1970-01-01
      相关资源
      最近更新 更多