请问C中不透明指针概念背后的用法和逻辑?
3 回答
不透明的指针是一个没有透露底层数据细节的指针(根据字典定义:不透明:形容词;不能被看穿;不透明)。
例如,您可以在头文件中声明(这是来自我的一些实际代码):
typedef struct pmpi_s *pmpi;
它声明了一个类型,该类型pmpi
是指向不透明结构 struct pmpi_s
的指针,因此您声明的任何内容都pmpi
将是不透明指针。
该声明的用户可以自由编写如下代码:
pmpi xyzzy = NULL;
不知道结构的实际“定义”。
然后,在知道定义的代码中(即提供pmpi
处理功能的代码,您可以“定义”结构:
struct pmpi_s {
uint16_t *data; // a pointer to the actual data array of uint16_t.
size_t sz; // the allocated size of data.
size_t used; // number of segments of data in use.
int sign; // the sign of the number (-1, 0, 1).
};
并轻松访问它的各个字段,这是头文件的用户无法做到的。
有关不透明指针的更多信息,请参见Wikipedia 页面。
它的主要用途是向库的用户隐藏实现细节。封装(尽管 C++ 人群会告诉你)已经存在了很长时间 :-)
您只想在您的库上发布足够的详细信息,以便用户有效地使用它,仅此而已。发布更多为用户提供了他们可能依赖的详细信息(例如 size 变量sz
位于结构中的特定位置的事实,这可能导致他们绕过您的控件并直接操作它。
然后,当你改变内部结构时,你会发现你的客户抱怨得很厉害。如果没有该结构信息,您的 API 将仅限于您提供的内容,并且您对内部的行动自由得以保持。
不透明指针用于编程接口 (API) 的定义。
通常它们是指向不完整结构类型的指针,声明如下:
typedef struct widget *widget_handle_t;
它们的目的是为客户端程序提供一种方法来保存对由 API 管理的对象的引用,除了它在内存中的地址(指针本身)之外,不透露有关该对象的实现的任何信息。
客户端可以传递对象,将其存储在自己的数据结构中,并比较两个这样的指针是否相同或不同,但它不能取消引用指针以查看对象中的内容。
这样做的原因是为了防止客户端程序依赖于这些细节,从而可以升级实现而无需重新编译客户端程序。
因为不透明指针是有类型的,所以有一个很好的类型安全措施。如果我们有:
typedef struct widget *widget_handle_t;
typedef struct gadget *gadget_handle_t;
int api_function(widget_handle_t, gadget_handle_t);
如果客户端程序混淆了参数的顺序,编译器会给出诊断,因为 astruct gadget *
被转换为 astruct widget *
而没有强制转换。
这就是我们定义struct
没有成员的类型的原因;每个struct
带有不同新标签的声明都会引入一种与先前声明的类型不兼容的新struct
类型。
客户变得依赖意味着什么?假设 awidget_t
具有宽度和高度属性。如果它不是不透明的并且看起来像这样:
typedef struct widget {
short width;
short height;
} widget_t;
然后客户端可以这样做来获取宽度和高度:
int widget_area = whandle->width * whandle->height;
而在不透明范式下,它必须使用访问函数(未内联):
// in the header file
int widget_getwidth(widget_handle_t *);
int widget_getheight(widget_handle_t *);
// client code
int widget_area = widget_getwidth(whandle) * widget_getheight(whandle);
请注意widget
作者如何使用该short
类型来节省结构中的空间,并且已向非透明接口的客户端公开。假设小部件现在可以具有不适合short
的尺寸并且必须更改结构:
typedef struct widget {
int width;
int height;
} widget_t;
现在必须重新编译客户端代码以获取这个新定义。根据工具和部署工作流程,甚至可能存在未完成的风险:旧客户端代码尝试使用新库并通过使用旧布局访问新结构而行为不端。动态库很容易发生这种情况。库已更新,但相关程序未更新。
使用不透明接口的客户端无需修改即可继续工作,因此不需要重新编译。它只是调用访问器函数的新定义。这些在小部件库中,并int
从结构中正确检索新的类型值。
请注意,从历史上看(现在仍然在这里和那里)也有使用该void *
类型作为不透明句柄类型的乏善可陈的做法:
typedef void *widget_handle_t;
typedef void *gadget_handle_t;
int api_function(widget_handle_t, gadget_handle_t);
在此方案下,您可以执行此操作,而无需任何诊断:
api_function("hello", stdout);
Microsoft Windows API 是一个系统示例,您可以在其中使用两种方式。默认情况下,HWND
(窗口句柄)和HDC
(设备上下文)等各种句柄类型都是void *
. 所以没有类型安全;aHWND
可能会HDC
错误地传递到预期 a 的地方。如果你这样做:
#define STRICT
#include <windows.h>
然后将这些句柄映射到相互不兼容的类型以捕获这些错误。
顾名思义,不透明是我们无法看透的东西。例如,木头是不透明的。不透明指针是指向数据结构的指针,该数据结构的内容在其定义时未公开。
例子:
struct STest* pSTest;
分配NULL
给不透明指针是安全的。
pSTest = NULL;