C.中的数据封装



我目前正在开发一个嵌入式系统,我在一块板上有一个组件,它出现了两次。我想要一个.c和一个.h文件用于该组件。

我有以下代码:

typedef struct {
    uint32_t pin_reset;
    uint32_t pin_drdy;
    uint32_t pin_start;
    volatile avr32_spi_t *spi_module;
    uint8_t cs_id;  
} ads1248_options_t;

这些都是硬件设置。我创建了这个结构的两个实例(每个部分一个)。

现在我需要在后台保留一个值数组。例如,我可以每秒读取该设备的值,并且我想保留最后100个值。我希望这些数据不能从我的组件的"外部"访问(只能通过我的组件中的特殊功能)。

我不确定如何在这里进行。我真的需要将数组作为结构的一部分吗?我想到的是做以下事情:

int32_t *adc_values; // <-- Add this to struct
int32_t *adc_value_buffer = malloc(sizeof(int32_t) * 100); // <-- Call in initialize function, this will never be freed on purpose

然而,我将能够从代码中的任何地方(也可以从组件外部)访问我不喜欢的int32_t指针。

这是唯一的方法吗?你知道更好的方法吗?

谢谢。

对于为微控制器编写硬件驱动程序的特定情况,请考虑这样做。

否则,请使用不透明/不完整类型。你会惊讶地发现,很少有C程序员知道如何真正实现自定义类型的100%私有封装。这就是为什么关于C缺乏被称为私有封装的OO特性的说法一直存在。这个神话源于缺乏C知识,没有其他原因。

事情就是这样发展的:

ads1248.h

typedef struct ads1248_options_t ads1248_options_t; // incomplete/opaque type
ads1248_options_t* ads1248_init (parameters); // a "constructor"
void ads1248_destroy (ads1248_options_t* ads); // a "destructor"

ads1248.c

#include "ads1248.h"
struct ads1248_options_t {
    uint32_t pin_reset;
    uint32_t pin_drdy;
    uint32_t pin_start;
    volatile avr32_spi_t *spi_module;
    uint8_t cs_id;  
};
ads1248_options_t* ads1248_init (parameters)
{
  ads1248_options_t* ads = malloc(sizeof(ads1248_options_t));
  // do things with ads based on parameters
  return ads;
}
void ads1248_destroy (ads1248_options_t* ads)
{
  free(ads);
}

main.c

#include "ads1248.h"
int main()
{
  ads1248_options_t* ads = ads1248_init(parameters);
  ...
  ads1248_destroy(ads);
}

现在main中的代码无法访问任何结构成员,所有成员都是100%私有的。它只能创建一个指向结构对象的指针,而不能创建它的实例。如果你熟悉的话,它的工作原理与C++中的抽象基类完全一样。唯一的区别是,您必须手动调用init/dedestroy函数,而不是使用真正的构造函数/析构函数。

C中的结构通常完全在标头中定义,尽管它们是完全不透明的(例如FILE),或者在文档中只指定了一些字段。

C缺少private来防止意外访问,但我认为这是一个小问题:如果规范中没有提到字段,为什么要有人尝试访问它?您是否曾意外访问过FILE的成员?(最好不要做像有一个已发布的成员foo和一个未发布的fooo这样的事情,因为它们很容易被一个小的打字错误访问。)有些人使用惯例,比如给它们起"不寻常"的名字,例如,在私人成员上加一个尾随下划线。

另一种方式是PIMPL习惯用法:将结构前向声明为不完整类型,并仅在实现文件中提供完整声明。这可能会使调试复杂化,并且由于内联的可能性较小和额外的间接性,可能会导致性能损失,尽管这可以通过链接时间优化来解决。两者的组合也是可能的,在标头中声明公共字段,同时声明指向包含私有字段的不完整结构类型的指针。

我希望这些数据不能从我的"外部"访问组件(仅通过我的组件中的特殊功能)。

你可以这样做(一个包含数据的大malloc):

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef struct {
    uint32_t pin_reset;
    uint32_t pin_drdy;
    uint32_t pin_start;
    volatile avr32_spi_t *spi_module;
    uint8_t cs_id;  
} ads1248_options_t;
void fn(ads1248_options_t *x)
{
    int32_t *values = (int32_t *)(x + 1);
    /* values are not accesible via a member of the struct */
    values[0] = 10;
    printf("%dn", values[0]);
}
int main(void)
{
    ads1248_options_t *x = malloc(sizeof(*x) + (sizeof(int32_t) * 100));
    fn(x);
    free(x);
    return 0;
}

您可以像这样将结构的一部分私有化。

object.h

struct object_public {
    uint32_t public_item1;
    uint32_t public_item2;
};

object.c

struct object {
    struct object_public public;
    uint32_t private_item1;
    uint32_t *private_ptr;
}

指向object的指针可以转换为指向object_public的指针,因为object_publicstruct object中的第一个项。因此,object.c之外的代码将通过指向object_public的指针引用该对象。而object.c中的代码通过指向object的指针引用该对象。只有object.c中的代码才能知道私有成员的情况。

程序不应该定义或分配实例object_public,因为该实例不会附加私有内容

将一个结构作为另一个结构中的第一项包含在内的技术实际上是在C中实现单一继承的一种方式。但我想我会放弃这个想法。

您可以:

  1. 使你的整个ads1248_options_t成为不透明类型(如其他答案中所讨论的)
  2. 仅使adc_values成员为不透明类型,如:

     // in the header(.h)
    typedef struct adc_values adc_values_t;
     // in the code (.c)
    struct adc_values { 
        int32_t *values;
    };
    
  3. 将值数组的静态数组"并行"到ads1248_options_t,并提供访问它们的函数。类似:

    // in the header (.h)
    int32_t get_adc_value(int id, int value_idx);
    // in the code (.c)
    static int32_t values[MAX_ADS][MAX_VALUES];
    // or
    static int32_t *values[MAX_ADS]; // malloc()-ate members somewhere
    int32_t get_adc_value(int id, int value_idx) {
        return values[id][value_idx]
    }
    

    如果用户不知道要使用的索引,请在ads1248_options_t中保留一个索引(id)。

  4. 您可以提供一些其他方式来"并行"分配值数组,而不是静态数组,但同样需要一种方式来识别哪个数组属于哪个ADC,其中id是最简单的解决方案。

相关内容

  • 没有找到相关文章

最新更新