开发者

C Variable Member List for structs, is this possible?

I have a question about structures having a "variable members list" similar to the "variable argument list" that we can define functions as having. I may sound stupid or completely off the line in terms of C language basics, but please correct me if I am wrong.

So can I have a C struct like this:

 
    struct Var_Members_Interface
    {
        int intMember;
        char *charMember;
        ... // is this possible?
    };

My idea is to have a c style interface that can be implemented by the classes but these classes can have additional members in this structure. However, they must have intMember and charMember.

Thanks in adv开发者_如何学Goance.


The closest approximation in C99 (but not C89) is to have a flexible array member at the end of the structure:

struct Var_Members_Interface
{
    int   intMember;
    char *charMember;
    Type  flexArrayMember[];
};

You can now dynamically allocate the structure with an array of the type Type at the end, and access the array:

struct Var_Members_Interface *vmi = malloc(sizeof(*vmi) + N * sizeof(Type));

vmi->flexArrayMember[i] = ...;

Note that this cannot be used in C++.

But that isn't a very close approximation to what you are after. What you are after cannot be done in C with a single structure type, and can only be approximated in C++ via inheritance - see other answers.


One trick that you can get away with - usually - in C uses multiple structure types and lots of casts:

struct VM_Base
{
    int   intMember;
    char *charMember;
};

struct VM_Variant1
{
    int   intMember;
    char *charMember;
    int   intArray[3];
};

struct VM_Variant2
{
    int   intMember;
    char *charMember;
    Type  typeMember;
};

struct VM_Variant3
{
    int     intMember;
    char   *charMember;
    double  doubleMember;
};

Now, with some sledgehammering casts, you can write functions which take 'struct VM_Base *' arguments, and pass in a pointer to any of the VM_VariantN types. The 'intMember' can probably be used to tell which of the variants you actually have. This is more or less what happens with the POSIX sockets functions. There are different types of socket address, and the structures have different lengths, but they have a common prefix, and the correct code ends up being called because the common prefix identifies the type of socket address. (The design is not elegant; but it was standard - a de facto standard from BSD sockets - before POSIX standardized it. And the BSD design pre-dates C89, let alone C99. Were it being designed now, from scratch, with no requirement for compatibility with existing code, it would be done differently.)

This technique is ugly as sin and requires casts galore to make it compile -- and great care to make it work correctly. You shouldn't bother with this sort of mess in C++.


You can't do anything like this with direct language support in C; but in C++, classes that extended your struct would inherit those data members and could add their own. So in C++, not only can you do this, but it's a normal mode of operation.


You first need to understand what a struct really is.

A struct in C is little more than a standard for interpreting bytes in memory.

To see what that means, let's use your struct:

struct Var_Members_Interface
{
    int intMember;
    char *charMember;
};

struct Var_Members_Interface instance; //An instance of the struct

What this means is, "I'll reserve some memory and call it instance, and I'll interpret the first few bytes to mean an integer, and the next few bytes to mean that the point to somewhere in memory."

Given this, it makes little sense to have "variable-member" structs, because a struct is just the layout specification for an existing block of memory -- and existing blocks don't have "variable" length.


You could do it the way the old X11 Xt widget library did it:

struct Var_Members_Interface {
    int intMember;
    char *charMember;
};
struct Other_Part {
    int extraInt;
    char *extraString;
}
struct Var_Other_Interface {
    struct Var_Members_Interface base;
    struct Other_Part other;
};

As long as you're careful with your allocations, alignment, and padding issues, then this will work:

struct Var_Other_Interface   *other      = create_other();
struct Var_Members_Interface *member     = (struct Var_Other_Interface *)other;
struct Var_Other_Interface   *back_again = (struct Var_Other_Interface)member;

And you can nest the structs as deep as needed to get a single inheritance hierarchy.

This sort of thing is not for the feint of heart: you have to be very careful with you allocations, structure nesting, etc.

Have a look at an old school Xt widget and you'll get the idea; Xt widgets were usually implemented in three files: a C source file, a public header with the function interface, and a private header to define the structure layout (this one would be needed for subclassing).

For example, the Ghostscript widget that I used to use in mgv looked like this:

typedef struct {
    /* Bunch of stuff. */
} GhostviewPart;

typedef struct _GhostviewRec {
        CorePart      core;
        GhostviewPart ghostview;
} GhostviewRec;

The CorePart was the standard Xt widget definition and the GhostviewRec was the actual widget itself.


Not exactly what you are looking for but you can create a void pointer within your struct that can be used to point to another struct where the new types are defined.

struct Var_Members_Interface
    {
        int intMember;
        char *charMember;
        void *otherMembers;
    };

Edit: The solution in this article might be a lot closer to what you are looking for.


You have two choices I think. You can emulate what C++ does but unfortunately, you have to see all the gory details. You define your common base struct and have that as a member of all your variant structs e.g.

struct VM_Base
{
    int   intMember;
    char *charMember;
};

struct VM_Variant1
{
    struct VM_Base base;
    int   foo;
};

struct VM_Variant2
{
    struct VM_Base base;
    char *charMember;
    double bar;
};

struct VM_Variant3
{
    struct VM_Base base;
    char   *charMember;
    char baz[10];
};

Pointers to any of the variant structs are also pointers to the base member of the variant struct so you can cast to the base member freely. Going back the other way is obviously more problematic, since you need a check to make sure you are casting to the right type.

You can do away with the casts by using union instead e.g.

struct VM_Variant1
{
    struct VM_Base base;
    int   foo;
};

struct VM_Variant2
{
    struct VM_Base base;
    char *charMember;
    double bar;
};

struct VM_Variant3
{
    struct VM_Base base;
    char   *charMember;
    char baz[10];
};

struct VM
{
    int   intMember;
    char *charMember;
    union
    {
        struct VM_Variant1 vm1;
        struct VM_Variant2 vm2;
        struct VM_Variant3 vm3;
    }
};

This second method obviates the need for type casts. You access the members like this:

double aDouble = aVMStruct.vm2.bar;

The three members of the union overlay each other in memory so the allocated block will only be the size of the largest of the three variants.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜