在C语言中为相似但不同的数据结构组合通用API
我有两个结构:A包含A,b,c,d作为成员,以及b包含b,c,d作为成员。我有多个API可以通过A或B传递在C语言中为相似但不同的数据结构组合通用API,c,structure,C,Structure,我有两个结构:A包含A,b,c,d作为成员,以及b包含b,c,d作为成员。我有多个API可以通过A或B传递 typedef struct { int a; int b; int c; int d; } A; typedef struct { int b; int c; int d; } B; Set_b(struct A, int); Set_c(struct A, int); Set_d(struct A, int); Set_b'
typedef struct {
int a;
int b;
int c;
int d;
} A;
typedef struct {
int b;
int c;
int d;
} B;
Set_b(struct A, int);
Set_c(struct A, int);
Set_d(struct A, int);
Set_b'(struct B, int);
Set_c'(struct B, int);
Set_d'(struct B, int);
在C.E.g.中实现相同的通用API最简单的方法是什么
Set_b(X, int);
Set_c(X, int);
Set_d(X, int);
我不允许使用
联合
,因为代码必须符合MISRA C。C11支持类型泛型表达式,它应该满足您的要求:
#define Set_b(X, y) _Generic((X), A: Set_b_A,\
B: Set_b_B \
)((X), (y))
然后对适当的类型执行
Set_b_A
和Set_b_b
。我会这样做。它是可调试的,不会生成太多的代码(memcpy将根据实际代码在任何优化级别上进行优化)
顺便说一句,在本例中,IMO的不安全版本与IMO的安全版本相同,它确实违反了严格的别名规则
typedef enum
{
TYPE_A,
TYPE_B,
TYPE_C
}Types;
struct a
{
int a;
int b;
int c;
int d;
};
struct b
{
int a;
int b;
int c;
};
struct c
{
int a;
int b;
};
void inline __attribute__((always_inline)) Set_a(void *ptr, Types type, int value)
{
struct a a;
struct b b;
struct c c;
switch(type)
{
case TYPE_A:
memcpy(&a, ptr, sizeof(a));
a.a = value;
memcpy(ptr, &a, sizeof(a));
break;
case TYPE_B:
memcpy(&b, ptr, sizeof(b));
b.a = value;
memcpy(ptr, &b, sizeof(b));
break;
case TYPE_C:
memcpy(&c, ptr, sizeof(c));
c.a = value;
memcpy(ptr, &c, sizeof(c));
break;
}
}
void inline __attribute__((always_inline)) Set_a_unsafe(void *ptr, Types type, int value)
{
struct a a;
struct b b;
struct c c;
switch(type)
{
case TYPE_A:
((struct a *)ptr) -> a = value;
break;
case TYPE_B:
((struct b *)ptr) -> a = value;
break;
case TYPE_C:
((struct c *)ptr) -> a = value;
break;
}
}
struct a x,y;
int main()
{
Set_a(&x, TYPE_A, 45);
Set_a_unsafe(&y, TYPE_B, 100);
printf("%d\n", x.a);
printf("%d\n", y.a);
}
您可以创建一个接口。比如说,
struct A {}; // As you have defined
void set_b_for_A(struct A, int) {} // function that works with A
// interface
struct helper {
void *ptr; // pointer to your structs variants (A, ...)
void (*set_b)(struct helper *, int); // helper to dispatch to correct worker
};
void set_b_helper_for_A(struct helper *pointer, int i) { // helper for worker A
struct A *s = (struct A *) pointer->ptr;
set_b_for_A(*s, i);
}
struct helper helper_A {/* A struct */, set_b_helper_for_A};
现在您的API
void set_b(struct helper *ptr, int i) {
ptr->set_b(ptr, i);
}
例如,你打电话:
set_b(&helper_A, 0);
对其他结构执行相同操作。可以使用两种不同的替代方法,但这取决于您拥有的和可以确定的内容与您不拥有的和不能修改或更改的内容。实际实现可能取决于
structA
与API一起使用的频率,而不是structB
。您确实没有提供足够的信息来确定建议的方法
看来你的帖子可以重述如下
有两个结构,structA
和structB
,它们有共同的成员。这些普通成员存储相同类型的数据,检查时,structB
的全部内容包含在structA
中,如下所示:
typedef struct {
int a;
int b; // beginning of portion that is same as structB below.
int c;
int d; // end of portion that is same as structB below.
int e;
} structA;
typedef struct {
int b; // same type of data as in b member of structA above
int c; // same type of data as in c member of structA above
int d; // same type of data as in d member of structA above
} structB;
例如,structA
描述了某个三维空间中的对象,其位置是在structA
成员b
、c
和d
中指定的x、y、z元组,structB
仅用于将位置存储为x、y、z元组
您有一个API可以处理structB
中的数据,由于structA
中有相同的数据,您面临的问题是必须有一个由一组重复函数组成的API,一个版本的API作为参数structB
,另一个版本作为参数structA
为了扩展3D空间中对象的具体示例,您有一个API,其中包含一个函数translate()
,该函数将坐标转换一定距离。因为根据MISRA C,您有两个不同的结构,所以您需要此函数的两个不同版本,translate_structA()
,它将astructA
作为参数,第二个translate_structB()
将astructB
作为参数
typedef struct {
int b; // same type of data as in b member of structA above
int c; // same type of data as in c member of structA above
int d; // same type of data as in d member of structA above
} structB;
typedef struct {
int a;
structB xyz; // replace the cloned members with an actual structB
int e;
} structA;
因此,您必须在API中编写每个函数的两个版本,而您不想这样做
备选方案1-用实际结构替换克隆成员
使用良好的软件工程,而不是在structA
中将此structB
数据类型作为克隆的成员集,而是将这些克隆的成员替换为structB
typedef struct {
int b; // same type of data as in b member of structA above
int c; // same type of data as in c member of structA above
int d; // same type of data as in d member of structA above
} structB;
typedef struct {
int a;
structB xyz; // replace the cloned members with an actual structB
int e;
} structA;
然后您编写的API只在structB
方面与structB
一起工作。在使用structA
的地方,只需在函数调用界面中使用xyz
成员即可
这种方法的好处是,如果添加了需要structB
的其他新数据类型,只需插入structB
成员,而不是克隆成员,使用structB
的API就可以用于新的数据类型
然而,为了采用这种方法,您需要拥有技术并能够进行这种更改。另一方面,这是我能想到的最直接、最简单、最可读的选择。它还应该具有相当好的运行时效率
关于下两个备选方案的说明
<>在进入下两个备选方案之前,你应该考虑这两个方面的基本缺陷。
如果在structA
中使用structB
未将structA
对structB
的依赖性指定为一种契约,则会引入一种逻辑或认知,其中有一个公共组件是源代码本身,而不是源代码派生的软件组件
维护变得很麻烦,因为现在两个结构必须一起更改。除非在源代码和结构定义中记录了这两个区域之间的联系,否则对代码不熟悉的程序员可能会错过这一点
如果引入了使用structB
数据的新数据类型,则需要再次执行克隆步骤,您只是在扩展复杂链接的表面
备选方案2-向接口对象编组/从接口对象编组
如果您不能控制结构,那么另一种选择是将数据编组到structA
并从structB
到structB
中,然后仅根据structB
编写API。然后,在structA
需要使用API的任何地方,都会有一个编组或转换,在其中挑选structA
中的特定数据来创建一个临时structB
,然后与函数一起使用。如果函数修改了structB
中的数据,则在删除临时数据之前,需要将数据从structB
复制回structA
或者您可以决定
int funcThing1 (structB thing);
int funcThing2 (structB thing);
int funcThing3 (structB thing);
int funcThingSet_structA (structA thing, int (*f)(structB thing)) {
structB temp = {0};
temp.b = thing.b;
temp.c = thing.c;
temp.d = thing.d;
return f (temp);
}
// and the above is used like
structA thingA;
// … code
i = funcThingSet_structA (thingA, funcThing1); // call funcThing1() with the structA data
i = funcThingSet_structA (thingA, funcThing2); // call funcThing2() with the structA data
i = funcThingSet_structA (thingA, funcThing3); // call funcThing3() with the structA data
int funcThing1 (structB *thing);
int funcThing2 (structB *thing);
int funcThing3 (structB *thing);
int funcThingSet_structA (structA *thing, int (*f)(structB *thing)) {
structB temp = {0};
int iRetVal = 0;
temp.b = thing->b;
temp.c = thing->c;
temp.d = thing->d;
iRetVal = f (&temp);
thing->b = temp.b;
thing->c = temp.c;
thing->d = temp.d;
return iRetVal;
}
// and the above is used like
structA thingA;
// … code
i = funcThingSet_structA (&thingA, funcThing1); // call funcThing1() with the structA data
i = funcThingSet_structA (&thingA, funcThing2); // call funcThing2() with the structA data
i = funcThingSet_structA (&thingA, funcThing3); // call funcThing3() with the structA data
structB *AssignAtoB (structB *pB, structA A) {
pB->b = A.b;
pB->c = A.c;
pB->d = A.d;
return pB;
}
structB ConvertAtoB (structA A) {
structB B = {0};
B.b = A.b;
B.c = A.c;
B.d = A.d;
return B;
}
void AssignBtoA (structA *pA, structB B) {
pA->b = B.b;
pA->c = B.c;
pA->d = B.d;
}
int funcThing1 (structB thing);
int funcThing2 (structB thing);
int funcThing3 (structB thing);
structA aThing;
// …. code
{ // create a local scope for this temporary bThing.
structB bThing = ConvertAtoB (aThing);
i = funcThing1(bThing);
// other operations on bThing and then finally.
AssignBtoA (&aThing, bThing);
}
structB funcThing1 (structB thing);
structB funcThing2 (structB thing);
structB funcThing3 (structB thing);
structA aThing;
// …. code
{ // create a local scope for this temporary bThing.
structB bThing = ConvertAtoB (aThing);
bThing = funcThing1(bThing);
bThing = funcThing2(bThing);
AssignBtoA (&aThing, bThing);
}
{ // create a local scope for this temporary bThing.
structB bThing = ConvertAtoB (aThing);
AssignBtoA (&aThing, funcThing2(funcThing1(bThing)));
}
AssignBtoA (&aThing, funcThing2(funcThing1(ConvertAtoB (aThing))))
structB MakeClone (structA thing) {
return *(structB *)&thing.b; // return a copy of the structB part of structA
}
structB *MakePointer (structA *pThing) {
return (structB *)&thing.b; // return a pointer to the structB part of structA
}
#define MAKEPOINTER(pThing) ((structB *)&((pThing)->b))
int funcThing (structB *pBthing);
// then in code want to use the above function with a structA
structA aThing = {0};
// do things with aThing then call our function that wants a structB
funcThing (MAKEPOINTER(&aThing));
funcThing ((structB *)&(aThing.b));
structA aThing = {0};
structB bThing = {0};
// somewhere in code we have
memcpy (&bThing, &aThing.b, sizeof(structB)); // assign the structB part of aThing to a structB
// more code to modify bThing then call our function
funcThing (&bThing);
memcpy (&aThing.b, &bThing, sizeof(structB)); // assign the structB back into the structB part of aThing