在C语言中存储和使用类型信息

在C语言中存储和使用类型信息,c,casting,null-pointer,C,Casting,Null Pointer,我来自Java,我试图用C实现一个双链表作为练习。我想做一些类似Java泛型的事情,将指针类型传递给列表初始化,该指针类型将用于强制转换列表无效指针,但我不确定这是否可行 我要寻找的是可以存储在列表结构中并用于从节点将*数据转换为正确类型的东西。我曾考虑使用双指针,但后来我需要将其声明为空指针,我也会遇到同样的问题 typedef struct node { void *data; struct node *next; struct node *previous; } n

我来自Java,我试图用C实现一个双链表作为练习。我想做一些类似Java泛型的事情,将指针类型传递给列表初始化,该指针类型将用于强制转换列表无效指针,但我不确定这是否可行

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

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;

<> C.没有办法做你想做的事情,没有办法在变量中存储一个类型,C没有一个模板系统,比如C++,它可以让你在预处理器中伪造它。
<>你可以定义自己的模板类宏,可以快速定义你的< C++ >代码>节点> <代码>列表> /java >结构,不管你需要什么类型,但是我认为,除非你真的需要一组只在它们存储的类型上不同的链表,否则一般不同意这种做法。

< P>不同于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 ... */
};
然后,您可以创建一个库,该库在
节点的无数据列表上运行

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结构表示法,现在大多数编译器都是这样做的。IMHO说,从长远来看,它增加了可读性。你可以肯定,在这个问题上,有些人不会,有些人也会同意我的观点。

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;
确保将它们设置为实际删除resp的函数。克隆要存储在容器中的类型的元素,然后在需要时使用它们,例如在删除函数中,可以执行以下操作

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

通常,使用以下特定函数

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);

对于学习者来说不太容易的方法是使用
\u Generic
。C11提供了
\u Generic
,允许在编译时根据类型对代码进行所需的引导

下面提供了保存/获取3种类型指针的基本代码。宏需要为每种新类型进行扩展<代码>\u Generic
不允许列出两种可能相同的类型,如
无符号*
大小*
。所以有一些限制

type\u id(X)
宏为3种类型创建枚举,可用于检查运行时问题,如
LIST\u 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

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

我不理解关于空指针的所有内容<代码>0
计算为任何类型的空指针,就像
(void*)0
。。。但是,对于代码注释中更清晰的问题:不是真的,您必须提出自己的解决方案(例如,对于您使用的类型,
enum
)。C中的类型信息一经编译就不再存在了,因此没有“Type”类型的对象。可以使用
void list\u put\u int(list*L,int*p){….xx.data=p;..}
int*list\u get\u int(list*L){….return xx.data;}
但是,没有“Type”类型的对象,也没有在运行时更改的强制转换,(除了可能的弗拉斯,这在这里也没有任何帮助。)代码可以使用\u Generic和宏来实现大部分目标。这有点复杂。@FelixPalmen你是对的,很抱歉我的大脑不是空的,而是空的,更新了问题C不提供任何类型安全性——你不是真的这么说吗?可能你是说你必须为类型Generic代码或其他类型代码牺牲一些类型安全性felixpalmen有些C编译器将所有数据指针都视为void*。我不会把这种东西称为“C编译器”你有没有这样一个例子来说明这种破坏行为,它允许隐式地将任何数据指针类型转换为任何其他类型?@FelixPalmen
A A;B*B;void*p=&A;B=p;
没有c98这样的东西,你可能指的是c89。套接字代码当然可以编译,但你必须非常小心使用别名指针,你可以轻松地调用UB wi当然,调用UB的代码也会编译。你应该试着“理解C”,而你声称C不提供任何类型安全是完全错误的。