【问题标题】:Storing and using type information in C在 C 中存储和使用类型信息
【发布时间】:2017-06-21 19:03:49
【问题描述】:

我来自 Java,我正在尝试在 C 中实现一个双向链表作为练习。我想做一些类似于 Java 泛型的事情,我会将指针类型传递给列表初始化,并且此指针类型将用于强制转换列表 void 指针,但我不确定这是否可能?

我正在寻找的是可以存储在列表结构中并用于将 *data 从节点转换为正确类型的东西。我正在考虑使用双指针,但我需要将其声明为 void 指针,我也会遇到同样的问题。

typedef struct node {
    void *data;
    struct node *next;
    struct node *previous;
} node;

typedef struct list {
    node *head;
    node *tail;
   //??? is there any way to store the data type of *data? 
} list;

【问题讨论】:

  • 我不明白所有关于 空指针 的东西 ...0 评估为任何类型的空指针,(void *)0 也是如此 ...但是至于代码注释中更清晰的问题:并非如此,您必须提出自己的解决方案(例如,enum 用于您使用的类型)。 C 中的类型信息一旦编译就不再存在,因此没有“type”类型的对象。
  • 可以使用void list_put_int(list *L, int *p) { .... xx.data = p; ...}int *list_get_int(list *L) { .... return xx.data;} 但正如@Felix Palmen 所说,没有“类型”类型的对象,也没有在运行时发生变化的演员,(除了可能是VLAs.and反正在这里也无济于事。)
  • 代码可以使用带有宏的_Generic 来实现大部分目标。这有点令人费解。
  • @FelixPalmen 你说得对,对不起,我的大脑掉进了 null 而不是 void,更新了问题

标签: c casting null-pointer


【解决方案1】:

通常使用如下特定函数。

void    List_Put_int(list *L, int *i);
void    List_Put_double(list *L, double *d);
int *   List_Get_int(list *L);
double *List_Get_double(list *L);

对于学习者来说不太容易的方法是使用_Generic。 C11 提供了_Generic,它允许在编译时根据类型对代码进行控制。

下面提供了保存/获取 3 种类型指针的基本代码。宏需要针对每种新类型进行扩展。 _Generic 不允许列出可能与 unsigned *size_t * 相同的 2 种类型。所以有限制。

type_id(X) 宏为 3 种类型创建了一个枚举,可用于检查运行时问题,如下面的LIST_POP(L, &d);

typedef struct node {
  void *data;
  int type;
} node;

typedef struct list {
  node *head;
  node *tail;
} list;

node node_var;
void List_Push(list *l, void *p, int type) {
  // tbd code - simplistic use of global for illustration only
  node_var.data = p;
  node_var.type = type;
}

void *List_Pop(list *l, int type) {
  // tbd code
  assert(node_var.type == type);
  return node_var.data;
}

#define cast(X,ptr) _Generic((X), \
  double *: (double *) (ptr), \
  unsigned *: (unsigned *) (ptr), \
  int *: (int *) (ptr) \
  )

#define type_id(X) _Generic((X), \
  double *: 1, \
  unsigned *: 2, \
  int *: 3 \
  )

#define LIST_PUSH(L, data)  { List_Push((L),(data), type_id(data)); }
#define LIST_POP(L, dataptr) (*(dataptr)=cast(*dataptr, List_Pop((L), type_id(*dataptr))) )

使用示例和输出

int main() {
  list *L = 0; // tbd initialization
  int i = 42;
  printf("%p %d\n", (void*) &i, i);
  LIST_PUSH(L, &i);
  int *j;
  LIST_POP(L, &j);
  printf("%p %d\n", (void*) j, *j);
  double *d;
  LIST_POP(L, &d);
}

42
42
assertion error

【讨论】:

    【解决方案2】:

    没有办法在 C 中做你想做的事。没有办法将类型存储在变量中,而且 C 没有像 C++ 那样允许你在预处理器中伪造它的模板系统。

    您可以定义自己的类似模板的宏,这些宏可以快速为您需要的任何类型定义 nodelist 结构,但我认为除非您真的 em> 需要一大堆链表,它们只存储类型不同。

    【讨论】:

      【解决方案3】:

      C 没有任何运行时类型信息,也没有类型“Type”。一旦代码被编译,类型就没有意义了。因此,语言提供的问题无法解决您的问题。

      您希望类型在运行时可用的一个常见原因是您有一些代码可能会看到容器的不同实例,并且必须为存储在容器中的不同类型执行不同的操作。您可以使用enum 轻松解决这种情况,例如

      enum ElementType
      {
          ET_INT; // int
          ET_DOUBLE; // double
          ET_CAR; // struct Car
          // ...
       };
      

      并在此处枚举应该进入您的容器的任何类型。另一个原因是您的容器是否应该拥有存储在其中的对象的所有权,因此必须知道如何销毁它们(有时如何克隆它们)。对于这种情况,我建议使用函数指针:

      typedef void (*ElementDeleter)(void *element);
      typedef void *(*ElementCloner)(const void *element);
      

      然后扩展您的结构以包含这些:

      typedef struct list {
          node *head;
          node *tail;
          ElementDeleter deleter;
          ElementCloner cloner;
      } list;
      

      确保将它们设置为实际删除相应的函数。克隆要存储在容器中的类型的元素,然后在需要时使用它们,例如在删除功能中,您可以执行类似的操作

      myList->deleter(myNode->data);
      // delete the contained element without knowing its type
      

      【讨论】:

        【解决方案4】:

        创建枚举类型,它将根据该枚举存储数据类型并分配内存。这可以在 switch/case 构造中完成。

        【讨论】:

          【解决方案5】:

          与 Java 或 C++ 不同,C 不提供任何类型安全性。要简洁地回答您的问题,请以这种方式重新排列您的节点类型:

          struct node {
             node* prev;  /* put these at front */
             node* next;
             /* no data here */
          };
          

          然后您可以单独声明携带任何数据的节点

          struct data_node {.
              data_node *prev;            // keep these two data members at the front 
              data_node *next;            // and in the same order as in struct list.
          
              // you can add more data members here.
          };
          /* OR... */
          enter code here
          struct data_node2 {
            node node_data;    /* WANING: this may look a bit safer, but is _only_ if placed at the front.
            /*  more data ... */
          };
          

          然后您可以创建一个库,该库对nodes 的无数据列表进行操作。

          void list_add(list* l, node* n);
          void list_remove(list* l, node* n);
          /* etc... */
          

          通过强制转换,使用这个“通用列表”api 对您的列表进行操作

          您可以在列表声明中包含某种类型的信息,因为 C 不提供有意义的类型保护。

          struct data_list
          {
            data_node* head;    /* this makes intent clear. */
            data_node* tail;
          };
          
          struct data2_list
          {
            data_node2* head;
            data_node2* tail;
          };
          
          /* ... */
          
          data_node* my_data_node = malloc(sizeof(data_node));
          data_node2* my_data_node2 = malloc(sizeof(data_node2));
          
          /* ... */
          
          list_add((list*)&my_list, (node*)my_data_node); 
          list_add((list*)&my_list2, &(my_data_node2->node_data)); 
          
          /* warning above is because one could write this */
          list_add((list*)&my_list2, (node*)my_data_node2); 
          
          
          /* etc... */
          

          这两种技术生成相同的目标代码,所以你选择哪一种完全取决于你。

          顺便说一句,如果您的编译器允许,请避免使用 typedef struct 表示法,如今大多数编译器都这样做。从长远来看,它增加了可读性,恕我直言。不过,您可以肯定有些人不会,有些人会同意我在这个问题上的看法。

          【讨论】:

          • C 不提供任何类型安全性——你真的不是这个意思吗?可能你的意思是你必须为类型通用代码或类似的东西牺牲一些类型安全。
          • @felixpalmen 一些 C 编译器将所有数据指针视为 void*。
          • 我不会将这种东西称为“C 编译器”。对于这种破坏行为,您是否有一个示例,可以将任何数据指针类型隐式转换为任何其他类型?
          • @FelixPalmen A a; B* b; void* p = &a; b=p;
          • 没有c98这样的东西,你的意思可能是c89。套接字代码当然可以编译,但是您必须非常小心地使用别名指针,您可以轻松在这种混乱情况下调用 UB。当然,调用 UB 的代码也会编译。 应该尝试“理解C”,而你声称C不提供任何类型安全是完全错误的。
          猜你喜欢
          • 1970-01-01
          • 2022-08-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-18
          • 1970-01-01
          相关资源
          最近更新 更多