普通C中的类型安全通用数据结构?

时间:2010-06-14 17:44:48

标签: c generics data-structures code-generation

我做了比“普通的旧C”编程更多的C ++编程。在普通C编程时,我非常想念的一件事是类型安全的通用数据结构,它是通过模板在C ++中提供的。

为了具体起见,请考虑一般的单链表。在C ++中,定义自己的模板类,然后根据所需的类型实例化它是一件简单的事情。

在C中,我可以想到一些实现通用单链表的方法:

  1. 编写链表类型和支持程序一次,使用void指针绕过类型系统。
  2. 使用必要的类型名称等编写预处理器宏,以生成特定于类型的数据结构版本和支持程序。
  3. 使用更复杂的独立工具为您需要的类型生成代码。
  4. 我不喜欢选项1,因为它颠覆了类型系统,并且可能比特定类型的特定实现具有更差的性能。对于所有类型使用数据结构的统一表示,并向/从void指针进行转换,据我所知,需要一个专门针对元素类型的实现可以避免的间接。

    选项2不需要任何额外的工具,但感觉有些笨拙,并且在使用不当时可能会导致错误的编译错误。

    选项3可以提供比选项2更好的编译器错误消息,因为专用数据结构代码将以扩展形式存在,可以在编辑器中打开并由程序员检查(而不是由预处理器宏生成的代码)。但是,这个选项是最重量级的,一种“穷人的模板”。我之前使用过这种方法,使用一个简单的sed脚本来专门化一些C代码的“模板化”版本。

    我想用C而不是C ++编写我未来的“低级”项目,但是为每种特定类型重写通用数据结构的想法让我感到害怕。

    人们对此问题有什么经验? C中是否存在通用数据结构和算法的良好库,它们不与选项1一起使用(即向无效指针进行转换,这会牺牲类型安全性并增加间接级别)?

9 个答案:

答案 0 :(得分:22)

选项1是我看到的大多数通用容器的C实现所采用的方法。 Windows驱动程序工具包和Linux内核使用宏来允许容器的链接嵌入到结构中的任何位置,宏用于从指向链接字段的指针获取结构指针:

选项2是BSD的tree.h和queue.h容器实现所采用的方法:

我认为我不认为这些方法中的任何一种都是安全的。有用,但不是类型安全。

答案 1 :(得分:3)

选项1,使用void *或基于某些union的变体是大多数C程序使用的,并且它可以提供比C ++ / macro样式更好的性能,具有针对不同类型的多个实现,因为它具有更少的代码重复,因此更少的icache压力和更少的icache未命中。

答案 2 :(得分:2)

GLib中有一堆通用数据结构,http://www.gtk.org/

CCAN有很多有用的摘录,例如http://ccan.ozlabs.org/

答案 3 :(得分:1)

您的选项1是大多数旧时间程序员可以使用的选项,可能只需要少量2来减少重复键入,并且可能使用一些函数指针来调整风格多态性。

答案 4 :(得分:1)

选项1有一个常见的变体,它更有效,因为它使用联合将值存储在列表节点中,即没有额外的间接。这有一个缺点,即列表只接受某些类型的值,如果类型的大小不同,可能会浪费一些内存。

但是,如果你愿意打破严格的别名,可以通过使用灵活的数组成员来摆脱union。 C99示例代码:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct ll_node
{
    struct ll_node *next;
    long long data[]; // use `long long` for alignment
};

extern struct ll_node *ll_unshift(
    struct ll_node *head, size_t size, void *value);

extern void *ll_get(struct ll_node *head, size_t index);

#define ll_unshift_value(LIST, TYPE, ...) \
    ll_unshift((LIST), sizeof (TYPE), &(TYPE){ __VA_ARGS__ })

#define ll_get_value(LIST, INDEX, TYPE) \
    (*(TYPE *)ll_get((LIST), (INDEX)))

struct ll_node *ll_unshift(struct ll_node *head, size_t size, void *value)
{
    struct ll_node *node = malloc(sizeof *node + size);
    if(!node) assert(!"PANIC");

    memcpy(node->data, value, size);
    node->next = head;

    return node;
}

void *ll_get(struct ll_node *head, size_t index)
{
    struct ll_node *current = head;
    while(current && index--)
        current = current->next;
    return current ? current->data : NULL;
}

int main(void)
{
    struct ll_node *head = NULL;
    head = ll_unshift_value(head, int, 1);
    head = ll_unshift_value(head, int, 2);
    head = ll_unshift_value(head, int, 3);

    printf("%i\n", ll_get_value(head, 0, int));
    printf("%i\n", ll_get_value(head, 1, int));
    printf("%i\n", ll_get_value(head, 2, int));

    return 0;
}

答案 5 :(得分:1)

一个老问题,我知道,但是如果仍有兴趣:我今天正在尝试选项2)(预处理器宏),并提出了我将在下面粘贴的示例。确实有点笨重,但并不可怕。代码不是完全类型安全的,但包含健全性检查以提供合理的安全级别。在编写时编译错误消息与我在C ++模板发挥作用时所看到的相比是温和的。您可能最好开始阅读&#34; main&#34;中的示例使用代码。功能

#include <stdio.h>

#define LIST_ELEMENT(type) \
    struct \
    { \
        void *pvNext; \
        type value; \
    }

#define ASSERT_POINTER_TO_LIST_ELEMENT(type, pElement) \
    do { \
        (void)(&(pElement)->value  == (type *)&(pElement)->value); \
        (void)(sizeof(*(pElement)) == sizeof(LIST_ELEMENT(type))); \
    } while(0)

#define SET_POINTER_TO_LIST_ELEMENT(type, pDest, pSource) \
    do { \
        ASSERT_POINTER_TO_LIST_ELEMENT(type, pSource); \
        ASSERT_POINTER_TO_LIST_ELEMENT(type, pDest); \
        void **pvDest = (void **)&(pDest); \
        *pvDest = ((void *)(pSource)); \
    } while(0)

#define LINK_LIST_ELEMENT(type, pDest, pSource) \
    do { \
        ASSERT_POINTER_TO_LIST_ELEMENT(type, pSource); \
        ASSERT_POINTER_TO_LIST_ELEMENT(type, pDest); \
        (pDest)->pvNext = ((void *)(pSource)); \
    } while(0)

#define TERMINATE_LIST_AT_ELEMENT(type, pDest) \
    do { \
        ASSERT_POINTER_TO_LIST_ELEMENT(type, pDest); \
        (pDest)->pvNext = NULL; \
    } while(0)

#define ADVANCE_POINTER_TO_LIST_ELEMENT(type, pElement) \
    do { \
        ASSERT_POINTER_TO_LIST_ELEMENT(type, pElement); \
        void **pvElement = (void **)&(pElement); \
        *pvElement = (pElement)->pvNext; \
    } while(0)

typedef struct { int a; int b; } mytype;

int main(int argc, char **argv)
{
    LIST_ELEMENT(mytype) el1;
    LIST_ELEMENT(mytype) el2;
    LIST_ELEMENT(mytype) *pEl;
    el1.value.a = 1;
    el1.value.b = 2;
    el2.value.a = 3;
    el2.value.b = 4;
    LINK_LIST_ELEMENT(mytype, &el1, &el2);
    TERMINATE_LIST_AT_ELEMENT(mytype, &el2);
    printf("Testing.\n");
    SET_POINTER_TO_LIST_ELEMENT(mytype, pEl, &el1);
    if (pEl->value.a != 1)
        printf("pEl->value.a != 1: %d.\n", pEl->value.a);
    ADVANCE_POINTER_TO_LIST_ELEMENT(mytype, pEl);
    if (pEl->value.a != 3)
        printf("pEl->value.a != 3: %d.\n", pEl->value.a);
    ADVANCE_POINTER_TO_LIST_ELEMENT(mytype, pEl);
    if (pEl != NULL)
        printf("pEl != NULL.\n");
    printf("Done.\n");
    return 0;
}

答案 6 :(得分:1)

我使用void指针(void *)来表示使用structs和typedef定义的通用数据结构。下面我分享我正在进行的lib的实现。

通过这种实现,您可以考虑使用typedef定义的每个新类型,如伪类。这里,这个伪类是源代码(some_type_implementation.c)及其头文件(some_type_implementation.h)的集合。

在源代码中,您必须定义将呈现新类型的结构。注意&#34; node.c&#34;中的结构。源文件。在那里,我做了一个指向&#34; info&#34;的无效指针。属性附加伤害。这个指针可以携带任何类型的指针(我认为),但你需要支付的价格是struct(int类型)中的类型标识符,以及所有定义的每个类型的propper句柄的开关。所以,在node.h&#34;头文件,我定义了类型&#34; Node&#34; (只是为了避免每次都必须输入struct节点),而且我还必须定义常量&#34; EMPTY_NODE&#34;,&#34; COMPLEX_NODE&#34;和&#34; MATRIX_NODE&#34;。< / p>

您可以手动执行编译,使用&#34; gcc * .c -lm&#34;。

main.c源文件

#include <stdio.h>
#include <math.h>

#define PI M_PI

#include "complex.h"
#include "matrix.h"
#include "node.h" 


int main()
{
    //testCpx();
    //testMtx();
    testNode();

    return 0;
}

node.c源文件

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "node.h"
#include "complex.h"
#include "matrix.h"

#define PI M_PI


struct node
{
    int type;

    void* info;
};


Node* newNode(int type,void* info)
{
    Node* newNode = (Node*) malloc(sizeof(Node));

    newNode->type = type;

    if(info != NULL)
    {
        switch(type)
        {
            case COMPLEX_NODE:
                newNode->info = (Complex*) info;
            break;

            case MATRIX_NODE:
                newNode->info = (Matrix*) info;
            break;
        }
    }
    else
        newNode->info = NULL;

    return newNode;
}

int emptyInfoNode(Node* node)
{
    return (node->info == NULL);
}

void printNode(Node* node)
{
    if(emptyInfoNode(node))
    {
        printf("Type:%d\n",node->type);
        printf("Empty info\n");
    }
    else
    {
        switch(node->type)
        {
            case COMPLEX_NODE:
                printCpx(node->info);
            break;

            case MATRIX_NODE:
                printMtx(node->info);
            break;
        }
    }
}

void testNode()
{
    Node *node1,*node2, *node3;
    Complex *Z;
    Matrix *M;

    Z = mkCpx(POLAR,5,3*PI/4);

    M = newMtx(3,4,PI);

    node1 = newNode(COMPLEX_NODE,Z);
    node2 = newNode(MATRIX_NODE,M);
    node3 = newNode(EMPTY_NODE,NULL);



    printNode(node1);
    printNode(node2);
    printNode(node3);
}

node.h头文件

#define EMPTY_NODE   0
#define COMPLEX_NODE 1
#define MATRIX_NODE  2


typedef struct node Node;


Node* newNode(int type,void* info);
int emptyInfoNode(Node* node);
void printNode(Node* node);
void testNode();

matrix.c源文件

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "matrix.h"

struct matrix
{
    // Meta-information about the matrix 
    int rows;
    int cols;

    // The elements of the matrix, in the form of a vector 
    double** MTX;
};

Matrix* newMtx(int rows,int cols,double value)
{
    register int row , col;
    Matrix* M = (Matrix*)malloc(sizeof(Matrix));

    M->rows = rows;
    M->cols = cols;
    M->MTX = (double**) malloc(rows*sizeof(double*));

    for(row = 0; row < rows ; row++)
    {
        M->MTX[row] = (double*) malloc(cols*sizeof(double));

        for(col = 0; col < cols ; col++) 
            M->MTX[row][col] = value;
    }

    return M;
}

Matrix* mkMtx(int rows,int cols,double** MTX)
{   
    Matrix* M;
    if(MTX == NULL)
    {
        M = newMtx(rows,cols,0);
    }
    else
    {
        M = (Matrix*)malloc(sizeof(Matrix));
        M->rows = rows;
        M->cols = cols;
        M->MTX  = MTX;
    }
    return M;
}

double getElemMtx(Matrix* M , int row , int col)
{
    return M->MTX[row][col];
}

void printRowMtx(double* row,int cols)
{
    register int j;
    for(j = 0 ; j < cols ; j++) 
        printf("%g ",row[j]);           
}

void printMtx(Matrix* M)
{
    register int row = 0, col = 0;

    printf("\vSize\n");
    printf("\tRows:%d\n",M->rows);
    printf("\tCols:%d\n",M->cols);
    printf("\n");
    for(; row < M->rows ; row++)
    {
        printRowMtx(M->MTX[row],M->cols);
        printf("\n");
    }

    printf("\n");
}

void testMtx()
{
    Matrix* M = mkMtx(10,10,NULL);
    printMtx(M);
}

matrix.h头文件

typedef struct matrix Matrix;

Matrix* newMtx(int rows,int cols,double value);
Matrix* mkMatrix(int rows,int cols,double** MTX);
void print(Matrix* M);
double getMtx(Matrix* M , int row , int col);
void printRowMtx(double* row,int cols);
void printMtx(Matrix* M);
void testMtx();

complex.c源文件

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "complex.h"

struct complex
{
    int type;

    double a;
    double b;
};

Complex* mkCpx(int type,double a,double b)
{
    /** Doc - {{{
     * This function makes a new Complex number.
     * 
     * @params:
     * |-->type: Is an interger that denotes if the number is in
     * |         the analitic or in the polar form.
     * |         ANALITIC:0
     * |         POLAR   :1
     * |
     * |-->a: Is the real part if type = 0 and is the radius if 
     * |      type = 1
     * |
     * `-->b: Is the imaginary part if type = 0 and is the argument
     *        if type = 1
     * 
     * @return:
     *      Returns the new Complex number initialized with the values 
     *      passed
     *}}} */

    Complex* number = (Complex*)malloc(sizeof(Complex));

    number->type = type;
    number->a    = a;
    number->b    = b;

    return number;
}

void printCpx(Complex* number)
{
    switch(number->type)
    {
        case ANALITIC:
            printf("Re:%g | Im:%g\n",number->a,number->b);
        break;

        case POLAR:
            printf("Radius:%g | Arg:%g\n",number->a,number->b);
        break;
    }
}

void testCpx()
{
    Complex* Z = mkCpx(ANALITIC,3,2);
    printCpx(Z);
}

complex.h头文件

#define ANALITIC 0 
#define POLAR    1 

typedef struct complex Complex;

Complex* mkCpx(int type,double a,double b);
void printCpx(Complex* number);
void testCpx();

我希望我没有错过什么。

答案 7 :(得分:0)

  

我想用C而不是C ++编写我未来的“低级”项目......

为什么呢?您的目标是否缺少C ++编译器或C ++运行时?

答案 8 :(得分:0)

您可以签出https://github.com/clehner/ll.c

易于使用:

#include <stdio.h>
#include <string.h>
#include "ll.h"

int main()
{
   int *numbers = NULL;
   *( numbers = ll_new(numbers) ) = 100;
   *( numbers = ll_new(numbers) ) = 200;

   printf("num is %d\n", *numbers);
   numbers = ll_next(numbers);
   printf("num is %d\n", *numbers);

   typedef struct _s {
      char *word;
   } s;

   s *string = NULL;
   *( string = ll_new(string) ) = (s) {"a string"};
   *( string = ll_new(string) ) = (s) {"another string"};

   printf("string is %s\n", string->word);
   string = ll_next( string );
   printf("string is %s\n", string->word);

   return 0;
}

输出:

num is 200
num is 100
string is another string
string is a string