2

我有两个正在使用的结构,它们的定义几乎相同。这些是在我无法修改的头文件中定义的。

typedef struct
{
    uint32_t    property1;
    uint32_t    property2;
} CarV1;

typedef struct
{
    uint32_t    property1;
    uint32_t    property2;
    /* V2 specific properties */
    uint32_t    property3;
    uint32_t    property4;
} CarV2;

在我的代码中,我在文件顶部初始化了 V2 结构,以涵盖我的所有基础:

static const carV2 my_car =  {
    .property1 =   value,
    .property2 =   value,
    /* V2 specific properties */
    .property3 =   value,
    .property4 =   value
};

稍后,我想检索已初始化的值并将它们复制到结构中,以便通过 void 指针从函数返回。我有时想要汽车的 V2 属性,有时需要 V1。如何在没有重复定义/初始化的情况下安全地 memcpy?我对C相当陌生,我的理解是这很丑陋,跟着我看这段代码的工程师不会批准。什么是干净的方法来做到这一点?

int get_properties(void *returned_car){
     int version = get_version();
     switch (version){
         case V1:
         {
             CarV1 *car = returned_car;
             memcpy(car, &my_car, sizeof(CarV1)); // is this safe? What's a better way?
         }
         case V2:
         {
             CarV2 *car = returned_car;
             memcpy(car, &my_car, sizeof(CarV2));
         }
    }
}
4

4 回答 4

4

是的,绝对可以按照您的要求进行操作。

您可以使用基本结构成员来实现继承,如下所示:

typedef struct
{
    uint32_t    property1;
    uint32_t    property2;
} CarV1;

typedef struct
{
    CarV1       base;
    /* V2 specific properties */
    uint32_t    property3;
    uint32_t    property4;
} CarV2;

在这种情况下,您将消除重复的定义。当然,在 type 的变量上CarV2*,你不能直接引用基类的字段——你必须做一个小的重定向,像这样:

cv2p->base.property1 = 0;

要向上转换为CarV1*,请执行以下操作:

CarV1* cv1p = &(cv2p->base);
c1vp->property1 = 0;

你已经写了memcpy(&car, &my_car, sizeof(CarV1))。这看起来像是一个错误,因为它复制了指针变量的数据(即结构的地址,而不是结构本身)。因为car已经是一个指针 ( CarV1*) 并且我假设它是my_car,所以您可能想要这样做:

memcpy(car, my_car, sizeof(CarV1));

如果my_carisCarV2*caris CarV1*(反之亦然),那么上面的代码保证符合 C 标准,因为 struct 的第一个成员始终处于零偏移量,因此,这两个成员的第一个sizeof(CarV1)字节的内存布局将是相同的。

不允许编译器以不同的方式对齐/填充该部分(我假设这是您关于优化的意思),因为您已明确将第一部分声明CarV2为.CarV1


由于在您的情况下,您会遇到无法更改的相同定义的结构,因此您可能会发现 C 标准定义了一个名为offsetof.

为了绝对确定您的内存布局,我建议您在程序的初始化阶段进行一系列检查,以验证所有常见属性是否offsetof(struct CarV1, property1)等于等:offsetof(struct CarV2, property1)

void validateAlignment(void)
{
    if (offsetof(CarV1, property1) != offsetof(CarV2, property1)) exit(-1);
    if (offsetof(CarV1, property2) != offsetof(CarV2, property2)) exit(-1);
    // and so on
}

这将停止程序继续运行,以防编译器对填充做了任何创造性的事情。

它也不会减慢程序的初始化速度,因为offsetof实际上是在编译时计算的。因此,在所有优化到位后,该void validateAlignment(void)函数应该被完全优化(因为静态分析会显示退出条件总是错误的)。

于 2013-05-15T05:28:20.353 回答
0

你写的几乎可以工作,除了 memcpy(&car, ... 你应该只有 memcpy (car, ...,但在这种情况下没有理由使用 memcpy。相反,你应该复制每个在单独的语句中的字段。

汽车->property1 = my_car.property1

(my_car 是不是指针?从代码片段中无法判断)

对于第二种情况,我认为您可以分配整个结构: *car = my_car

于 2013-05-15T03:24:00.997 回答
0

没有完美的解决方案,但一种方法是使用联合

typedef union car_union
{
  CarV1 v1;
  CarV2 v2;

} Car;

这样,当您执行 memcpy 时,大小不会有所不同 - 如果版本为 v1,则不会初始化 v2 的特定部分。

于 2013-05-16T08:08:18.463 回答
0

在 C 和 Objective-C 中,这在实践中很好。(理论上,编译器必须将包含两个结构的联合声明视为成员)。

在 C++(和 Objective-C++)中,该语言非常仔细地描述了何时安全,何时不安全。例如,如果您从

typedef struct {
    public: 
    ...

然后编译器可以自由地重新安排结构成员的位置。如果该结构不使用 C++ 功能,那么您是安全的。

于 2014-04-07T12:27:45.330 回答