2012-02-22 44 views
5

我想在C中使用宏制作类型安全的通用链接列表。它应该与C++中的模板工作方式类似。例如,类型安全通用容器与宏

LIST(int) *list = LIST_CREATE(int); 

我的第一次尝试是为#define LIST(TYPE)定义struct _List_##TYPE {...}(I上面使用的宏)。但是,这并不奏效,因为每次我声明一个新列表时,结构都会被重新定义。我做这个纠正的问题:

/* You would first have to use this macro, which will define 
    the `struct _List_##TYPE`...        */ 
DEFINE_LIST(int); 

int main(void) 
{ 
    /* ... And this macro would just be an alias for the struct, it 
     wouldn't actually define it.         */ 
    LIST(int) *list = LIST_CREATE(int); 
    return 0; 
} 

/* This is how the macros look like */ 

#define DEFINE_LIST(TYPE) \ 
    struct _List_##TYPE  \ 
    {      \ 
     ...     \ 
    } 

#define LIST(TYPE)  \ 
    struct _List_##TYPE 

但另一个问题是,当我有使用DEFINE_LIST(int),例如多个文件,其中一些包括对方的话,仍然会有相同的多个定义结构。有没有办法让DEFINE_LIST检查结构是否已被定义?

/* one.h */ 
DEFINE_LIST(int); 

/* two.h */ 
#include "one.h" 
DEFINE_LIST(int); /* Error: already defined in one.h */ 
+0

语法看起来很酷:) – 2012-02-22 20:05:50

+0

[OpenGC3](https://github.com/kevin-dong-nai-jia/OpenGC3)就是你要找的东西。 ['ccxll(T)'](https://github.com/kevin-dong-nai-jia/OpenGC3/blob/master/doc/ccxll-list.pdf)甚至可以[嵌套](https:// gist.github.com/kevin-dong-nai-jia/af150182091f2871a92176b15965f814)! – 2017-04-05 06:27:07

回答

0

为什么不使用库? 我喜欢用GLib的,但我讨厌在我的代码中的空指针,以获得由GLib中提供的数据类型的类型安全的版本,我编写了一些非常简单的宏:

http://pastebin.com/Pc0KsadV

如果我想要一个符号*的列表(假设它是我前面定义的类型),我只需要到:

GLIST_INSTANCE(SymbolList, Symbol*, symbol_list); 

如果你不希望使用全库(这将是一种矫枉过正的)一个简单的链接列表,实现一个处理void *的列表并创建一些函数来封装并进行正确的类型转换。

+2

我不确定这是否符合类型安全。 – 2012-02-22 20:47:50

+0

这些宏对我来说似乎并不安全。为每个不同列表生成的函数将接受任何其他列表类型。 – ugoren 2012-02-22 21:16:03

+0

@ugoren,true,但他们不会接受不同的参数类型。例如,如果你调用'symbol_list_append(OtherTypeList,otherTypeObj);'它会失败。 – Victor 2012-02-22 21:29:33

0

如何创建一个list_template.h文件,然后为每个模板实例创建一个list_TYPE.h文件和一个list_TYPE.c文件。当然,这些可以带有适当的头部保护器。 然后,您只能包含模板标题,但确保将所有.c文件添加到编译和链接过程中,并且它应该可以工作。

这基本上就是C++自动为您做...复制的情况下...

1

你总是可以第二个参数添加到DEFINE_LIST宏,将让您“名称”列表中。例如:

#define DEFINE_LIST(TYPE, NAME)   \ 
struct _List_##TYPE_##NAME    \ 
{          \ 
    TYPE member_1;      \ 
    struct _List_##TYPE_##NAME* next; \ 
} 

然后,你可以简单地做:

DEFINE_LIST(int, my_list); 
//... more code that uses the "my_list" type 

你只需要限制自己同一列表“名”,当两个不同的头文件包括彼此不使用-RE,并且都使用宏DEFINE_LIST。在使用LIST_CREATE等时,您还必须按名称引用该列表。

将列表传递给您编写的函数时,您始终可以创建用户定义的“指定”版本的“通用”类型被投掷到。这不应该影响任何事情,因为struct中的实际信息保持不变,“name”标记仅仅将类型从声明而不是二进制的角度区分开来。例如,这里是需要存储int类型列表对象的功能:

#define GENERIC_LIST_PTR(TYPE) struct _generic_list_type_##TYPE* 
#define LIST_CAST_PTR(OBJ, TYPE) (GENERIC_LIST_PTR(TYPE))(OBJ) 

void function(GENERIC_LIST_PTR(INT) list) 
{ 
    //...use list as normal (i.e., access it's int data-member, etc.) 
} 

DEFINE_LIST(int, my_list); 

int main() 
{ 
    LIST(int, my_list)* list = LIST_CREATE(int, my_list); 
    function(LIST_CAST_PTR(list, int)); 

    //...more code 

    return 0; 
} 

我知道这并不一定是最方便的事情,但确实消除了命名问题,和你能控制的是什么版本struct _generic_list_type_XXX是在一些私人头文件中创建的,其他用户不会添加到它们(除非他们希望为他们自己的类型添加),但它将是一种将通用列表的声明和定义分开的机制类型来自实际的用户定义列表类型。

+0

但是接下来我会如何编写接受某种类型列表的函数呢?期望他们知道名单的“名字”听起来不太好。 – 2012-02-22 20:50:43

+0

因为“命名”版本不是重要部分,所以您始终可以使用演员表。我会给我的答案添加一个例子。 – Jason 2012-02-22 21:26:14

0

我真的怀疑你可以检查存在和定义(一个结构)在一个宏。把另一个#ifndef检查DEFINE_LIST(int)。这不是优雅的,而是做你想要的。

8

我在C++之前解决了这个问题,然后C++获得了模板,我仍然有 有代码。

您无法用宏 完全限定为头文件的方式定义真正通用的类型安全容器模板。标准预处理器 不提供“推送”和“弹出”您将要求的宏分配的方式,以便通过嵌套和顺序的 扩展的上下文保持其完整性。一旦你试图通过定义T的容器来吃自己的狗粮,你就会遇到嵌套的上下文。

的事情可以做,因为我们将看到,但作为@immortal表明,它需要 产生不同.h.c文件,你需要的T每个值。 可以,例如,定义一个宏的完全通用的列表的-T在 内嵌文件,比方说,list_type.inl,然后包括在 list_type.inl每对小集合式包装的 - list_float.hlist_float.c - 这 将分别定义和实现浮动列表容器。对于int列表,浮点列表列表,双向列表列表, 等,也是如此, 等等。

一个示意性的例子将使所有清楚。但首先要获得完整的 这个吃你自己的狗食的挑战。

将这样的二阶容器当作一个列表的东西。我们希望 能够通过设置T = list-of-thingummy为我们的宏列表 T解决方案进行实例化。但是,绝不会将其列为POD 数据类型。无论是我们自己的dogfood还是其他人的东西,它的 将成为一个抽象数据类型,它位于堆上,并通过typedef-ed指针类型表示为 其用户。或者至少在 的堆上有动态组件。无论如何,不​​是POD。

这意味着我们的T清单解决方案仅仅被告知 T = list-of-thingummy是不够的。还必须告知T是否需要非POD 复制构建和销毁,如果是的话如何复制构建和销毁 。在C而言,这意味着:

  • 复制建设:如何在未提交记忆的T-大小 地区创造一个给定的T的副本,给出了这样一个区域的地址。

  • 破坏:如何破坏给定地址处的T.

我们可以不先了解有关默认施工或施工从 非T的参数,我们可以合理地限制我们的列表的-T解决方案, 从用户提供的原件复制的对象的遏制。但我们 必须复制它们,我们必须处置我们的副本。除了T列表之外,假设我们希望为T集或T 1到T 2的映射 提供模板。这些按键排序的数据类型添加了另一个参数 我们必须插入T或T1的任何非POD值,即如何订购 密钥类型的任何两个对象。实际上,我们将需要 的任何关键数据类型的参数memcmp()不会做。

注意到之后,我们将坚持使用 原理图示例中更简单的T列表问题;为了进一步简化,我会忘记任何const API的合意 。

对于这一点,我们会想其他任何模板容器类型的一些令牌粘贴 宏,让我们方便组装的功能和类型, 加上可能其他工具宏标识符。这些都可以去一个头,说macro_kit.h, 如:

#ifndef MACRO_KIT_H 
#define MACRO_KIT_H 

/* macro_kit.h */ 

#define _CAT2(x,y) x##y 

// Concatenate 2 tokens x and y 
#define CAT2(x,y) _CAT2(x,y) 
// Concatenate 3 tokens x, y and z 
#define CAT3(x,y,z) CAT2(x,CAT2(y,z)) 

// Join 2 tokens x and y with '_' = x_y 
#define JOIN2(x,y) CAT3(x,_,y) 
// Join 3 tokens x, y and z with '_' = x_y_z 
#define JOIN3(x,y,z) JOIN2(x,JOIN2(y,z)) 
// Compute the memory footprint of n T's 
#define SPAN(n,T) ((n) * sizeof(T)) 

#endif 

我们的list_type.inl示意结构:

//! There is intentionally no idempotence guard on this file 
#include "macro_kit.h" 
#include <stddef.h> 

#ifndef INCLUDE_LIST_TYPE_INL 
#error This file should only be included from headers \ 
that define INCLUDE_LIST_TYPE_INL 
#endif 

#ifndef LIST_ELEMENT_TYPE 
#error Need a definition for LIST_ELEMENT_TYPE 
#endif 

/* list_type.inl 

    Defines and implements a generic list-of-T container 
    for T the current values of the macros: 

    - LIST_ELEMENT_TYPE: 
     - must have a definition = the datatype (or typedef alias) for \ 
     which a list container is required. 

    - LIST_ELEMENT_COPY_INITOR: 
     - If undefined, then LIST_ELEMENT_TYPE is assumed to be copy- 
     initializable by the assignment operator. Otherwise must be defined 
     as the name of a copy initialization function having a prototype of 
     the form: 

     LIST_ELEMENT_TYPE * copy_initor_name(LIST_ELEMENT_TYPE *pdest, 
              LIST_ELEMENT_TYPE *psrc); 

     that will attempt to copy the LIST_ELEMENT_TYPE at `psrc` into the 
     uncommitted memory at `pdest`, returning `pdest` on success and NULL 
     on failure. 

     N.B. This file itself defines the copy initializor for the list-type 
     that it generates. 

    - LIST_ELEMENT_DISPOSE 
     If undefined, then LIST_ELEMENT_TYPE is assumed to need no 
     destruction. Otherwise the name of a destructor function having a 
     protoype of the form: 

     void dtor_name(LIST_ELEMENT_TYPE pt*); 

     that appropriately destroys the LIST_ELEMENT_TYPE at `pt`. 

     N.B. This file itself defines the destructor for the list-type that 
     it generates. 
*/ 

/* Define the names of the list-type to generate, 
    e.g. list_int, list_float 
*/ 
#define LIST_TYPE JOIN2(list,LIST_ELEMENT_TYPE) 

/* Define the function-names of the LIST_TYPE API. 
    Each of the API macros LIST_XXXX generates a function name in 
    which LIST becomes the value of LIST_TYPE and XXXX becomes lowercase, 
    e.g list_int_new 
*/ 
#define LIST_NEW JOIN2(LIST_TYPE,new) 
#define LIST_NODE JOIN2(LIST_TYPE,node) 
#define LIST_DISPOSE JOIN2(LIST_TYPE,dispose) 
#define LIST_COPY_INIT JOIN2(LIST_TYPE,copy_init) 
#define LIST_COPY JOIN2(LIST_TYPE,copy) 
#define LIST_BEGIN JOIN2(LIST_TYPE,begin) 
#define LIST_END JOIN2(LIST_TYPE,end) 
#define LIST_SIZE JOIN2(LIST_TYPE,size) 
#define LIST_INSERT_BEFORE JOIN3(LIST_TYPE,insert,before) 
#define LIST_DELETE_BEFORE JOIN3(LIST_TYPE,delete,before) 
#define LIST_PUSH_BACK JOIN3(LIST_TYPE,push,back) 
#define LIST_PUSH_FRONT JOIN3(LIST_TYPE,push,front) 
#define LIST_POP_BACK JOIN3(LIST_TYPE,pop,back) 
#define LIST_POP_FRONT JOIN3(LIST_TYPE,pop,front) 
#define LIST_NODE_GET JOIN2(LIST_NODE,get) 
#define LIST_NODE_NEXT JOIN2(LIST_NODE,next) 
#define LIST_NODE_PREV JOIN2(LIST_NODE,prev) 

/* Define the name of the structure used to implement a LIST_TYPE. 
    This structure is not exposed to user code. 
*/ 
#define LIST_STRUCT JOIN2(LIST_TYPE,struct) 

/* Define the name of the structure used to implement a node of a LIST_TYPE. 
    This structure is not exposed to user code. 
*/ 
#define LIST_NODE_STRUCT JOIN2(LIST_NODE,struct) 

/* The LIST_TYPE API... */ 


// Define the abstract list type 
typedef struct LIST_STRUCT * LIST_TYPE; 

// Define the abstract list node type 
typedef struct LIST_NODE_STRUCT * LIST_NODE; 

/* Return a pointer to the LIST_ELEMENT_TYPE in a LIST_NODE `node`, 
    or NULL if `node` is null 
*/ 
extern LIST_ELEMENT_TYPE * LIST_NODE_GET(LIST_NODE node); 

/* Return the LIST_NODE successor of a LIST_NODE `node`, 
    or NULL if `node` is null. 
*/ 
extern LIST_NODE LIST_NODE_NEXT(LIST_NODE node); 

/* Return the LIST_NODE predecessor of a LIST_NODE `node`, 
    or NULL if `node` is null. 
*/ 
extern LIST_NODE LIST_NODE_PREV(LIST_NODE node); 


/* Create a new LIST_TYPE optionally initialized with elements copied from 
    `start` and until `end`. 

    If `end` is null it is assumed == `start` + 1. 

    If `start` is not NULL then elements will be appended to the 
    LIST_TYPE until `end` or until an element cannot be successfully copied. 
    The size of the LIST_TYPE will be the number of successfully copied 
    elements. 
*/ 
extern LIST_TYPE LIST_NEW(LIST_ELEMENT_TYPE *start, LIST_ELEMENT_TYPE *end); 

/* Dispose of a LIST_TYPE 
    If the pointer to LIST_TYPE `plist` is not null and addresses 
    a non-null LIST_TYPE then the LIST_TYPE it addresses is 
    destroyed and set NULL. 
*/ 
extern void LIST_DISPOSE(LIST_TYPE * plist); 

/* Copy the LIST_TYPE at `psrc` into the LIST_TYPE-sized region at `pdest`, 
    returning `pdest` on success, else NULL. 

    If copying is unsuccessful the LIST_TYPE-sized region at `pdest is 
    unchanged. 
*/ 
extern LIST_TYPE * LIST_COPY_INIT(LIST_TYPE *pdest, LIST_TYPE *psrc); 

/* Return a copy of the LIST_TYPE `src`, or NULL if `src` cannot be 
    successfully copied. 
*/ 
extern LIST_TYPE LIST_COPY(LIST_TYPE src); 

/* Return a LIST_NODE referring to the start of the 
    LIST_TYPE `list`, or NULL if `list` is null. 
*/ 
extern LIST_NODE LIST_BEGIN(LIST_TYPE list); 

/* Return a LIST_NODE referring to the end of the 
    LIST_TYPE `list`, or NULL if `list` is null. 
*/ 
extern LIST_NODE LIST_END(LIST_TYPE list); 

/* Return the number of LIST_ELEMENT_TYPEs in the LIST_TYPE `list` 
    or 0 if `list` is null. 
*/ 
extern size_t LIST_SIZE(LIST_TYPE list); 

/* Etc. etc. - extern prototypes for all API functions. 
    ... 
    ... 
*/ 

/* If LIST_IMPLEMENT is defined then the implementation of LIST_TYPE is 
    compiled, otherwise skipped. #define LIST_IMPLEMENT to include this 
    file in the .c file that implements LIST_TYPE. Leave it undefined 
    to include this file in the .h file that defines the LIST_TYPE API. 
*/ 
#ifdef LIST_IMPLEMENT 
// Implementation code now included. 

// Standard library #includes...? 

// The heap structure of a list node 
struct LIST_NODE_STRUCT { 
    struct LIST_NODE_STRUCT * _next; 
    struct LIST_NODE_STRUCT * _prev; 
    LIST_ELEMENT_TYPE _data[1]; 
}; 

// The heap structure of a LIST_TYPE 
struct LIST_STRUCT { 
    size_t _size; 
    struct LIST_NODE_STRUCT * _anchor; 
}; 

/* Etc. etc. - implementations for all API functions 
    ... 
    ... 
*/ 

/* Undefine LIST_IMPLEMENT whenever it was defined. 
    Should never fall through. 
*/ 
#undef LIST_IMPLEMENT 

#endif // LIST_IMPLEMENT 

/* Always undefine all the LIST_TYPE parameters. 
    Should never fall through. 
*/ 
#undef LIST_ELEMENT_TYPE 
#undef LIST_ELEMENT_COPY_INITOR 
#undef LIST_ELEMENT_DISPOSE 
/* Also undefine the "I really meant to include this" flag. */ 

#undef INCLUDE_LIST_TYPE_INL 

注意list_type.inl具有对复式没有纳入宏观后卫。您至少需要一些 - 至少是模板API - 每次查看时都会包含 。

如果您阅读文件顶部的注释,您可以猜到如何编码 包装页眉以导入int列表容器类型。

#ifndef LIST_INT_H 
#define LIST_INT_H 

/* list_int.h*/ 

#define LIST_ELEMENT_TYPE int 
#define INCLUDE_LIST_TYPE_INL 
#include "list_type.inl" 

#endif 

,同样你会如何编写包裹头部导入列表的一览的-INT 容器类型:

#ifndef LIST_LIST_INT_H 
#define LIST_LIST_INT_H 

/* list_list_int.h*/ 

#define LIST_ELEMENT_TYPE list_int 
#define LIST_ELEMENT_COPY_INIT list_int_copy_init 
#define LIST_ELEMENT_DISPOSE list_int_dispose 
#define INCLUDE_LIST_TYPE_INL 
#include "list_type.inl" 

#endif 

您的应用程序可以安全地包括这样的包装,例如

#include "list_int.h" 
#include "list_list_int.h" 

尽管在他们冲突的方式定义LIST_ELEMENT_TYPE因为 list_type.inl总是#undefs都当它与他们做过参数列表型 宏:查看文件的最后几行。

还要注意使用宏LIST_IMPLEMENT。如果在解析list_type.inl 时未定义,则只显示模板API;跳过模板实现 。如果定义了LIST_IMPLEMENT,则编译整个文件。因此,我们的 封装标题(通过未定义LIST_IMPLEMENT)仅导入列表类型 API。

相反,对于我们的包装源文件list_int.c,list_list_int.c,我们将 定义为LIST_IMPLEMENT。在那之后,没有什么可以做,但包括 相应标题:

/* list_int.c */ 
#define LIST_IMPLEMENT 
#include "list_int.h" 

和:

/* list_list_int.c*/ 
#include "list_int.h" 
#define LIST_IMPLEMENT 
#include "list_list_int.h" 
在你的应用

现在,没有列表模板的宏出现。你的包裹 头解析出“真正的代码”:(!)

#include "list_int.h" 
#include "list_list_int.h" 
// etc. 

int main(void) 
{ 
    int idata[10] = {1,2,3,4,5,6,7,8,9,10}; 
    //... 
    list_int lint = list_int_new(idata,idata + 10); 
    //... 
    list_list_int llint = list_list_int_new(&lint,0); 
    //... 
    list_int_dispose(&lint); 
    //... 
    list_list_int_dispose(&llint); 
    //... 
    exit(0); 
} 

要使用“C++模板库”这种方式,只有努力工作 是编写.inl文件每个集装箱式装备自己,你想要并非常彻底地测试它 。然后,您可能会生成一个对象文件 和标头,用于每个本地数据类型和容器类型的组合,用于 现成的链接,并根据需要为其他类型的文件打包.h.c。不用说,只要C++发布了模板,我就热衷于出汗 他们这样蒸发。但它可以这样完成,一般来说,如果由于某种原因,C是唯一的选择,则通常是 。

0

可以使用宏创建泛型和类型安全的容器。从计算理论的角度来看,由宏扩展生成的语言(代码)可以通过非确定性下推自动机来识别,这意味着它至多是上下文无关文法。前面的陈述使得我们的目标似乎无法实现,因为容器及其附属迭代器应该记住它们包含的类型,但这只能通过上下文敏感的语法来完成。但是,我们可以做一些技巧!

成功的关键在于编译过程中建立符号表。如果在编译器查询表格时可以识别变量的类型,并且不会发生不安全的类型转换,则将其视为类型安全的。因此,我们必须给每个struct一个特殊名称,因为如果在同一级别的作用域上声明了两个或更多结构体,那么结构体名称可能会发生冲突。最简单的方法是将当前行号附加到结构名称。自ANSI C(C89/C90)以来,标准C支持预定义的宏__LINE__和宏concatenation/stringification

然后,我们要做的就是将一些属性隐藏到我们上面定义的结构中。如果你想在运行时创建另一个列表记录,在结构中放置一个指向它自己的指针实际上可以解决问题。但是,这还不够。我们可能需要一个额外的变量来存储我们在运行时分配的记录数量。这有助于我们弄清楚当程序员明确销毁列表时如何释放内存。另外,我们可以利用在宏编程中广泛使用的__typeof__()扩展。

我是OpenGC3其目的是在建筑类型安全与宏通用集装箱,这里是这个库是如何工作的一个短期和简单的例子笔者:

ccxll(int) list;      // declare a list of type int 
ccxll_init(list);      // initialize the list record 

for (int cnt = 8; cnt-- > 0;)  // 
    ccxll_push_back(list, rand()); // insert "rand()" to the end 

ccxll_sort(list);      // sort with comparator: XLEQ 

CCXLL_INCR_AUTO(pnum, list)   // traverse the list forward: 
    printf("num = %d\n", *pnum);  // access elems through iters 

ccxll_free(list);      // destroy the list after use 

这是很相似STL的语法。声明list时确定列表的类型。我们不需要担心类型安全性,因为在对列表执行操作时没有不安全的类型转换。