在普通的C语言中使用类型安全的通用数据结构?

时间:2022-03-02 16:00:22

I have done far more C++ programming than "plain old C" programming. One thing I sorely miss when programming in plain C is type-safe generic data structures, which are provided in C++ via templates.

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

For sake of concreteness, consider a generic singly linked list. In C++, it is a simple matter to define your own template class, and then instantiate it for the types you need.

为了具体起见,考虑一个通用的单链表。在c++中,定义自己的模板类,然后为需要的类型实例化它是一件简单的事情。

In C, I can think of a few ways of implementing a generic singly linked list:

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

  1. Write the linked list type(s) and supporting procedures once, using void pointers to go around the type system.
  2. 一次编写链表类型和支持过程,使用void指针遍历类型系统。
  3. Write preprocessor macros taking the necessary type names, etc, to generate a type-specific version of the data structure and supporting procedures.
  4. 编写具有必要的类型名等的预处理器宏,以生成数据结构和支持过程的特定类型版本。
  5. Use a more sophisticated, stand-alone tool to generate the code for the types you need.
  6. 使用更复杂的独立工具生成所需类型的代码。

I don't like option 1, as it is subverts the type system, and would likely have worse performance than a specialized type-specific implementation. Using a uniform representation of the data structure for all types, and casting to/from void pointers, so far as I can see, necessitates an indirection that would be avoided by an implementation specialized for the element type.

我不喜欢选项1,因为它颠覆了类型系统,并且可能比特定类型的实现性能更差。使用所有类型的数据结构的统一表示,以及从空指针转换到/从空指针,就我所见,需要使用专门针对元素类型的实现避免的间接表示。

Option 2 doesn't require any extra tools, but it feels somewhat clunky, and could give bad compiler errors when used improperly.

选项2不需要任何额外的工具,但感觉有点笨拙,如果使用不当,可能会导致糟糕的编译错误。

Option 3 could give better compiler error messages than option 2, as the specialized data structure code would reside in expanded form that could be opened in an editor and inspected by the programmer (as opposed to code generated by preprocessor macros). However, this option is the most heavyweight, a sort of "poor-man's templates". I have used this approach before, using a simple sed script to specialize a "templated" version of some C code.

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

I would like to program my future "low-level" projects in C rather than C++, but have been frightened by the thought of rewriting common data structures for each specific type.

我希望用C而不是c++来编写我未来的“低级”项目,但我害怕为每种特定类型重写公共数据结构。

What experience do people have with this issue? Are there good libraries of generic data structures and algorithms in C that do not go with Option 1 (i.e. casting to and from void pointers, which sacrifices type safety and adds a level of indirection)?

人们在这个问题上有什么经验?在C中是否有良好的通用数据结构和算法库,它们不与选项1(例如,从空指针中进行转换,牺牲类型安全并添加一个间接的级别)?

10 个解决方案

#1


21  

Option 1 is the approach taken by most C implementations of generic containers that I see. The Windows driver kit and the Linux kernel use a macro to allow links for the containers to be embedded anywhere in a structure, with the macro used to obtain the structure pointer from a pointer to the link field:

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

Option 2 is the tack taken by BSD's tree.h and queue.h container implementation:

选项2是BSD树采取的策略。h和队列。h容器实现:

I don't think I'd consider either of these approaches type safe. Useful, but not type safe.

我不认为这两种方法都是安全的。有用,但类型不安全。

#2


17  

C has a different kind of beauty to it than C++, and type safety and being able to always see what everything is when tracing through code without involving casts in your debugger is typically not one of them.

与c++相比,C有一种不同的优点,类型安全,并且能够在跟踪代码时始终看到所有内容,而不涉及调试器中的强制类型转换,这通常不是其中之一。

C's beauty comes a lot from its lack of type safety, of working around the type system and at the raw level of bits and bytes. Because of that, there's certain things it can do more easily without fighting against the language like, say, variable-length structs, using the stack even for arrays whose sizes are determined at runtime, etc. It also tends to be a lot simpler to preserve ABI when you're working at this lower level.

C的美妙之处在于它缺乏类型安全性,在类型系统中工作,并且在原始的位和字节级别上工作。因为这样,它可以做有些事情没有斗争的更容易的语言,说,变长结构,使用堆栈即使对数组的大小决定在运行时,等。它也往往是简单得多保留ABI当你工作在较低水平。

So there's a different kind of aesthetic involved here as well as different challenges, and I'd recommend a shift in mindset when you work in C. To really appreciate it, I'd suggest doing things many people take for granted these days, like implementing your own memory allocator or device driver. When you're working at such a low level, you can't help but look at everything as memory layouts of bits and bytes as opposed to 'objects' with behaviors attached. Furthermore, there can come a point in such low-level bit/byte manipulation code where C becomes easier to comprehend than C++ code littered with reinterpret_casts, e.g.

所以这里有一种不同的美学,也有不同的挑战,我建议你在c工作时改变一下心态。要真正欣赏它,我建议你做一些现在很多人认为理所当然的事情,比如实现你自己的内存分配器或设备驱动程序。当你在这么低的水平上工作时,你不得不把每件事都看成是比特和字节的内存布局,而不是带有行为的“对象”。此外,在这种低级的位/字节操作代码中,也会出现一个点,在这里C变得更容易理解,而不是c++代码中充满了re解释性的转换。

As for your linked list example, I would suggest a non-intrusive version of a linked node (one that does not require storing list pointers into the element type, T, itself, allowing the linked list logic and representation to be decoupled from T itself), like so:

对于链表示例,我建议采用一个不干涉的链表节点版本(不需要将链表指针存储到元素类型T中,允许链表逻辑和表示与T本身分离),如下所示:

struct ListNode
{
    struct ListNode* prev;
    struct ListNode* next;
    MAX_ALIGN char element[1]; // Watch out for alignment here.
                               // see your compiler's specific info on 
                               // aligning data members.
};

Now we can create a list node like so:

现在我们可以创建一个列表节点,如下所示:

struct ListNode* list_new_node(int element_size)
{
    // Watch out for alignment here.
    return malloc_max_aligned(sizeof(struct ListNode) + element_size - 1);
}

// create a list node for 'struct Foo'
void foo_init(struct Foo*);
struct ListNode* foo_node = list_new_node(sizeof(struct Foo));
foo_init(foo_node->element);

To retrieve the element from the list as T*:

从列表中检索元素为T*:

T* element = list_node->element;

Since it's C, there's no type checking whatsoever when casting pointers in this way, and that will probably also give you an uneasy feeling if you're coming from a C++ background.

由于它是C语言,所以在以这种方式转换指针时不需要进行任何类型检查,如果您来自c++背景,这可能也会让您感到不安。

The tricky part here is to make sure that this member, element, is properly aligned for whatever type you want to store. When you can solve that problem as portably as you need it to be, you'll have a powerful solution for creating efficient memory layouts and allocators. Often this will have you just using max alignment for everything which might seem wasteful, but typically isn't if you are using appropriate data structures and allocators which aren't paying this overhead for numerous small elements on an individual basis.

这里棘手的部分是确保这个成员元素对于您想要存储的任何类型都是正确对齐的。当您可以根据需要以可移植性的方式解决这个问题时,您将拥有创建高效内存布局和分配程序的强大解决方案。这通常会让您对所有看起来可能很浪费的东西都使用最大对齐,但如果您使用适当的数据结构和分配器,而这些数据结构和分配器并不是为单个的许多小元素支付这种开销,那么通常不是这样的。

Now this solution still involves the type casting. There's little you can do about that short of having a separate version of code of this list node and the corresponding logic to work with it for every type, T, that you want to support (short of dynamic polymorphism). However, it does not involve an additional level of indirection as you might have thought was needed, and still allocates the entire list node and element in a single allocation.

现在这个解决方案仍然涉及类型转换。除了拥有这个列表节点的一个单独版本的代码和相应的逻辑来处理您想要支持的每个类型T(缺少动态多态性),您几乎没有什么可以做的。但是,它不涉及到您可能认为需要的额外的间接层,并且仍然在一个分配中分配整个列表节点和元素。

And I would recommend this simple way to achieve genericity in C in many cases. Simply replace T with a buffer that has a length matching sizeof(T) and aligned properly. If you have a reasonably portable and safe way you can generalize to ensure proper alignment, you'll have a very powerful way of working with memory in a way that often improves cache hits, reduces the frequency of heap allocations/deallocations, the amount of indirection required, build times, etc.

在很多情况下,我建议使用这种简单的方法来实现C中的泛型性。只需将T替换为具有长度匹配的sizeof(T)并正确对齐的缓冲区。如果您有一种合理的可移植性和安全的方法,可以进行归纳以确保适当的对齐,那么您将拥有一种非常强大的处理内存的方法,这种方法通常可以提高缓存命中率、减少堆分配/释放的频率、所需的间接数量、构建时间等。

If you need more automation like having list_new_node automatically initialize struct Foo, I would recommend creating a general type table struct that you can pass around which contains information like how big T is, a function pointer pointing to a function to create a default instance of T, another to copy T, clone T, destroy T, a comparator, etc. In C++, you can generate this table automatically using templates and built-in language concepts like copy constructors and destructors. C requires a bit more manual effort, but you can still reduce it the boilerplate a bit with macros.

如果你需要更多的自动化像list_new_node自动初始化结构体Foo,我建议创建一个通用类型表的结构,您可以通过包含信息,如T是多大,一个函数指针指向的函数来创建一个默认实例T,T另一个复制,克隆T,T摧毁,一个比较器,等等。在c++中,您可以使用模板自动生成此表和内置的语言概念,比如复制构造函数和析构函数。C需要更多的手工工作,但是您仍然可以使用宏将其简化为样板。

Another trick that can be useful if you go with a more macro-oriented code generation route is to cash in a prefix or suffix-based naming convention of identifiers. For example, CLONE(Type, ptr) could be defined to return Type##Clone(ptr), so CLONE(Foo, foo) could invoke FooClone(foo). This is kind of a cheat to get something akin to function overloading in C, and is useful when generating code in bulk (when CLONE is used to implement another macro) or even a bit of copying and pasting of boilerplate-type code to at least improve the uniformity of the boilerplate.

如果使用更宏观的代码生成路径,另一个有用的技巧是使用前缀或基于后缀的标识符命名约定。例如,克隆(Type, ptr)可以定义为返回类型##克隆(ptr),因此克隆(Foo, Foo)可以调用FooClone(Foo)。这是一种作弊来获得类似于函数重载在C语言中,批量生成代码时,有用(当克隆用于实现另一个宏),甚至有点boilerplate-type复制和粘贴的代码至少提高样板的均匀性。

#3


2  

Option 1, either using void * or some union based variant is what most C programs use, and it may give you BETTER performance than the C++/macro style of having multiple implementations for different types, as it has less code duplication, and thus less icache pressure and fewer icache misses.

选项1,使用void *或基于union的变体是大多数C程序所使用的,它可能比针对不同类型拥有多个实现的c++ /宏风格提供更好的性能,因为它具有更少的代码重复,因此icache压力更小,icache遗漏更少。

#4


2  

GLib is has a bunch of generic data structures in it, http://www.gtk.org/

GLib中有很多通用的数据结构,http://www.gtk.org/

CCAN has a bunch of useful snippets and such http://ccan.ozlabs.org/

CCAN有很多有用的代码片段,比如http://ccan.ozlabs.org/

#5


1  

Your option 1 is what most old time c programmers would go for, possibly salted with a little of 2 to cut down on the repetitive typing, and just maybe employing a few function pointers for a flavor of polymorphism.

您的选项1是大多数以前的c程序员所要做的,可能是加了一点2的盐,以减少重复的输入,并且可能只是使用一些函数指针来表示多态性的味道。

#6


1  

There's a common variation to option 1 which is more efficient as it uses unions to store the values in the list nodes, ie there's no additional indirection. This has the downside that the list only accepts values of certain types and potentially wastes some memory if the types are of different sizes.

选项1有一个常见的变体,它使用联合来将值存储在列表节点中,因此效率更高。它的缺点是,列表只接受某些类型的值,如果类型不同,可能会浪费一些内存。

However, it's possible to get rid of the union by using flexible array member instead if you're willing to break strict aliasing. C99 example code:

但是,如果您愿意打破严格的混叠,那么可以使用灵活的数组成员来摆脱联合。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;
}

#7


1  

An old question, I know, but in case it is still of interest: I was experimenting with option 2) (pre-processor macros) today, and came up with the example I will paste below. Slightly clunky indeed, but not terrible. The code is not fully type safe, but contains sanity checks to provide a reasonable level of safety. And dealing with the compiler error messages while writing it was mild compared to what I have seen when C++ templates came into play. You are probably best starting reading this at the example use code in the "main" function.

我知道这是一个老问题,但如果仍然感兴趣的话:我今天正在试验选项2(预处理器宏),我将在下面粘贴一个示例。确实有点笨拙,但并不可怕。该代码不是完全类型安全的,但是包含完整的检查,以提供合理的安全级别。在编写编译器错误消息时,与我在使用c++模板时看到的情况相比,处理这些错误消息是比较温和的。在“main”函数中使用代码的示例中,最好开始阅读本文。

#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;
}

#8


1  

I use void pointers (void*) to represent generic data structures defined with structs and typedefs. Below I share my implementation of a lib which I'm working on.

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

With this kind of implementation, you can think of each new type, defined with typedef, like a pseudo-class. Here, this pseudo-class is the set of the source code (some_type_implementation.c) and its header file (some_type_implementation.h).

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

In the source code, you have to define the struct that will present the new type. Note the struct in the "node.c" source file. There I made a void pointer to the "info" atribute. This pointer may carry any type of pointer (I think), but the price you have to pay is a type identifier inside the struct (int type), and all the switchs to make the propper handle of each type defined. So, in the node.h" header file, I defined the type "Node" (just to avoid have to type struct node every time), and also I had to define the constants "EMPTY_NODE", "COMPLEX_NODE", and "MATRIX_NODE".

在源代码中,必须定义表示新类型的结构。注意“节点”中的结构体。c源文件。在那里,我做了一个空指针指向“信息”中庭。这个指针可以携带任何类型的指针(我认为),但是您必须付出的代价是结构体(int类型)中的一个类型标识符,以及所有使每个类型的propper句柄被定义的开关。所以,在节点。h“头文件,我定义了类型“Node”(只是为了避免每次都必须键入struct Node),而且我还必须定义常量“EMPTY_NODE”、“COMPLEX_NODE”和“MATRIX_NODE”。

You can perform the compilation, by hand, with "gcc *.c -lm".

您可以使用“gcc *”手工执行编译。c - lm”。

main.c Source File

#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 Source File

#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 Header File

#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 Source File

#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 Header File

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 Source File

#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 Header File

#define ANALITIC 0 
#define POLAR    1 

typedef struct complex Complex;

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

I hope I hadn't missed nothing.

我希望我没有错过什么。

#9


0  

I would like to program my future "low-level" projects in C rather than C++...

我想用C而不是c++来编程我未来的“低级”项目。

Why? Does your target lack a C++ compiler or C++ runtime?

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

#10


0  

I am using option 2 for a couple of high performance collections, and it is extremely time-consuming working through the amount of macro logic needed to do anything truly compile-time generic and worth using. I am doing this purely for raw performance (games). An X-macros approach is used.

我正在为一些高性能的集合使用选项2,在完成任何真正的编译时泛型和值得使用的操作所需的宏逻辑时,这是非常耗时的。我这么做纯粹是为了单纯的表现(比赛)。使用X-macros方法。

A painful issue that constantly comes up with Option 2 is, "Assuming some finite number of options, such as 8/16/32/64 bit keys, do I make said value a constant and define several functions each with a different element of this set of values that constant can take on, or do I just make it a member variable?" The former means a less performant instruction cache since you have a lot of repeated functions with just one or two numbers different, while the latter means you have to reference allocated variables which in the worst case means a data cache miss. Since Option 1 is purely dynamic, you will make such values member variables without even thinking about it. This truly is micro-optimisation, though.

痛苦的问题,不断地提出了选项2,“假设一些有限数目的选项,比如8/16/32/64钥匙,我说一个常量值,定义几个函数都有一组不同的元素的值常数可以取,或者我只是一个成员变量吗?”前者意味着更少的高性能指令缓存,因为你有很多重复的功能只有一个或两个数字不同,而后者意味着你必须引用变量分配在最坏的情况下意味着数据缓存小姐因为选项1纯粹是动态的,你会做出这样值成员变量而不考虑它。这确实是微优化。

Also bear in mind the trade-off between returning pointers vs. values: the latter is most performant when the size of the data item is less than or equal to pointer size; whereas if the data item is larger, it is most likely better to return pointers than to force a copy of a large object by returning value.

还要记住返回指针与值之间的权衡:当数据项的大小小于或等于指针大小时,后者是最有效的;然而,如果数据项较大,那么返回指针很可能比通过返回值强制复制大对象要好。

I would strongly suggest going for Option 1 in any scenario where you are not 100% certain that collection performance will be your bottleneck. Even with my use of Option 2, my collections library supplies a "quick setup" which is like Option 1, i.e. use of void * values in my list and map. This is sufficient for 90+% of circumstances.

我强烈建议在任何情况下选择选项1,您不能100%确定收集性能将是您的瓶颈。即使使用了选项2,我的集合库也提供了类似选项1的“快速设置”,即在列表和映射中使用void *值。这在90%以上的情况下是足够的。

#1


21  

Option 1 is the approach taken by most C implementations of generic containers that I see. The Windows driver kit and the Linux kernel use a macro to allow links for the containers to be embedded anywhere in a structure, with the macro used to obtain the structure pointer from a pointer to the link field:

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

Option 2 is the tack taken by BSD's tree.h and queue.h container implementation:

选项2是BSD树采取的策略。h和队列。h容器实现:

I don't think I'd consider either of these approaches type safe. Useful, but not type safe.

我不认为这两种方法都是安全的。有用,但类型不安全。

#2


17  

C has a different kind of beauty to it than C++, and type safety and being able to always see what everything is when tracing through code without involving casts in your debugger is typically not one of them.

与c++相比,C有一种不同的优点,类型安全,并且能够在跟踪代码时始终看到所有内容,而不涉及调试器中的强制类型转换,这通常不是其中之一。

C's beauty comes a lot from its lack of type safety, of working around the type system and at the raw level of bits and bytes. Because of that, there's certain things it can do more easily without fighting against the language like, say, variable-length structs, using the stack even for arrays whose sizes are determined at runtime, etc. It also tends to be a lot simpler to preserve ABI when you're working at this lower level.

C的美妙之处在于它缺乏类型安全性,在类型系统中工作,并且在原始的位和字节级别上工作。因为这样,它可以做有些事情没有斗争的更容易的语言,说,变长结构,使用堆栈即使对数组的大小决定在运行时,等。它也往往是简单得多保留ABI当你工作在较低水平。

So there's a different kind of aesthetic involved here as well as different challenges, and I'd recommend a shift in mindset when you work in C. To really appreciate it, I'd suggest doing things many people take for granted these days, like implementing your own memory allocator or device driver. When you're working at such a low level, you can't help but look at everything as memory layouts of bits and bytes as opposed to 'objects' with behaviors attached. Furthermore, there can come a point in such low-level bit/byte manipulation code where C becomes easier to comprehend than C++ code littered with reinterpret_casts, e.g.

所以这里有一种不同的美学,也有不同的挑战,我建议你在c工作时改变一下心态。要真正欣赏它,我建议你做一些现在很多人认为理所当然的事情,比如实现你自己的内存分配器或设备驱动程序。当你在这么低的水平上工作时,你不得不把每件事都看成是比特和字节的内存布局,而不是带有行为的“对象”。此外,在这种低级的位/字节操作代码中,也会出现一个点,在这里C变得更容易理解,而不是c++代码中充满了re解释性的转换。

As for your linked list example, I would suggest a non-intrusive version of a linked node (one that does not require storing list pointers into the element type, T, itself, allowing the linked list logic and representation to be decoupled from T itself), like so:

对于链表示例,我建议采用一个不干涉的链表节点版本(不需要将链表指针存储到元素类型T中,允许链表逻辑和表示与T本身分离),如下所示:

struct ListNode
{
    struct ListNode* prev;
    struct ListNode* next;
    MAX_ALIGN char element[1]; // Watch out for alignment here.
                               // see your compiler's specific info on 
                               // aligning data members.
};

Now we can create a list node like so:

现在我们可以创建一个列表节点,如下所示:

struct ListNode* list_new_node(int element_size)
{
    // Watch out for alignment here.
    return malloc_max_aligned(sizeof(struct ListNode) + element_size - 1);
}

// create a list node for 'struct Foo'
void foo_init(struct Foo*);
struct ListNode* foo_node = list_new_node(sizeof(struct Foo));
foo_init(foo_node->element);

To retrieve the element from the list as T*:

从列表中检索元素为T*:

T* element = list_node->element;

Since it's C, there's no type checking whatsoever when casting pointers in this way, and that will probably also give you an uneasy feeling if you're coming from a C++ background.

由于它是C语言,所以在以这种方式转换指针时不需要进行任何类型检查,如果您来自c++背景,这可能也会让您感到不安。

The tricky part here is to make sure that this member, element, is properly aligned for whatever type you want to store. When you can solve that problem as portably as you need it to be, you'll have a powerful solution for creating efficient memory layouts and allocators. Often this will have you just using max alignment for everything which might seem wasteful, but typically isn't if you are using appropriate data structures and allocators which aren't paying this overhead for numerous small elements on an individual basis.

这里棘手的部分是确保这个成员元素对于您想要存储的任何类型都是正确对齐的。当您可以根据需要以可移植性的方式解决这个问题时,您将拥有创建高效内存布局和分配程序的强大解决方案。这通常会让您对所有看起来可能很浪费的东西都使用最大对齐,但如果您使用适当的数据结构和分配器,而这些数据结构和分配器并不是为单个的许多小元素支付这种开销,那么通常不是这样的。

Now this solution still involves the type casting. There's little you can do about that short of having a separate version of code of this list node and the corresponding logic to work with it for every type, T, that you want to support (short of dynamic polymorphism). However, it does not involve an additional level of indirection as you might have thought was needed, and still allocates the entire list node and element in a single allocation.

现在这个解决方案仍然涉及类型转换。除了拥有这个列表节点的一个单独版本的代码和相应的逻辑来处理您想要支持的每个类型T(缺少动态多态性),您几乎没有什么可以做的。但是,它不涉及到您可能认为需要的额外的间接层,并且仍然在一个分配中分配整个列表节点和元素。

And I would recommend this simple way to achieve genericity in C in many cases. Simply replace T with a buffer that has a length matching sizeof(T) and aligned properly. If you have a reasonably portable and safe way you can generalize to ensure proper alignment, you'll have a very powerful way of working with memory in a way that often improves cache hits, reduces the frequency of heap allocations/deallocations, the amount of indirection required, build times, etc.

在很多情况下,我建议使用这种简单的方法来实现C中的泛型性。只需将T替换为具有长度匹配的sizeof(T)并正确对齐的缓冲区。如果您有一种合理的可移植性和安全的方法,可以进行归纳以确保适当的对齐,那么您将拥有一种非常强大的处理内存的方法,这种方法通常可以提高缓存命中率、减少堆分配/释放的频率、所需的间接数量、构建时间等。

If you need more automation like having list_new_node automatically initialize struct Foo, I would recommend creating a general type table struct that you can pass around which contains information like how big T is, a function pointer pointing to a function to create a default instance of T, another to copy T, clone T, destroy T, a comparator, etc. In C++, you can generate this table automatically using templates and built-in language concepts like copy constructors and destructors. C requires a bit more manual effort, but you can still reduce it the boilerplate a bit with macros.

如果你需要更多的自动化像list_new_node自动初始化结构体Foo,我建议创建一个通用类型表的结构,您可以通过包含信息,如T是多大,一个函数指针指向的函数来创建一个默认实例T,T另一个复制,克隆T,T摧毁,一个比较器,等等。在c++中,您可以使用模板自动生成此表和内置的语言概念,比如复制构造函数和析构函数。C需要更多的手工工作,但是您仍然可以使用宏将其简化为样板。

Another trick that can be useful if you go with a more macro-oriented code generation route is to cash in a prefix or suffix-based naming convention of identifiers. For example, CLONE(Type, ptr) could be defined to return Type##Clone(ptr), so CLONE(Foo, foo) could invoke FooClone(foo). This is kind of a cheat to get something akin to function overloading in C, and is useful when generating code in bulk (when CLONE is used to implement another macro) or even a bit of copying and pasting of boilerplate-type code to at least improve the uniformity of the boilerplate.

如果使用更宏观的代码生成路径,另一个有用的技巧是使用前缀或基于后缀的标识符命名约定。例如,克隆(Type, ptr)可以定义为返回类型##克隆(ptr),因此克隆(Foo, Foo)可以调用FooClone(Foo)。这是一种作弊来获得类似于函数重载在C语言中,批量生成代码时,有用(当克隆用于实现另一个宏),甚至有点boilerplate-type复制和粘贴的代码至少提高样板的均匀性。

#3


2  

Option 1, either using void * or some union based variant is what most C programs use, and it may give you BETTER performance than the C++/macro style of having multiple implementations for different types, as it has less code duplication, and thus less icache pressure and fewer icache misses.

选项1,使用void *或基于union的变体是大多数C程序所使用的,它可能比针对不同类型拥有多个实现的c++ /宏风格提供更好的性能,因为它具有更少的代码重复,因此icache压力更小,icache遗漏更少。

#4


2  

GLib is has a bunch of generic data structures in it, http://www.gtk.org/

GLib中有很多通用的数据结构,http://www.gtk.org/

CCAN has a bunch of useful snippets and such http://ccan.ozlabs.org/

CCAN有很多有用的代码片段,比如http://ccan.ozlabs.org/

#5


1  

Your option 1 is what most old time c programmers would go for, possibly salted with a little of 2 to cut down on the repetitive typing, and just maybe employing a few function pointers for a flavor of polymorphism.

您的选项1是大多数以前的c程序员所要做的,可能是加了一点2的盐,以减少重复的输入,并且可能只是使用一些函数指针来表示多态性的味道。

#6


1  

There's a common variation to option 1 which is more efficient as it uses unions to store the values in the list nodes, ie there's no additional indirection. This has the downside that the list only accepts values of certain types and potentially wastes some memory if the types are of different sizes.

选项1有一个常见的变体,它使用联合来将值存储在列表节点中,因此效率更高。它的缺点是,列表只接受某些类型的值,如果类型不同,可能会浪费一些内存。

However, it's possible to get rid of the union by using flexible array member instead if you're willing to break strict aliasing. C99 example code:

但是,如果您愿意打破严格的混叠,那么可以使用灵活的数组成员来摆脱联合。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;
}

#7


1  

An old question, I know, but in case it is still of interest: I was experimenting with option 2) (pre-processor macros) today, and came up with the example I will paste below. Slightly clunky indeed, but not terrible. The code is not fully type safe, but contains sanity checks to provide a reasonable level of safety. And dealing with the compiler error messages while writing it was mild compared to what I have seen when C++ templates came into play. You are probably best starting reading this at the example use code in the "main" function.

我知道这是一个老问题,但如果仍然感兴趣的话:我今天正在试验选项2(预处理器宏),我将在下面粘贴一个示例。确实有点笨拙,但并不可怕。该代码不是完全类型安全的,但是包含完整的检查,以提供合理的安全级别。在编写编译器错误消息时,与我在使用c++模板时看到的情况相比,处理这些错误消息是比较温和的。在“main”函数中使用代码的示例中,最好开始阅读本文。

#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;
}

#8


1  

I use void pointers (void*) to represent generic data structures defined with structs and typedefs. Below I share my implementation of a lib which I'm working on.

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

With this kind of implementation, you can think of each new type, defined with typedef, like a pseudo-class. Here, this pseudo-class is the set of the source code (some_type_implementation.c) and its header file (some_type_implementation.h).

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

In the source code, you have to define the struct that will present the new type. Note the struct in the "node.c" source file. There I made a void pointer to the "info" atribute. This pointer may carry any type of pointer (I think), but the price you have to pay is a type identifier inside the struct (int type), and all the switchs to make the propper handle of each type defined. So, in the node.h" header file, I defined the type "Node" (just to avoid have to type struct node every time), and also I had to define the constants "EMPTY_NODE", "COMPLEX_NODE", and "MATRIX_NODE".

在源代码中,必须定义表示新类型的结构。注意“节点”中的结构体。c源文件。在那里,我做了一个空指针指向“信息”中庭。这个指针可以携带任何类型的指针(我认为),但是您必须付出的代价是结构体(int类型)中的一个类型标识符,以及所有使每个类型的propper句柄被定义的开关。所以,在节点。h“头文件,我定义了类型“Node”(只是为了避免每次都必须键入struct Node),而且我还必须定义常量“EMPTY_NODE”、“COMPLEX_NODE”和“MATRIX_NODE”。

You can perform the compilation, by hand, with "gcc *.c -lm".

您可以使用“gcc *”手工执行编译。c - lm”。

main.c Source File

#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 Source File

#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 Header File

#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 Source File

#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 Header File

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 Source File

#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 Header File

#define ANALITIC 0 
#define POLAR    1 

typedef struct complex Complex;

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

I hope I hadn't missed nothing.

我希望我没有错过什么。

#9


0  

I would like to program my future "low-level" projects in C rather than C++...

我想用C而不是c++来编程我未来的“低级”项目。

Why? Does your target lack a C++ compiler or C++ runtime?

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

#10


0  

I am using option 2 for a couple of high performance collections, and it is extremely time-consuming working through the amount of macro logic needed to do anything truly compile-time generic and worth using. I am doing this purely for raw performance (games). An X-macros approach is used.

我正在为一些高性能的集合使用选项2,在完成任何真正的编译时泛型和值得使用的操作所需的宏逻辑时,这是非常耗时的。我这么做纯粹是为了单纯的表现(比赛)。使用X-macros方法。

A painful issue that constantly comes up with Option 2 is, "Assuming some finite number of options, such as 8/16/32/64 bit keys, do I make said value a constant and define several functions each with a different element of this set of values that constant can take on, or do I just make it a member variable?" The former means a less performant instruction cache since you have a lot of repeated functions with just one or two numbers different, while the latter means you have to reference allocated variables which in the worst case means a data cache miss. Since Option 1 is purely dynamic, you will make such values member variables without even thinking about it. This truly is micro-optimisation, though.

痛苦的问题,不断地提出了选项2,“假设一些有限数目的选项,比如8/16/32/64钥匙,我说一个常量值,定义几个函数都有一组不同的元素的值常数可以取,或者我只是一个成员变量吗?”前者意味着更少的高性能指令缓存,因为你有很多重复的功能只有一个或两个数字不同,而后者意味着你必须引用变量分配在最坏的情况下意味着数据缓存小姐因为选项1纯粹是动态的,你会做出这样值成员变量而不考虑它。这确实是微优化。

Also bear in mind the trade-off between returning pointers vs. values: the latter is most performant when the size of the data item is less than or equal to pointer size; whereas if the data item is larger, it is most likely better to return pointers than to force a copy of a large object by returning value.

还要记住返回指针与值之间的权衡:当数据项的大小小于或等于指针大小时,后者是最有效的;然而,如果数据项较大,那么返回指针很可能比通过返回值强制复制大对象要好。

I would strongly suggest going for Option 1 in any scenario where you are not 100% certain that collection performance will be your bottleneck. Even with my use of Option 2, my collections library supplies a "quick setup" which is like Option 1, i.e. use of void * values in my list and map. This is sufficient for 90+% of circumstances.

我强烈建议在任何情况下选择选项1,您不能100%确定收集性能将是您的瓶颈。即使使用了选项2,我的集合库也提供了类似选项1的“快速设置”,即在列表和映射中使用void *值。这在90%以上的情况下是足够的。