struct Base{ struct{ int id; // ... }; char data[]; // necessary? } struct Sub{ struct{ int id; // same '...' }; // actual data };
这些功能是否保证安全且技术上正确? (另外:零长度char数据[]成员是否必要且有用?)
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; }
编辑
我没有计划在Sub中嵌套任何比Base更多的东西,而是可以在以后添加其他子类型(直接在Base下),而无需更改Base.我主要担心的是,是否可以在Base和任何sub之间来回安全地转换指向结构的指针.对(C11)标准的参考将是最受欢迎的.
编辑v2
稍微改变了措辞以阻止OOP /继承讨论.我想要的是一个标记联合,没有联合,所以它可以在以后扩展.我没有计划进行额外的嵌套.需要其他子类型功能的子类型可以明确地执行此操作,而无需进行任何进一步的嵌套.
上下文
对于脚本解释器1,我制作了一个伪对象的tagged-union类型系统,没有联合.它有一个(抽象)泛型基类型Object,有几个(特定的)子类型,如String,Number,List等.每个type-struct都有以下未命名的struct作为第一个成员:
#define OBJHEAD struct{ \ int id; \ int line; \ int column; \ }
id标识对象的类型,行和列应该(也)是不言自明的.各种对象的简化实现:
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; }
我知道最优雅和技术上正确的方法是使用enum作为id和各种子类型的union.但我希望类型系统是可扩展的(通过某种形式的类型注册表),以便以后可以添加类型而无需更改所有与Object相关的代码.
后期/外部添加可以是:
typedef struct File{ OBJHEAD; FILE * fp; } File;
无需更改Object.
这些转换是否保证安全?
(至于小的宏观滥用:OBJHEAD当然会被广泛记录,因此其他实施者将知道不使用哪些成员名称.想法不是隐藏标题,而是每次都保存它.)
解决方法
struct Base{ struct{ int id; }; char data[]; // necessary? } struct Sub{ struct{ int id; }; long long int value; };
…可能是实现允许Base对象在32位边界上对齐,但要求Sub对象在64位边界上对齐,或者甚至在更严格的边界上对齐.
这些都不受Base是否具有灵活阵列成员的影响.
另一个问题是,通过强制转换不同类型的指针值来取消引用一种类型的指针值是否安全是一个不同的问题.首先,C对实现如何选择布局结构的限制很少:成员必须按照声明的顺序布局,并且在第一个之前不得有任何填充,否则,实现具有自由统治.据我所知,在您的情况下,如果两个结构中的匿名结构成员具有多个成员,则不要求它们以相同的方式布局. (如果他们只有一个成员,那么为什么要使用一个非常大的结构呢?)假设Base.data与Sub中匿名结构后面的第一个元素的偏移量相同也是不安全的.
在实践中,取消引用subToBase()的结果可能没问题,你当然可以实现测试来验证它.此外,如果您有通过Sub *转换获得的Base *,那么将其转换回来的结果(例如通过baseToSub())保证与原始Sub *相同(C11 6.3.2.3/ 7再次).在这种情况下,转换为Base *并返回对将指针解除引用为Sub *的安全性没有影响.
另一方面,虽然我在标准中找不到它的引用但我不得不说baseToSub()在一般上下文中非常危险.如果实际上没有指向Sub的Base *被转换为Sub *(其本身是允许的),那么取消引用该指针以访问不与Base共享的成员是不安全的.特别是,根据上面的声明,如果引用的对象实际上是Base,那么声明的Base.data绝不会阻止((Sub *)really_a_Base_ptr) – >生成未定义行为的值.
要避免所有未定义和实现定义的行为,您需要一种避免转换并确保一致布局的方法.在这方面,@ LoPiTaL建议在子结构中嵌入一个类型化的Base结构是一种很好的方法.