【问题标题】:Data encapsulation in CC中的数据封装
【发布时间】:2015-03-18 10:14:50
【问题描述】:

我目前正在研究嵌入式系统,并且我在板上有一个组件,它出现了两次。我想为组件创建一个 .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 指针。

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

谢谢。

【问题讨论】:

  • 标准警告:请do not cast malloc()C 中的家人的返回值。
  • @JoachimPileborg 为什么无效?为什么不是不完整的结构类型?
  • 如果你使结构不透明,那么只需使用特殊函数来设置结构中的字段。
  • 使用不透明的指针来封装你的struct,比如explained here
  • @TomL。那你为什么不总是为 4 个 IC 预留空间,因为你的程序必须始终能够处理最坏的情况?

标签: c struct embedded malloc


【解决方案1】:

对于为微控制器编写硬件驱动程序的具体情况,这似乎是,请考虑doing like this

否则,请使用不透明/不完整类型。您会惊讶地发现知道如何真正实现自定义类型的 100% 私有封装的 C 程序员数量之多令人震惊。这就是为什么有一些关于 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/destroy 函数,而不是使用真正的构造函数/析构函数。

【讨论】:

  • 如果你尝试访问私有成员,你会得到一个编译器错误。良好的反应伦德林。
【解决方案2】:

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

C 缺少private 以防止意外访问,但我认为这是一个小问题:如果规范中未提及某个字段,为什么要尝试访问它?您是否曾经意外访问过FILE 的成员? (最好不要做诸如拥有已发布成员 foo 和未发布的 fooo 之类的事情,这很容易被一个小错字访问。)有些人使用约定,比如给他们“不寻常”的名字,例如,私人成员的尾随下划线。

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

【讨论】:

  • C 使用不完整类型进行私有封装。这与完整的私有课程完全相同。
  • @Lundin:有些人使用它,有些人不使用。我提到了两种方式。并且 pimpling 与 C++ private 的不同之处在于,即使对于编译器(和调试器),成员也是不可见的,因此并不完全相同。
  • @Lundin:而 C++ private 仍然允许在堆栈上声明此类类型的实例,这对于 pimpling 也是不可能的。
  • 不,您可以使用诸如alloca 之类的函数在堆栈上分配它,尽管这是一个非标准函数。另一种方法是分配一个私有静态内存池,这是您通常在无法使用堆的嵌入式系统上执行的操作。
  • 我同意mafso。问题是关于深度嵌入式系统(Atmel)。任何使用该库的人都会在每次包含“atmegaxx.h”时将所有寄存器符号注入到他们的代码中,因此试图在库中隐藏一些数据结构成员确实是一个小问题——正如他在回答中所说的那样。当每个人不可避免地需要修改库或调试它时,将其“公开”并带有有用的注释将有助于每个人。
【解决方案3】:

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

你可以这样做(一个大的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("%d\n", values[0]);
}

int main(void)
{
    ads1248_options_t *x = malloc(sizeof(*x) + (sizeof(int32_t) * 100));

    fn(x);
    free(x);
    return 0;
}

【讨论】:

  • 这难道不是与我想要达到的目标相反吗?我的主要功能是不需要任何基础数据知识的功能。
  • 这不会提供私有封装,因为整个程序都知道结构声明。需要使用 opaque/incomplete 类型来实现私有封装。
  • @Lundin,一个不透明的类型隐藏了结构的所有成员,我不知道这是否是OP想要的。
【解决方案4】:

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

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 中实现单继承的方法。我不记得曾经像这样使用它进行封装。但我想我会把这个想法扔掉。

【讨论】:

    【解决方案5】:

    你可以:

    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. 1234563最简单的解决方案。

    【讨论】:

      猜你喜欢
      • 2014-07-28
      • 1970-01-01
      • 2012-02-25
      • 1970-01-01
      • 1970-01-01
      • 2016-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多