没有联合的结构类型别名/标记联合

Kni*_*nug 6 c language-lawyer c11

对于两个(或更多)structS:BaseSub具有共同第一(未命名)struct,是安全的转换/从投BaseSub反之亦然?

struct Base{
    struct{
        int id;
        // ...
    };
    char data[]; // necessary?
}

struct Sub{
    struct{
        int id;
        // same '...'
    };
    // actual data
};
Run Code Online (Sandbox Code Playgroud)

这些功能是否保证安全且技术上正确?(另外:零长度char data[]成员是否必要且有用?)

struct Base * subToBase(struct Sub * s){
    return (struct Base*)s;
}

struct Sub * baseToSub(struct Base * b){
    if(b->id != SUB_ID){
        return NULL;
    }

    return (struct Sub*)b;
}
Run Code Online (Sandbox Code Playgroud)

编辑

我没有计划Base在内部进一步嵌套Sub,而是可以在以后添加其他子类型(直接在下面Base),而无需更改Base.我主要担心的是指针是否struct可以安全地在指针Base和任何子指针之间来回转换.对(C11)标准的参考将是最受欢迎的.

编辑v2

稍微改变了措辞以阻止OOP /继承讨论.我想要的是一个标记联合,没有union它,所以它可以在以后扩展.我没有计划进行额外的嵌套.需要其他子类型功能的子类型可以明确地执行此操作,而无需进行任何进一步的嵌套.


上下文

对于脚本解释器1,我制作了一个伪面向对象的 标记联合类型系统,没有union.它有一个(摘要) 通用基本类型Object与几个(特定)的子类型,如String,Number,List等,每个类型- struct具有以下无名struct作为第一构件:

#define OBJHEAD struct{    \
    int id;                \
    int line;              \
    int column;            \
}
Run Code Online (Sandbox Code Playgroud)

所述id识别对象的类型,line并且column应该(也)是不言自明.各种对象的简化实现:

typedef struct Object{
    OBJHEAD;

    char data[]; // necessary?
} Object;

typedef struct Number{
    OBJHEAD;

    int value; // only int for simplicity
} Number;

typedef struct String{
    OBJHEAD;

    size_t length;
    char * string;
} String;

typedef struct List{
    OBJHEAD;

    size_t size;
    Object * elements; // may be any kind and mix of objects
} List;

Object * Number_toObject(Number * num){
    return (Object*)num;
}

Number * Number_fromObject(Object * obj){
    if(obj->type != TYPE_NUMBER){
        return NULL;
    }

    return (Number*)obj;
}
Run Code Online (Sandbox Code Playgroud)

我知道,最优雅和技术上是正确的方式做,这将是使用enumidunion针对各种亚型.但我希望类型系统是可扩展的(通过某种形式的类型注册表),以便以后可以添加类型而不需要更改所有Object相关的代码.

后期/外部添加可以是:

typedef struct File{
    OBJHEAD;

    FILE * fp;
} File;
Run Code Online (Sandbox Code Playgroud)

无需改变Object.

这些转换是否保证安全?

(至于小的宏观滥用:OBJHEAD遗嘱当然会被广泛记录,因此额外的实施者将知道不使用哪些成员名称.想法不是隐藏标题,而是每次都保存它.)

LoP*_*TaL 1

这是正确的,因为它保证在结构的第一个成员上具有相同的对齐方式,因此您可以从一个结构转换为另一个结构。

尽管如此,实现行为的常见方法是“继承”基类:

//Base struct definition
typedef struct Base_{
    int id;
    // ...
    //char data[]; //This is not needed.
}Base;

//Subclass definition
typedef struct Sub_{
    Base base;  //Note: this is NOT a pointer
    // actual data
}Sub;
Run Code Online (Sandbox Code Playgroud)

所以现在,您可以将 Sub 结构强制转换为 Base 结构,或者只返回第一个成员,该成员已经是 Base 类型,因此不再需要强制转换。

需要注意的是:不要滥用宏。宏对于很多事情来说都很好,但滥用它们可能会导致代码难以阅读和维护。在这种情况下,宏很容易被替换为基本成员。

最后一句话,您的宏很容易出错,因为成员名称现在被隐藏了。最后,您可能会添加同名的新成员,并在不知道原因的情况下收到奇怪的错误。

当您进一步将层次结构扩展为子子类时,您最终将不得不编写所有基类宏,而如果您使用“继承”方法,则只需编写直接基类。

这些解决方案都没有真正解决您的问题:继承。您拥有的唯一真正的解决方案(首选)是更改为真正的面向对象语言。由于与 C 相似,最好的匹配是 C++,但也可以使用任何其他语言。

  • 如果您意识到宏的陷阱,那么宏并不是邪恶的。在 C 中,它们是定义 **真正** 符号常量和实现类似模板功能的方法。 (3认同)
  • @Kninnug:“我没有计划进行任何进一步的嵌套......”我希望每次我说这句话时我都能得到一分钱,并且我的计划发生了变化 - 有时在几个小时后。 (2认同)