在C语言中为相似但不同的数据结构组合通用API

在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'

我有两个结构:A包含A,b,c,d作为成员,以及b包含b,c,d作为成员。我有多个API可以通过AB传递

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()
,它将a
structA
作为参数,第二个
translate_structB()
将a
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中编写每个函数的两个版本,而您不想这样做

备选方案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