I'm currently learning C by working on my first project.
It's a calculator with a parser that transforms input into an operator tree. These trees consist of nodes of different types: Operators (inner nodes), constants and variables. My first attempt was to create a single **struct node** and just put everything a Node *might have* in it -- which wastes a lot of memory because not all of the fields are used, depending on the actual type a Node has.

My solution:
I implemented three different structs for the three different Node types.
```C
typedef enum
{
    NTYPE_OPERATOR,
    NTYPE_CONSTANT,
    NTYPE_VARIABLE
} NodeType;

typedef NodeType* Node;

struct VariableNode_
{
    NodeType type;
    char var_name[];
};

struct ConstantNode_
{
    NodeType type;
    double const_value;
};

struct OperatorNode_
{
    NodeType type;
    Operator *op;
    size_t num_children;
    Node children[];
};
```

They all have a type as their first field -- a header to let me know which type a Node actually has. A Node is always a pointer to the heap, so I typedef'ed it to hide its pointer type. A node may be created and used by the following "constructors" and accessors:

```c
Node malloc_variable_node(char *var_name)
{
    VariableNode res = malloc(sizeof(struct VariableNode_) + (strlen(var_name) + 1) * sizeof(char));
    if (res == NULL) return NULL;
    res->type = NTYPE_VARIABLE;
    strcpy(res->var_name, var_name);
    return (Node)res;
}

Node malloc_constant_node(ConstantType value)
{
    ConstantNode res = malloc(sizeof(struct ConstantNode_));
    if (res == NULL) return NULL;
    res->type = NTYPE_CONSTANT;
    res->const_value = value;
    return (Node)res;
}

Node malloc_operator_node(Operator *op, size_t num_children)
{
    if (num_children > MAX_ARITY)
    {
        // Max. arity exceeded
        return NULL;
    }

    OperatorNode res = malloc(sizeof(struct OperatorNode_) + num_children * sizeof(Node));
    if (res == NULL) return NULL;
    
    for (size_t i = 0; i < num_children; i++)
    {
        res->children[i] = NULL;
    }

    res->type = NTYPE_OPERATOR;
    res->op = op;
    res->num_children = num_children;
    return (Node)res;
}

void free_tree(Node tree)
{
    if (tree == NULL) return;
    if (get_type(tree) == NTYPE_OPERATOR)
    {
        for (size_t i = 0; i < get_num_children(tree); i++)
        {
            free_tree(get_child(tree, i));
        }
    }
    free(tree);
}

NodeType get_type(Node node)
{
    return *node;
}

Operator *get_op(Node node)
{
    return ((OperatorNode)node)->op;
}

size_t get_num_children(Node node)
{
    return ((OperatorNode)node)->num_children;
}

Node get_child(Node node, size_t index)
{
    return ((OperatorNode)node)->children[index];
}

Node *get_child_addr(Node node, size_t index)
{
    return &((OperatorNode)node)->children[index];
}

void set_child(Node node, size_t index, Node child)
{
    ((OperatorNode)node)->children[index] = child;
}

char *get_var_name(Node node)
{
    return ((VariableNode)node)->var_name;
}

double get_const_value(Node node)
{
    return ((ConstantNode)node)->const_value;
}
```

Is this common practice? Would there be a better way to do it? Would it have been okay to just waste the space and use a single struct as I did before? This would almost always safe me a level of indirection when dealing with trees and transforming them because all Nodes would have the same size and could be recycled.
If you want to look at the full file(s): [node.h hosted at GitHub][1]

Thanks in advance :)

Edit: Changed ConstantType to double because the type was not defined in the excerpt I posted

  [1]: https://github.com/PhilippHochmann/Calculator/blob/master/src/parsing/node.h