0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

真刀真枪模块化(2.5)— 掩码结构体

电子设计 来源:电子设计 作者:电子设计 2020-12-14 21:00 次阅读
作者: GorgonMeducer 傻孩子
首发:裸机思维


【说在前面的话】


在本系列的前一篇文章《真刀真枪模块化(2)——图解Service模型》中,我们介绍了一种模块化封装的模型——Service模型。该模型的设计理念实际上服务于一个叫做“黑盒子哲学”的设计思维,其核心思想是:

  • 将模块视作一个黑盒子:模块的设计者不用向外透露黑盒子的实现细节;同时模块的使用者也无法看到黑盒子的内部
  • 模块的设计者和模块的使用者完全通过“接口”来进行约定和沟通。这里所有的接口约定都是通过接口头文件来进行描述和传递的。
  • 接口(及接口头文件)遵循“最小信息公开原则”,即,任何跟使用模块所提供的服务无关的、或者非必要(可有可无)信息都应该从接口头文件中删除。

实践中,要想实现黑盒子,我们实际上要完成两大任务:

  1. 如何隐藏模块的实现,或者说隐藏源代码;
  2. 接口头文件中数据结构的保护,或者说如何阻止用户绕开模块所提供的API而直接访问关键结构体的内部(私有)成员

对于第一条来说,我们只需要把模块编译成library,连同接口头文件一起提供给客户使用就可以做到;而对于第二条要想实现起来却并非那么简单——虽然我们常常说C语言可以通过结构体来模拟类的概念,但它却无法像C++的类那样提供对私有(private)和受保护(protected)成员的隐藏。换句话说,在实践“最小信息公开原则”的时候,如果用户调用服务的时候,确实需要用到结构体(这个结构体是最小信息),如何防止结构体的定义信息被“非法使用”,就成了一个切实的难题。
为了让后续的讨论更为清晰,我们不妨具体的定义一下我们的任务:

  • 只允许用户使用结构体的大小对齐信息——这样用户可以自由的定义变量,或是通过malloc这样的函数进行动态分配;
  • 以某种“通过实际手段强制了的君子协定”的形式——仅在语法层面——阻止用户直接访问结构体的成员。

要想同时做到以上两点,离不开今天索要介绍的主角:掩码结构体(Masked Structure)。
【什么是掩码结构体】


要想理解掩码结构体,抛开复杂和抽象的文字描述,我们不妨来看一个具体的例子:假设我们做了一个字节队列的模块,其中最核心的结构体 byte/_queue/_t 的定义如下:

typedef struct byte_queue_t byte_queue_t;

针对这一结构体(或者叫类)我们提供一系列API(或者叫类的方法),比如:

typedef struct byte_queue_cfg_t {

为了保证模块的正常工作,防止运行期间,用户为了自身的便利,直接”外科手术式的“访问 byte/_queue/_t 的成员导致不必要的问题(比如用户说:我知道你遵循的是最小信息公开原则,也就是说,只要你放了结构体在接口头文件里,我当然理解为我可以任意使用咯?),我们想将整个 byte/_queue/_t 都保护起来——这就好比,我们试图引入一个“蒙版”,遮住结构体的成员信息然后在客户的耳边念起魔咒:
你什么都看不到,你看到了也没法用……
你什么都看不到,你看到了也没法用……
你什么都看不到,你看到了也没法用……

...

要想实现这样的“蒙版效果”其实并不困难,只需要知道要屏蔽的部分实际占用memory的大小,再根据这一大小来定义数组即可,因此,我们可以修改对应的定义为:

typedef struct byte_queue_t byte_queue_t;

这里,我们实际上是给原来的类型重命名为/_/_byte/_queue/_t,并建立了一个内部只使用数组来“滥竽充数”的替身——也就是我们所说的掩码结构体。

如果你看过我之前的文章《漫谈C变量——对齐(3)》,你会注意到,上述替身实际上丢失了结构体 /_/_byte/_queue/_t 的对齐信息——容易注意到 struct /_/_byte/_queue/_t 的结构体整体是对齐到 4 字节的,而掩码结构体中数组chMask本身是对齐到字节的——这会导致当用户使用掩码结构体来定义变量时,由编译器分配的空间可能无法满足原结构体对对齐的要求,造成非对齐访问——轻则性能下降,重则hardfault。

要解决这一问题也并不复杂,只需要借助GCC扩展的运算符 /_/_alignof/_/_() 提取目标类型的对齐信息,再使用/_/_attribute/_/_((aligned())) 来设置掩码数组的对齐要求就可以了:

typedef struct byte_queue_t byte_queue_t;

至此,掩码结构体 byte/_queue/_t 拥有了和原本的结构体 struct /_/_byte/_queue/_t 一样的尺寸和对齐;同时还在“语法”层面阻止了用户直接访问结构体成员的可能(当然,这也只能防君子不防小人),我们原本设立的两个目标都已成功达成。然而,聪明的你会在脑海里浮现出一个疑问——要想掩码结构体能正常工作,上述信息都必须放置到接口头文件中,难道用户是傻子,看不到结构体 /_/_byte/_queue/_t 么?

借助宏的力量,我们可以成功的隐藏住 struct /_/_byte/_queue/_t 的存在。


下面的宏只是为了演示一种简单的实现方法,暂时的打消你的疑虑,而实际在后面我们将要介绍的PLOOC模板中所使用的技法则更为复杂。由于本文只是着重于实际工程实践中如何简单的应用掩码结构体,而不在于介绍复杂的宏技巧,因此我们将不在讨论 PLOOC的实现细节。


#definedeclare_class(__name)     /

借助上述宏,我们可以将接口头文件 byte/_queue.h 中代码简化为:

...

而模块源代码中,则可以使用 class/_internal() 来获取原本的结构体类型:

...

【如何使用PLOOC来简化开发】


PLOOCProtected Low-overheadObject-Oriented programming with ANSI-C的英文缩写,意为:为(类)提供保护的、低开销的、面向对象C语言开发。它是我在 Github 上的一个开源项目(https://github.com/GorgonMedu...)。PLOOC 是目前已知唯一使用掩码结构体对私有(private)和受保护(protected)的成员提供隐藏的OOPC模板;除此以外,通过几近于0的额外资源消耗来实现面向对象封装特性,也是PLOOC的一大卖点。

虽然PLOOC自带的 MDK 例子工程演示了常见的面向对象特性,但处于时间问题,仍然没有来得及提供一份简单直接的手把手使用教程。这里我们仍然以 byte/_queue/_t 为例,为大家介绍一下如何在自己的工程中部署 PLOOC,并应用到 service模型中。

准备阶段

  • 从Github上下载最新的 release 版本。

  • 解压缩后重命名目录为 PLOOC,并复制到你的目标工程中

  • 在你的工程中添加对PLOOC目录的引用

  • 在工程配置中打开对 C99 的支持,如果可能,直接开启 C11和GNU扩展的支持:

  • 如果你使用的是 gcc, clang 或是 arm compiler 6,你还需要打开对微软扩展的支持(-fms-extensions)并屏蔽一些恼人且无害的 warning:
-fms-extensions -Wno-microsoft-anon-tag -Wno-empty-body


NOTE:如果你使用的是 arm compiler 6,在开启微软扩展以后,还需要额外定义一个宏 /_MSC/_VER 来避免底层库中的一些不必要的编译错误。
至此,我们就完成了 PLOOC 在你工程中的部署。

如何在模块中部署

仍以 byte/_queue 模块为例,假设你已经根据 service 模型构建好了目录结构:

  • 打开接口头文件 byte/_queue.h 并在靠近结构体定义的地方其中添加以下内容:
/*! /NOTE: Make sure #include "plooc_class.h" is close to the class definition 

这里,我们定义了两个很重要的宏 /_/_BYTE/_QUEUE/_CLASS/_IMPLEMENT/_/_BYTE/_QUEUE/_CLASS/_INHERIT/_/_。容易看出,他们分别是根据

__<模块名称>_CLASS_IMPLEMENT

__<模块名称>_CLASS_INHERIT__

的形式改写而成的。前者的作用是给 C 源代码标记“我是这个类的实现,我是类的主人”的身份用的;后者的作用是给 C代码标记“我是派生类的实现,我派生自基类”。具体使用方法,后面会具体介绍。
需要特别强调的是,一定不要忘记在接口头文件的尾部将这两个宏都undef掉

...
  • 在 byte/_queue.h 里定义目标类:
//! /name class byte_queue_t

值得注意的是,这里我们用 private/_member()protected/_member()的形式规定了成员变量的属性:其中private的成员是只有类的主人自己可见;而 protected的成员是类的主人以及派生类都可见。如果你想指定某些成员是公共可见的,则可以使用 public/_member()

  • 打开 byte/_queue.c,在文件的最开始通过定义宏 /_/_BYTE/_QUEUE/_CLASS/_IMPLEMENT 来标记自己“类主人”的身份,当然,别忘记包含自己的接口头文件:
#define __BYTE_QUEUE_CLASS_IMPLEMENT
  • 在 byte/_queue.c 中,如果某个函数(类的方法)试图访问类的成员,则应该首先借助 class/_internal() 来“脱下马甲”。方法跟前文一样,这里就不再赘述。

完整的例子在 PLOOC 的example目录下:诸如派生类应该如何处理函数重载应该如何实现等等问题,大家可以打开MDK的例子工程后“细品”。

【后记】


掩码结构体是一种全新的方法,可以在语法层面上限制模块的使用者对关键的结构体(类)成员的访问。相比大家熟悉的“不完全类型”,掩码结构体携带了足够的信息(大小信息和对齐信息),从而允许模块的使用者自由的定义变量或是动态分配,这与“不完全类型”必须依赖动态分配的缺点形成了鲜明的对比。
曾几何时,掩码结构体还有“模块的.c不能包含模块的接口头文件” 这样的限定,在最新的PLOOC中,这一问题已经得到了彻底的解决——再也不用担心 ".c" 和 ".h" 中的类型描述不一致导致的运行时错误。
最后,需要强调一下,对 service 模型来说,掩码结构体,或者说PLOOC的使用只是“锦上添花”——并非必须。读者完全可以根据自己的喜好来决定模块的实现方式。如果你喜欢或者对PLOOC使用有什么建议,欢迎在 github上提交你的issue。


审核编辑 黄昊宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 深度学习
    +关注

    关注

    73

    文章

    5503

    浏览量

    121176
收藏 人收藏

    评论

    相关推荐

    模块化结构与同容量分立器件结构的不同

    模块化结构提高了产品的密集性、安全性和可靠性,同时也可降低装置的生产成本,缩短新产品进入市场的周期,提高企业的市场竞争力。由于电路的联线已在模块内部完成,因此,缩短了元器件之间的连线,可实现优化布线
    发表于 09-09 09:15

    模块化程序设计

    模块化程序设计思想,单片机c语言的模块化设计,方便移植,将程序封装备用。
    发表于 03-22 15:29 9次下载

    AMD二代霄龙迎来历史上的最佳机遇

    AMD Ryzen锐龙这两年让微处理器乃至是整个PC领域焕发了新的勃勃生机,喜闻乐见的真刀真枪激烈竞争重现江湖,是整个行业以及所有消费者的福音。
    发表于 07-02 15:17 589次阅读

    爱立信:5G提前开花结果,中国市场期待真正收获

    在5G真刀真枪的开启商用元年之后,百年老店也持续露出复苏态势。
    的头像 发表于 07-22 11:26 2010次阅读

    西门子s7-300模块化结构及组成部分

    基本的模块化硬件结构结构包括机架、电源、处理器CPU、输入输出I/O模块、编程或通讯用接口,图1表示了一个模块化控制器是如何由
    的头像 发表于 02-22 06:56 1.9w次阅读
    西门子s7-300<b class='flag-5'>模块化</b><b class='flag-5'>结构</b>及组成部分

    模块化UPS电源的分布结构

    分布式是早期模块化UPS经常使用的一种架构。此类模块化UPS系统层面上等价于数台独立的UPS直接并联,其功率模块利用小型UPS改造而成,可自主独立工作。
    发表于 05-29 10:25 1529次阅读

    真刀真枪模块化(2)—图解Service模型

    作者: GorgonMeducer 傻孩子首发:裸机思维 【说在前面的话】在前面一篇文章《真刀真枪模块化(1)——一本糊涂账》中,我们讨论了:在工...
    的头像 发表于 12-14 22:38 503次阅读

    FPGA模块化设计与AlteraHardCopy结构化ASIC

    本文档的主要内容详细介绍的是FPGA模块化设计与AlteraHardCopy结构化ASIC。
    发表于 01-20 17:03 6次下载
    FPGA<b class='flag-5'>模块化</b>设计与AlteraHardCopy<b class='flag-5'>结构化</b>ASIC

    华为模块化数据中心解决方案显著提升模块化建筑结构的抗震性能

    近日,华为联合同济大学申报的预制模块化数据中心解决方案磐石架构技术——箱式模块化数据中心建筑结构设计技术与设计软件开发技术,荣获中国建筑学会科技进步奖。这是继2020年,该技术获上海市建筑学会科技进步奖后,再次实现“跨界出圈”,
    的头像 发表于 11-20 14:15 3124次阅读

    真刀真枪模块化(3.5)——骚操作?不!这才是正统

    首发:裸机思维作者: GorgonMeducer 傻孩子【你可曾怀疑过?】C语言写多了,或多或少会听说一些“上古传下来”的教条,比如:include 语...
    发表于 01-25 18:28 0次下载
    <b class='flag-5'>真刀真枪</b><b class='flag-5'>模块化</b>(3.5)——骚操作?不!这才是正统

    真刀真枪模块化(3)—— 层次框架初探

    作者: GorgonMeducer 傻孩子首发:裸机思维(图片来自网络,侵删) 【说在前面的话】在本系列的前面几篇文章中,我们依次讨论了如下的几...
    发表于 01-25 19:44 0次下载
    <b class='flag-5'>真刀真枪</b><b class='flag-5'>模块化</b>(3)—— 层次框架初探

    真刀真枪模块化(1)——一本糊涂账

    对很多人来,嵌入式软件开发过程中 模块化(Modularization)是一个海市蜃楼、是一个书面词汇、是一个过气的时尚——模块化似乎从未真正的...
    发表于 01-26 19:33 0次下载
    <b class='flag-5'>真刀真枪</b><b class='flag-5'>模块化</b>(1)——一本糊涂账

    什么是模块化自动

    什么是模块化自动
    的头像 发表于 03-10 16:29 2852次阅读
    什么是<b class='flag-5'>模块化</b>自动<b class='flag-5'>化</b>?

    欧姆龙模块化编程的使用技巧

    【导读】在平常使用欧姆龙SysmacStudio 编程时,有新建大量的结构和全局变量,若不分类进行模块化,会造成查找不方便,下面分享的就是对全局变量和数据类型进行模块分类,方便查找,
    的头像 发表于 03-17 17:45 2013次阅读

    PLC模块化结构化编程实例

    模块化编程中OB1起着主程序的作用,FC或FB控制着不同的过程任务,相当于主循环程序的子程序。模块化编程中被调用块不向调用块返回数据。
    的头像 发表于 07-10 14:42 1031次阅读
    PLC<b class='flag-5'>模块化</b>和<b class='flag-5'>结构化</b>编程实例