搜索
bottom↓
回复: 68

用“不透明指针”实现C语言的struct成员的隐藏

  [复制链接]

出0入0汤圆

发表于 2018-10-30 16:15:13 | 显示全部楼层 |阅读模式
之前浏览论坛,看到有用“掩码结构体”的方式实现struct成员的隐藏,这样用户就只能通过指定接口来访问struct成员数据,从而在一定程度上实现了信息隐藏,但是“掩码结构体”方法需要在引入一组宏定义,同时需要在xxx.c文件和xxx.h文件中写一遍struct的定义(当然复制一下也方便),但是最头疼的是,有时在一处修改了struct的定义,而另一处忘了修改,但是编译时并不会报错,而运行时会出错。

今天偶然看到“不透明指针”的概念,发现可以实现与“掩码结构体”类似的效果。
其实,这个方法我之前也用过,但是不知道还有“不透明指针”的这一概念。

首先,必须要说明一下,C的struct中成员本身是公开的,所有C编程的奇技淫巧,只是实现形式上的隐藏,并不能真正彻底的隐藏数据。

比如:
  1. struct DevLed
  2. {
  3.         uint8_t state;
  4.         char *name;
  5. };
复制代码


然后现在有一个指针p指向一个具体的DevLed结构体对象
所谓”形式上的隐藏“是指:在外部文件试图通过p->state来访问struct中的成员,编译时会报错,从而“强制”用户通过指定的接口来访问数据,因此实现了信息的隐藏和访问权限控制。

所谓“并不能真正彻底的隐藏数据”是指:在C语言中,如果获得了一个对象的指针(内存地址),那么就可以获得相应的数据,比如使用 *((uint8_t *)p)就可以获得成员state。当然实际情况下,由于用户并不清楚结构体中成员信息,所以虽然获得了数据,但是这些数据代表什么含义,那就靠猜了。但是,多猜几次总归还是可以猜出来了。所以说,C语言中的这些隐藏方法是“防君子不防小人”。

那不管怎样,有总比没好,而且“不透明指针”方法比“掩码结构体”方法,使用起来更加方便点。
  1. /*led.h*/

  2. #ifndef __LED_H
  3. #define __LED_H

  4. //供用户使用的类型
  5. //DevLed_t就是所谓的“不透明指针”
  6. typedef struct DevLed * DevLed_t;

  7. //供用户使用的接口
  8. extern DevLed_t DevCreate(void);
  9. extern uint8_t        DevRead(DevLed_t s);
  10. extern void        DevWrite(DevLed_t s, uint8_t arg);

  11. #endif
复制代码

  1. /*led.c*/

  2. #include <stdint.h>
  3. #include <stdlib.h>

  4. //DevLed结构体定义
  5. struct DevLed
  6. {
  7.         uint8_t state;
  8. };

  9. typedef struct DevLed * DevLed_t;

  10. //接口的具体实现
  11. DevLed_t DevCreate(void)
  12. {
  13.         //注意,此处演示用了malloc动态内存分配,但也可以用静态分配
  14.         DevLed_t s = (DevLed_t)malloc(sizeof(struct DevLed));
  15.         if (NULL != s)
  16.                 s->state = 0;
  17.         return s;
  18. }

  19. uint8_t        DevRead(DevLed_t s)
  20. {
  21.         return s->state;
  22. }

  23. void DevWrite(DevLed_t s, uint8_t arg)
  24. {
  25.         s->state = arg;
  26. }
复制代码

  1. /*main.c*/

  2. #include <stdio.h>
  3. #include <stdint.h>
  4. #include "led.h"

  5. int main()
  6. {
  7.         //创建对象并获取指针
  8.         DevLed_t me = DevCreate();

  9.         printf("readout = %d\n", DevRead(me));

  10.         DevWrite(me, 10);

  11.         printf("readout = %d\n", DevRead(me));

  12.         //防君子不防小人,故意采用这种方式,也是可以获取数据的
  13.         printf("readout = %d\n", *((uint8_t *)me));

  14.         //直接访问me->state编译会报错,实现形式上的隐藏
  15.         //printf("readout = %d\n", me->state);

  16.         getchar();
  17.         return 0;
  18. }
复制代码

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入8汤圆

发表于 2018-10-30 16:22:57 | 显示全部楼层
有兴趣的话,可以去看李先静的『系统程序员成长计划』。

出0入0汤圆

 楼主| 发表于 2018-10-30 16:26:07 | 显示全部楼层
security 发表于 2018-10-30 16:22
有兴趣的话,可以去看李先静的『系统程序员成长计划』。

谢谢。哈哈哈,看来我是孤陋寡闻了。不过,想想上面这些东西也是早就存在了,只是我一直没发现有这么个东西而已。

出0入8汤圆

发表于 2018-10-30 16:29:47 | 显示全部楼层
dxgdsx 发表于 2018-10-30 16:26
谢谢。哈哈哈,看来我是孤陋寡闻了。不过,想想上面这些东西也是早就存在了,只是我一直没发现有这么个东 ...

闻道有先后。
这些确实很早就有的,但是知道的人并不多。
李先静那本书,也是收集了前人的一些优秀思想,他自称拙著,但其实不拙。

出0入8汤圆

发表于 2018-10-30 16:39:46 | 显示全部楼层
另外,可以去这边下载「【分享书籍】『C语言程序设计:现代方法(第 2 版)』中文 」
里面较为系统专业的介绍了这一技巧,在『第 19 章 程序设计』。

出0入0汤圆

 楼主| 发表于 2018-10-30 16:40:48 | 显示全部楼层
security 发表于 2018-10-30 16:29
闻道有先后。
这些确实很早就有的,但是知道的人并不多。
李先静那本书,也是收集了前人的一些优秀思想, ...

刚下载大致浏览了一下,觉得应该会有些启发,抽空好好拜读下。

出0入0汤圆

发表于 2018-10-30 16:50:12 | 显示全部楼层
这个方法用来隐藏成员字段不错,但无法在.c文件之外直接通过sizeof获取结构体的实际大小,需加以改造方可实现。

出0入296汤圆

发表于 2018-10-30 18:08:28 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-10-30 18:14 编辑

这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类型获得真实的
类型大小信息。建议还是不要使用。

另外用typedef定义指针也不是一个好习惯,因为通过typedef以后,使用时根本看不出这是个指针,增加了代码阅读的难度,增加了用户
犯错的概率:


  1. typedef struct DevLed * DevLed_t;
复制代码


通过DevLed_t,你能看出这是一个指针么?你的函数原型看起来是值传递,其实是指针的值传递。这里很容易造成误解。

指针,就应该有指针的样子。不然系统大了,代码阅读起来就是坑人——我猜你是不坑自己的,但坑不坑别人就不好说了。

基于同样的道理,这就是为什么我一直提倡,函数指针的定义不要直接用typedef定义指针,而是用typedef定义原型:


  1. extern bool usart_try_to_read_byte(uint8_t *ptByte);

  2. typedef bool fn_serial_in_t (uint8_t *ptByte);

  3. fn_serial_in_t *ptInput = &usart_try_to_read_byte;
复制代码

使用的时候:


  1. uint8_t chByte;

  2. if (false != (*ptInput)(&chByte)) {
  3.     //! 成功的读取到了字节
  4.    ...
  5. }
复制代码


你可以看到,同样是函数指针,通过typedef函数原型以后,无论何时使用看起来都知道是个指针,保持了和普通指针
一样的语法结构,降低了学习和记忆的难度,也使得代码更容易阅读,风格更为统一。

所以说,typedef是好东西,但不要乱用。最好不要丢失必要的信息。

出30入54汤圆

发表于 2018-10-30 19:51:44 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

对,指针定义成类型在查找问题的时候简直坑爹,相对快速的方法就只能打开-Wall然后仔细检查各项警告了

出0入0汤圆

 楼主| 发表于 2018-10-30 21:11:32 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

是的,DevLed_t我觉得从字面上看更像一个结构体类型,无奈好多开源软件里面都这么写(比如rt-thread),不知道出于什么原因。
如果按我个人爱好来取名字的话,我更喜欢把指针写成pDevLed,像一个句柄的样子。

至于野指针更多是要编码者自己来谨慎规避的。

如果写成这样呢:
  1. /*led.h*/

  2. #ifndef __LED_H
  3. #define __LED_H

  4. //供用户使用的类型
  5. typedef struct DevLed DevLed_t;

  6. //供用户使用的接口
  7. extern DevLed  *DevCreate(void);
  8. extern uint8_t        DevRead(DevLed *s);
  9. extern void        DevWrite(DevLed *s, uint8_t arg);

  10. #endif
复制代码

出615入1076汤圆

发表于 2018-10-30 21:26:02 | 显示全部楼层
python 這種高級語言都沒有支持 private member, 不想讓用戶直接訪問的變量和函數前面加下劃線,通過這種方式來防止君子。

也就是說,把你的 state 改爲 _state 即可。

出0入8汤圆

发表于 2018-10-30 21:44:17 来自手机 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

对,说的好,看到楼主把指针定义成类型的时候,就感觉很不自在

出425入0汤圆

发表于 2018-10-30 21:58:11 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

我用着量子框架QPN,经常地用着这样的不透明指针。危险吗?如何避免?

出0入42汤圆

发表于 2018-10-30 22:00:02 | 显示全部楼层
我喜欢直接上void *指针

(xxx.h)
...
int xxx_size(void);
int xxx_init(void * handler);
...

(xxx_type.h)
...
typedef struct
{
        int a;
        int b;
        ...
}XXX_TYPE;
...


(xxx.c)
#include "xxx.h"
#include "xxx_type.h"
...
int xxx_size(void)
{
        return sizeof(XXX_TYPE);
}

int xxx_init(void * handler)
{
        int err;
        XXX_TYPE * pxxx = (XXX_TYPE *)handler;
        ...
        return err;
}


使用库时,上层只包含xxx.h,因此不知道结构体的具体结构,也就避免了意外访问结构体成员。使用库时,先xxx_size()获取大小,然后分配内存,调用xxx_init,随后使用库函数。

出0入8汤圆

发表于 2018-10-30 22:27:59 | 显示全部楼层
dxgdsx 发表于 2018-10-30 21:11
是的,DevLed_t我觉得从字面上看更像一个结构体类型,无奈好多开源软件里面都这么写(比如rt-thread), ...

当你看到很多人都那么做的话,那些人、那些项目的质量水准还很在线的话。
那么你就要思考一下,你一直以来所顾虑的东西,是否真那么重要?

其实,说句大话:在现代编辑器如此智能的时代,你顾虑的点,更显得不是那么重要。

出0入0汤圆

发表于 2018-10-30 23:07:47 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

说的有道理,以后还是不要学这种技巧,

出0入96汤圆

发表于 2018-10-31 07:53:25 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

大家的出发点不同,LZ是从防“盗”的角度出发,您是从分享的角度出发,二者是冲突的,
各有各的无奈,如果人人自律,则“大道至简”

出0入0汤圆

发表于 2018-10-31 08:21:46 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

说得很好,学习了函数指针的谨慎用法!

出0入30汤圆

发表于 2018-10-31 10:57:19 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

请教各位,
P0 = *((unsigned char data*)&ledBuff+i);
功能意思懂,就是不能理解如何这样写的,
如何详细解释这程序?
并把这一行程序拆分成几行写个易懂的,谢谢!

出0入296汤圆

发表于 2018-10-31 19:05:30 | 显示全部楼层
ycheng2004 发表于 2018-10-31 10:57
请教各位,
P0 = *((unsigned char data*)&ledBuff+i);
功能意思懂,就是不能理解如何这样写的,

你问的这个问题,我在这个贴子里面做了非常详细的解释:

https://www.amobbs.com/thread-5640740-1-1.html

出0入296汤圆

发表于 2018-10-31 19:07:35 | 显示全部楼层
gallle 发表于 2018-10-31 07:53
大家的出发点不同,LZ是从防“盗”的角度出发,您是从分享的角度出发,二者是冲突的,
各有各的无奈,如 ...

我也防盗好么……掩码结构体就是我提出的。掩码结构体的一个变种就是把内部成员都改成类似

uint32_t               : 16;
uint32_t               : 8;
uint32_t               : 8;

这种形式来隐藏内容。

我强调的是防盗的时候仍然也要兼顾必要的信息——这里说的就是基本的大小信息。

出0入296汤圆

发表于 2018-10-31 19:09:58 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-10-31 19:12 编辑
security 发表于 2018-10-30 22:27
当你看到很多人都那么做的话,那些人、那些项目的质量水准还很在线的话。
那么你就要思考一下,你一直以 ...


实名反对“存在即合理”!!!!
我之所以有今天,就是不停的思考很多事情背后的原因,不会因为“从众”就觉得内心安全了。
对于一种做法,如果你不知道大家这么做的原因,说不出这么做的本质好处和缺点,不知道
使用的限制,我觉得还是不要做鸵鸟好。

当然,如果你觉得从众活得比较轻松,我觉得也能理解,但最好回答问题的时候直接这么说:

“别问我具体为什么,也别跟我争辩背后的原理,我就是我觉得大家都这么做心里比较安全”

这里我说话可能有点冒犯或者情绪化,我先道歉。但是反对”存在即合理“的心情是不会变的。
讨论就是讲道理。如果你知道什么背后的原理,还请说出来。最好不要用”为什么别人都这么
做“来引导别人”从众“——说难听就是,引导他人放弃”独立思考“。

出0入296汤圆

发表于 2018-10-31 19:16:50 | 显示全部楼层
wshtyr 发表于 2018-10-30 22:00
我喜欢直接上void *指针

(xxx.h)

(void *)的使用要谨慎。能避免最好避免。这是我的建议。
未经充分思考,就在某个”语境“下使用 (void *) 是比较危险的。一个类型包含
地址,大小和操作 三个部分。对用户来说,让别人能安全的使用这三部分信息
是一个良好”抽象数据类型(ADT)“设计的关键。

楼主也好,我引入掩码结构体也好,都是为了让用户能”安心“只使用我们提供的
”操作“函数来访问对应的类型。

这里的区别是,我强调要保留  地址 和 大小信息。 楼主说,只要有地址就行了。

出0入296汤圆

发表于 2018-10-31 19:18:33 | 显示全部楼层
guolun 发表于 2018-10-30 21:58
我用着量子框架QPN,经常地用着这样的不透明指针。危险吗?如何避免?

危险啊。我解释了,是野指针和内存入侵的温床。
我提出的解决方案就是:要么直接提供结构体本身,要么用掩码结构体。

出0入296汤圆

发表于 2018-10-31 19:19:48 | 显示全部楼层
另外,”不透明指针“的用法 在另外一本书中,被称为”C语言的陷阱“,原因就是丢失了 大小信息。

出0入296汤圆

发表于 2018-10-31 19:22:12 | 显示全部楼层
dxgdsx 发表于 2018-10-30 21:11
是的,DevLed_t我觉得从字面上看更像一个结构体类型,无奈好多开源软件里面都这么写(比如rt-thread), ...

除了依靠用户的自觉以外,你觉得作为库的编写者,我们是否也应该 简化用户的开发呢?
怎么简化?就是让别人可以用 sizeof,而不是去阅读 用户手册,把”缺乏类型大小信息“ 记
在心里——我就问,有多少人会好好阅读用户手册和使用说明?

出0入0汤圆

 楼主| 发表于 2018-10-31 20:23:08 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-31 19:22
除了依靠用户的自觉以外,你觉得作为库的编写者,我们是否也应该 简化用户的开发呢?
怎么简化?就是让别 ...

可能是开源软件觉得自己提供了源码,用户可以看到具体类型。
讲真,如果他们只提供lib的话,我看到DevLed_t,极有可能认为是一个结构体,而不会认为它是一个指针。

出0入296汤圆

发表于 2018-11-1 00:04:42 | 显示全部楼层
dxgdsx 发表于 2018-10-31 20:23
可能是开源软件觉得自己提供了源码,用户可以看到具体类型。
讲真,如果他们只提供lib的话,我看到DevLed ...

我觉得主要还是写软件的人没有觉得这是大问题——估计实际上也不是什么大问题,因为C语言
很难隐藏问题,一旦你写错了,实际运行很大程度上就没法通过 7x24 小时的可靠性测试。

也许是别的原因吧。但,好习惯值得推广,坏习惯最好在有机会的场合下改掉。Legacy的代码
如果修改代价太大,还是不要动了……当然希望不要因为这个原因,大家就觉得这是合理的。

出0入30汤圆

发表于 2018-11-1 09:11:16 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-31 19:05
你问的这个问题,我在这个贴子里面做了非常详细的解释:

https://www.amobbs.com/thread-5640740-1-1.ht ...

谢谢大师
您的祼机思维在微信公众号查不到,

出0入0汤圆

 楼主| 发表于 2018-11-1 09:27:29 | 显示全部楼层
本帖最后由 dxgdsx 于 2018-11-1 12:17 编辑
Gorgon_Meducer 发表于 2018-11-1 00:04
我觉得主要还是写软件的人没有觉得这是大问题——估计实际上也不是什么大问题,因为C语言
很难隐藏问题, ...


是的,类型不一致的问题在编译阶段基本上就可以排查干净。
反而是野指针之类的内存问题,很头疼。但也是有迹可循的,比如运行时出现各种莫名其妙的问题,软件上基本是内存问题,大方向确定以后,排查相对也是容易一些。
但是,好的编码习惯可能是事半功倍的。事后反查总是不值当的。
感谢大师指点!

但是,“不透明指针”用起来还是比较方便的,当然是在避免陷阱的前提下。
比如上面的例子中,


  1. //定义了一个“不透明指针”,并将其指向NULL
  2. DevLed_t me = NULL;

  3. //然后调用接口来真正创建一个对象实体,并将上述指针指向该实体
  4. me = DevCreate();
复制代码


个人觉得这里应该没什么大问题,因为后续要对me进行操作,必须首先要调用DevCreate()函数来创建它所向的实体。
而且,一般库函数中,如果入口参数中包含指针,一般是要对该指针的有效性进行检查的,如果传入一个NULL,函数当然直接返回了。
所以,使用时应该不会存在太大的问题。

至于对象大小信息,确实在这种方法,用户是没有办法获取对象大小信息(额外增加获取大小的函数除外)。
如果允许使用malloc等动态内存分配功能,那么大小信息对用户而言有没有,应该不是很重要的。
问题就是静态分配时需要对象大小信息。
其实,这个问题对绝大多数RTOS而言也没关系,因为RTOS中有内存管理,比如内存池等。在裸机中,也可以通过预分配内存来实现。

出0入0汤圆

发表于 2018-11-1 10:25:44 | 显示全部楼层
学习一下。。。。。。

出0入8汤圆

发表于 2018-11-1 10:30:51 | 显示全部楼层
dxgdsx 发表于 2018-10-31 20:23
可能是开源软件觉得自己提供了源码,用户可以看到具体类型。
讲真,如果他们只提供lib的话,我看到DevLed ...


看到 DevLed_t 时,潜意识就认为这是一个结构体,这是对的。
但是,我们不可能靠直觉,总要看手册、头文件的类型说明的。lib 也要提供对应的类型说明的。

对于一个项目而言,遵循一致的约定,就是 OK 的。
RT-Thread 因为历史因素,有时候 DevLed_t 对应着指针类型,有时候 DevLed_t  对应着结构体。确实有一些混淆,但我上面说过了,在现代编辑器已经相当智能的时代,这点成本开销,在降低。
更好的约定就是 DevLed_t  就代表着结构体,DevLed_t * 对应着指针,或者给 DevLed_t 加相应的标识,表征这是一个指针类型,就像面向对象语言的接口类型一样。

出0入0汤圆

 楼主| 发表于 2018-11-1 10:35:32 | 显示全部楼层
security 发表于 2018-11-1 10:30
看到 DevLed_t 时,潜意识就认为这是一个结构体,这是对的。
但是,我们不可能靠直觉,总要看手册、头文 ...

是的,用户指南手册里面的关键信息还是要看的,不可能仅靠一个无详细说明的头文件就动手写代码,也不能仅靠直觉就确定某个类型。

出0入8汤圆

发表于 2018-11-1 10:40:56 | 显示全部楼层
本帖最后由 security 于 2018-11-1 10:42 编辑
Gorgon_Meducer 发表于 2018-10-31 19:09
实名反对“存在即合理”!!!!
我之所以有今天,就是不停的思考很多事情背后的原因,不会因为“从众” ...


我是觉得这种封装技术,倒是挺正统的。
只是混用了 DevLed_t 表征的含义,一会儿是指针,一会儿是对象,这是不好的做法。
但我也说了,在现阶段,越来越高级的编辑器,能帮助我们降低这些成本开销。

这种技术,在『C语言程序设计:现代方法(第 2 版)』这本国外的经典教材中,用了一章节的篇幅,来阐述。
C语言接口与实现』这本经典书籍,整本书,也都是用这种方法。
当然这种面向对象技术,不一定适用低端的嵌入式环境,但目前的嵌入式环境较以前已经有很大的提升了,对于资源较为充分的环境,为什么不能试试这些正统的技术呢?

出100入143汤圆

发表于 2018-11-1 10:53:32 | 显示全部楼层
QP C 状态机传递对象就是子对象指针强制转换为父对象指针这样来用,子对象结构体可以继承父对象里面数据,父对象可以强制转换子对象指针

出0入8汤圆

发表于 2018-11-1 10:59:30 | 显示全部楼层
zzh90513 发表于 2018-11-1 10:53
QP C 状态机传递对象就是子对象指针强制转换为父对象指针这样来用,子对象结构体可以继承父对象里面数据, ...

这就是多态吧。

出0入0汤圆

发表于 2018-11-1 11:04:58 | 显示全部楼层
收藏了,

出0入0汤圆

发表于 2018-11-1 17:18:37 | 显示全部楼层
看明白了,这样做的目的就是为了实现访问控制,让外部访问不了私有变量。

出0入8汤圆

发表于 2018-11-1 17:38:16 | 显示全部楼层
磊磊映画 发表于 2018-11-1 17:18
看明白了,这样做的目的就是为了实现访问控制,让外部访问不了私有变量。 ...

封装,隐藏实现细节。

出0入0汤圆

发表于 2018-11-1 17:48:02 | 显示全部楼层
security 发表于 2018-11-1 17:38
封装,隐藏实现细节。

嗯嗯,这个类里面有哪些方法和成员   外部的确是不知道的,只能通过特定的接口去访问 。

出0入296汤圆

发表于 2018-11-1 18:26:04 | 显示全部楼层
ycheng2004 发表于 2018-11-1 09:11
谢谢大师
您的祼机思维在微信公众号查不到,

我刚刚测试了,可以搜索到的……

添加朋友->公众号

在这个页面里搜索才行。如果直接在添加朋友里面搜索是搜不到的。

出0入296汤圆

发表于 2018-11-1 18:31:17 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-11-1 18:40 编辑
security 发表于 2018-11-1 10:40
我是觉得这种封装技术,倒是挺正统的。
只是混用了 DevLed_t 表征的含义,一会儿是指针,一会儿是对象, ...


也许这就是“历史”的正统吧。那两本书的内容我看了,没看到什么特别的解释为什么这么做
就特别好,实际上我觉得《C语言的陷阱》这本书提到的各种问题恐怕更为中肯。另外,学术
界对于C语言不是“类型安全”的论断也是基本没有任何异议的。这也是为什么出现了很多在C
语言基础上进行改良而出现的类型安全的语言(C++不是,但是C#是的,rust也是的)。我
们因为环境限制不得不继续用C的人,需要做的事情就是尽可能让C在使用中遵循一些规则
——让自己使用的时候更贴近类型安全,也让别人用我们的库的时候更不容易犯错。这是一
种尝试和“贴近”,没法完美,但是尽力了。

为了追求同一个目的,但是有更好的方法,为什么不用呢?说缺点,“不透明指针”不算类型
安全(缺乏大小信息,无法静态分配,无法将对应的类型 串行化 以后通过数据流进行通信);
掩码结构体,需要.c和.h之间的同步。

都有缺点,但前者的缺点是体现在用户那边的——给用户带来不便;后者的缺点是体现在开发
者这里的,但是用户很happy。那这就问题来了,难道做模块不应该更多从用户体验的角度来
考虑么?难道继续“屁股决定脑袋”?我开发容易就好,用户不方便是它的事情,反正浪费的是
用户的时间,反正犯错时他不小心??嗯……好好想想,整天抨击“屁股决定脑袋”的人也许是
同一批人吧……

我不做过多辩解。只是对我,以及我可以控制的范围来说,有更好的方法,就用更好的方法。

做事情要讲道理,这点是要坚持的——是的,只在我可以控制的范围内,其它你们随意。

我只分享,不推广。

出0入0汤圆

 楼主| 发表于 2018-11-2 10:07:45 | 显示全部楼层
Gorgon_Meducer 发表于 2018-11-1 18:31
也许这就是“历史”的正统吧。那两本书的内容我看了,没看到什么特别的解释为什么这么做
就特别好,实际 ...

静态分配这个问题对绝大多数RTOS而言也没关系,因为RTOS中有内存管理,比如内存池、链表管理等系统服务。
而且,对于RTOS中的设备、消息、信号量这些,一般都是调用系统服务来创建的,所以也不需要用户来静态分配。

而在大师所崇尚的裸机思维中,确实“不透明指针”方法是没有办法让用户自主”静态分配“的。

任何技术都有一定的局限性和适用场合,并没有绝对的好与坏。

”掩码结构体“的信息隐藏相对弱一些,因为它在提供大小信息的同时,还给出了成员信息。只要稍微在代码上做点手脚,可以立马获取内部成员数据。
而”不透明指针“只给出了结构体变量内存地址,不知道结构体成员信息的情况,直接去读内存并没有太多意义。
使用指针所具有的优点和缺点,”不透明指针“方法都具有。正如用好指针,代码事半功倍;用不好指针,有可能事倍功半。

当然,技术开发人员,往往处于项目乙方的位置,特别当提供的产品又要被甲方进行二次开发时,如何提供让对方能快速、简单、准确开发的产品,是一个乙方的”自我修养“。

出0入296汤圆

发表于 2018-11-2 18:04:42 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-11-2 18:16 编辑
dxgdsx 发表于 2018-11-2 10:07
静态分配这个问题对绝大多数RTOS而言也没关系,因为RTOS中有内存管理,比如内存池、链表管理等系统服务。 ...


建议你看下22楼……然后再讨论掩码结构体的缺点。

而且你避开了我说的 “把对象序列化加入流” 以及 “将不透明指针所指向的类型作为某一个新的结构体成员一部分”的问题。

最后,MCU中滥用malloc 还要面对碎片化的问题(要过7x24小时测试,确定性很重要)——我不知道多少人会去用专用堆避免碎片化,但我看到大部人就是简单的用类似malloc的东西。

出0入0汤圆

发表于 2018-11-2 18:08:30 | 显示全部楼层
Gorgon_Meducer 发表于 2018-11-1 18:31
也许这就是“历史”的正统吧。那两本书的内容我看了,没看到什么特别的解释为什么这么做
就特别好,实际 ...

顶顶。。。。。
Mark。。。。。

出0入0汤圆

 楼主| 发表于 2018-11-2 19:28:26 | 显示全部楼层
Gorgon_Meducer 发表于 2018-11-2 18:04
建议你看下22楼……然后再讨论掩码结构体的缺点。

而且你避开了我说的 “把对象序列化加入流” 以及 “ ...

难道采用这种位域表示方式,不是在告诉别人第一个成员是多少字节,第i个成员是多少字节吗?
然后,没人规定必须要用malloc,我已经设定了前提条件,在RTOS场合下,存在内存池、对象池的基础上,采用“不透明指针”没啥问题。
没人强迫别人使用malloc,他想使用那就是他的事情。

出0入296汤圆

发表于 2018-11-2 20:56:12 来自手机 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-11-2 21:23 编辑
dxgdsx 发表于 2018-11-2 19:28
难道采用这种位域表示方式,不是在告诉别人第一个成员是多少字节,第i个成员是多少字节吗?
然后,没人规 ...


这个信息已经基本是无用状态了。而且,位域的灵活性实际上暗示了——哪个成员占用多大空间也是可以隐藏(混淆)的,不光如此,连结构体内部的对齐问题也可以手动模拟和指定。
rtos内核对象可以用专用堆,但用户自己的对象呢?rtos只是个工具啊,如果你仔细去研究rtos的内存管理,你会发现,除了内核对象的专用堆,给用户对象用的内存管理其实也不能解决碎片化的问题的。目前学术界普遍的结论是,离开垃圾收集器,基本上碎片问题是无解的,折中方案就是用户要自己为自己的类型构建专用堆。
如果真的要用“不透明指针”,我的建议是,一定要处理好动态分配的碎片问题。
你只说“没人强迫别人用malloc”,但从你支持者((你的读者)角度来说这不解决问题啊。从建设性的角度来说,你起码要说:

的确,要想大规模应用 不透明指针 要慎重考虑碎片化问题,甚至在可能得情况下尽可能用专用堆。

你只是把问题抛给别人,不提供解决方案或者思路,这算什么好的讨论。问题是客观存在的。而且对大部分人来说,内存管理技术其实是并不熟悉的,大家还是习惯上默认要用malloc的。他们并不知道内存碎片的形成原因和风险。( Memory Fragmentation )


序列化的问题你还是没回答我。
集成已有类型到新的结构体内部的问题,你也没有回答我——世界上并非只有引用。

对第一个问题,我也觉得你没有建设性的回答——你哪怕说,模块应该提供专门的api来应对可能的
应用需求,这也是一个解决思路。好坏不说,至少是建设性的。

第二个问题,估计真的要好好从设计模式出发才能找到答案了。

出0入0汤圆

 楼主| 发表于 2018-11-2 22:21:06 | 显示全部楼层
Gorgon_Meducer 发表于 2018-11-2 20:56
这个信息已经基本是无用状态了。而且,位域的灵活性实际上暗示了——哪个成员占用多大空间也是可以隐藏( ...

事实上,从上面的讨论来看,我从来没有回避过“不透明指针”方法难以实现“静态分配”这一事实。因此我将它的适用范围缩小到具有专用内存池、对象池管理的RTOS环境中。
至于用户自己的对象,当然由用户来选择适用何种实现手段。

这个信息已经基本是无用状态了。而且,位域的灵活性实际上暗示了——哪个成员占用多大空间也是可以隐藏(混淆)的,不光如此,连结构体内部的对齐问题也可以手动模拟和指定。
当然,在.h文件中完全可以伪造出一个与真实对象完全一致的类型(对齐、字节数),同时不显式地透露真实对象的成员信息(甚至可以故意混淆)。但是,这种种一切都是以花费开发者时间精力为代价的。
所以,你说的很好,关键就是你想把这个代价放在哪边,开发者 or 用户 ?

1、序列化的问题
抱歉,这个问题我不懂,也没有研究过。

2、将不透明指针所指向的类型作为某一个新的结构体成员一部分
虽然你说“世界上并非只有引用”,但是“引用”在此处确实可以实现数据的集成。

3、对于“不透明指针”方法,除了使用专用堆以外,也可以使用栈的。

4、对于“不透明指针”方法,一步到位的静态分配是做不到,但是也有些间接方法,比如提供api用于返回sizeof(struct xxx_t),用户先调用测试一下对象大小,再静态分配。当然,这个方法我自己是不喜欢的。

出0入296汤圆

发表于 2018-11-3 00:32:01 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-11-3 00:38 编辑
dxgdsx 发表于 2018-11-2 22:21
事实上,从上面的讨论来看,我从来没有回避过“不透明指针”方法难以实现“静态分配”这一事实。因此我将 ...


到这里,我们基本都清楚的表明了自己的观点也了解了对方的观点。
道理应该都很清楚了,剩下就看别人根据自己的喜好来取舍了。

只是有一点,RTOS的内存池管理和对象管理是不能解决碎片化问题的,这个,如果有可能,我推荐多了解下。我之前有看过,结论还记得,具体推导细节忘记了。

我推荐一本书《Garbage Collection: Algorithms for Automatic Dynamic Memory Management》
https://www.amazon.co.uk/Garbage ... =garbage+collection
国内有翻译版本
https://www.amazon.cn/dp/B01CQQ1 ... E%E6%94%B6%E9%9B%86

回头看了下帖子,火药味太重,从我的不当用词开始。我多反省,反省。非常抱歉。
道理不辨不明,还感谢你花费了这么多时间来回应。

出0入0汤圆

 楼主| 发表于 2018-11-3 12:20:21 | 显示全部楼层
本帖最后由 dxgdsx 于 2018-11-3 12:22 编辑
Gorgon_Meducer 发表于 2018-11-3 00:32
到这里,我们基本都清楚的表明了自己的观点也了解了对方的观点。
道理应该都很清楚了,剩下就看别人根据 ...


老师言重了!
显然,你在这个领域比我们绝大数更有经验,遇到的案例也更多,因而看问题的层次也更高。
不同层次之间可能确实存在沟通问题,因而更需要讨论,道理越变辨明。
只要讨论的重点还在技术领域,火药味重点也没啥的。

malloc导致碎片化问题,确实严重。
但是,使用预分配的静态内存作为专用池,这样还存在很大的碎片化问题,这个点我确实没有思考过。

另外,无法静态分配,这个问题是无论如何也回避不了,可能很多场合就因为这一点,这个技术实现方式就已经可以毙掉了。
另外,我帖子开头所说的“掩码结构体”需要同步.c文件和.h文件,以及单独修改一处可能导致bug的说法,其实本质上也是“为赋新词强说愁”。
因为,现在脚本语言这么丰富,完全可以利用自动化工具代替人工完成同步操作,以及加入断言等检测手段来保证.c文件和.h文件的完全同步。

讨论过程中如说的不对,或有冒犯之处,我必须表示道歉。

另外,关于“序列化”这个问题,大师是否有相关资料推荐? 这个问题之前没有听到过(逃)。

出10入12汤圆

发表于 2018-11-3 12:22:21 | 显示全部楼层
有点复杂!!!!!!

出0入8汤圆

发表于 2018-11-3 17:06:57 | 显示全部楼层
dxgdsx 发表于 2018-11-3 12:20
老师言重了!
显然,你在这个领域比我们绝大数更有经验,遇到的案例也更多,因而看问题的层次也更高。
不 ...

写代码都用上人工智能了,到底智能不智能呢

这两种方法,有各自的应用场合。
对于资源丰富的场合,我更倾向于正统的方法:用 incomplete type 来实现 ADT。
这种方法也是国外计算机教育的正统方法。因为传授 C 语言课程时,默认的环境是 PC 环境。
可以看看这里普林斯顿大学http://www.cs.princeton.edu/courses/archive/spr96/cs217/precepts/adt-precept/

相比而言,掩码结构体,更像一个独门秘籍,比较特立独行。
我的资质平平,只能随大众,学习点更普世的技术。

对象的序列化这东西,你去网上查一下,应该就知道了。

出0入296汤圆

发表于 2018-12-15 03:20:01 | 显示全部楼层
security 发表于 2018-11-3 17:06
写代码都用上人工智能了,到底智能不智能呢 。

这两种方法,有各自的应用场合。

掩码结构体我又改进了,之前的缺点都去掉了:

现在移除了以下限制:
1、以前需要一个额外的 extern_class宏放在.h里,现在不需要了
2、以前需要.c和.h里各放一个同样的内容,现在不需要了
3、以前不允许.c包涵自己的接口头文件,现在没有这个限制了
4、以前强制用户使用,现在不强制了

例子我上传了:

https://gitee.com/versaloon_simo ... /utilities/template

这里的vsf_pool模块:

vsf_pool.h  模块的接口头文件
vsf_pool.c  模块的C源文件
__class_pool.h  类的定义文件

新的掩码结构体支持文件在这里(被从ooc.h里分离出来了):
https://gitee.com/versaloon_simo ... ilities/ooc_class.h


还请多多拍砖。

出0入296汤圆

发表于 2018-12-15 03:22:16 | 显示全部楼层
dxgdsx 发表于 2018-11-3 12:20
老师言重了!
显然,你在这个领域比我们绝大数更有经验,遇到的案例也更多,因而看问题的层次也更高。
不 ...

为了你的吐槽,我绞尽脑汁,终于改进了掩码结构体,你再看看吧。改进看楼上。

出0入0汤圆

发表于 2018-12-15 08:52:36 | 显示全部楼层
Gorgon_Meducer 发表于 2018-12-15 03:20
掩码结构体我又改进了,之前的缺点都去掉了:

现在移除了以下限制:

收藏细读

出0入0汤圆

发表于 2018-12-15 11:51:00 | 显示全部楼层
支持 各方观点,方法本没有错,错在没理解方法含义的人

出0入0汤圆

发表于 2018-12-15 14:11:25 | 显示全部楼层
spacekey 发表于 2018-12-15 11:51
支持 各方观点,方法本没有错,错在没理解方法含义的人

不透明指针的用途就是:我保留所有细节,但给你我想给你的。

出0入8汤圆

发表于 2018-12-15 15:37:58 | 显示全部楼层
Gorgon_Meducer 发表于 2018-12-15 03:20
掩码结构体我又改进了,之前的缺点都去掉了:

现在移除了以下限制:

不错。
说到拍砖,倒不至于,只是面对这样的 OOP 框架,我总觉得自己的智商不够用。
开发有一定的学习曲线吧,学会、用熟了,倒也利索。
我是更倾向于不那么烧脑的做法。

出0入8汤圆

发表于 2018-12-15 16:19:43 | 显示全部楼层
mark。。。

出0入296汤圆

发表于 2018-12-15 17:46:22 来自手机 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-12-15 17:47 编辑
security 发表于 2018-12-15 15:37
不错。
说到拍砖,倒不至于,只是面对这样的 OOP 框架,我总觉得自己的智商不够用。
开发有一定的学习曲 ...


理解。其实只是用的话,在ooc_class.h的一开头就给出例子了。我问过很多其他人,都说现在很简单很易用,也没什么限制了。

出0入0汤圆

发表于 2020-7-31 09:08:45 | 显示全部楼层
这种在较大的项目中用得上

出0入0汤圆

发表于 2020-7-31 09:20:41 | 显示全部楼层
mark,收藏一下

出0入0汤圆

发表于 2020-7-31 09:58:39 | 显示全部楼层
Gorgon_Meducer 发表于 2018-12-15 03:20
掩码结构体我又改进了,之前的缺点都去掉了:

现在移除了以下限制:

无法访问,是权限的问题吗?

出0入296汤圆

发表于 2020-8-1 05:47:32 | 显示全部楼层
FireHe 发表于 2020-7-31 09:58
无法访问,是权限的问题吗?

不是,是通过掩码结构体隐藏了结构体的成员信息(有多少成员,成员各自的大小,成员的布局等等信息)。
对普通使用者来说,这个足以阻止他们简单的访问结构体内的信息了。

我在github上开源了
https://github.com/GorgonMeducer/PLOOC/tree/master/example

里面有MDK的例子工程,你可以体验下。

出0入0汤圆

发表于 2020-8-1 08:24:53 | 显示全部楼层
good,,,,,,

出0入0汤圆

发表于 2020-8-2 09:37:56 | 显示全部楼层
Gorgon_Meducer 发表于 2020-8-1 05:47
不是,是通过掩码结构体隐藏了结构体的成员信息(有多少成员,成员各自的大小,成员的布局等等信息)。
...

我是说上面Gitee的页面无法访问

现在这个Github上的是最新的吗?最后更新日期是5个月之前的了

我是想学习一下最新版本,没有什么限制的部分。头文件包含、单一声明的解决方案

出0入0汤圆

发表于 2020-8-2 11:50:28 | 显示全部楼层
Gorgon_Meducer 发表于 2018-10-30 18:08
这个方法是很危险的,基本上是 内存入侵和野指针的 温床。关键就是你给出了一个类型,却没有办法根据这个类 ...

typedef struct DevLed* DevLed_tp; 我一般这样定义,目前感觉还好

出0入296汤圆

发表于 2020-8-5 23:38:38 | 显示全部楼层
FireHe 发表于 2020-8-2 09:37
我是说上面Gitee的页面无法访问

现在这个Github上的是最新的吗?最后更新日期是5个月之前的 ...

https://github.com/GorgonMeducer/PLOOC

出0入296汤圆

发表于 2020-8-5 23:39:22 | 显示全部楼层
flash3g 发表于 2020-8-2 11:50
typedef struct DevLed* DevLed_tp; 我一般这样定义,目前感觉还好

萝卜青菜各有所爱吧。反正道理都摆在桌子上。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 07:02

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表