什么是C语言中的不透明指针?

104

请问在C语言中,不透明指针的概念是什么,它的用途和逻辑是什么?


6
可以的:http://zh.wikipedia.org/wiki/不透明指针 - David Heffernan
43
我无法理解维基页面上的任何内容。 - Renjith G
3
这个问题似乎已经被重复标记为C++,这并不理想。虽然两者有足够的共性,但C++具有C所没有的选项和功能。请注意不要改变原来的意思,让内容更通俗易懂。 - Jonathan Leffler
3个回答

153

不透明指针是指底层数据的细节未被公开的指针(根据词典定义:opaque: 形容词;不能透过看到;不透明的)。

例如,你可以在头文件中声明(以下是我实际代码的一部分):

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).
};

而用户无法做到的是轻松访问它们的各个字段。透明指针的更多信息可以在维基百科页面上找到。

其主要用途是隐藏库的实现细节,以防止用户对其进行访问。封装(尽管C++群众会告诉你相反)已经存在很长时间了 :-)

您希望仅发布足够的库信息,以使用户能够有效地使用它,不多也不少。 发布更多会向用户提供他们可能依赖的详细信息(例如,变量sz的大小在结构体中具有特定位置,这可能导致他们绕过您的控制并直接操纵它)。

然后,当您更改内部时,您将发现您的客户在强烈抱怨。没有那些结构信息,您的API仅限于您提供的内容,并且您关于内部的自由度得到保持。


3
这有什么用处呢? - Renjith G
11
不透明的_pointer_是“xyzzy”,是的。不透明指针类型是“pmpi”,不透明结构是“struct pmpi”。它的主要用途与封装有关,即隐藏实现细节以避免用户访问。我将在详细说明中更新答案。 - paxdiablo
1
非常感谢您的帮助。感谢您的合作。 - Renjith G
2
@VinothKumar,用户应该对不透明指针做的唯一事情就是从中获取或将其传递给那些“知道”其内部情况的函数,就像 FILE *fopenfclose 一样。 FILE 是否真正不透明取决于在 stdio.h 中找到的内容 - 如果它在那里被完全声明而不仅仅是类似于此答案中的类型,则用户可以访问内部,因此只有通过相互协议才能使其不透明 :-) - paxdiablo
4
注意,typedef struct pmpi_s *pmpi; 不是最理想的写法:有人可能会认为像 void f(const pmpi val) 这样的函数不会对其参数产生副作用,但事实并非如此。 - Dmitry Grigoryev
显示剩余4条评论

21

Opaque pointers在编程接口(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);

如果客户端程序混淆了参数的顺序,编译器会发出一条诊断信息,因为一个struct gadget *正在转换为struct widget *而没有进行强制类型转换。

这就是为什么我们定义了没有成员的struct类型的原因;每个带有不同新标记的struct声明都引入了一个新类型,它与先前声明的struct类型不兼容。

对于客户端变得依赖性是什么意思?假设一个 widget_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);

注意小部件的作者如何在结构中使用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);

微软的Windows API是一个可以两全其美的系统示例。默认情况下,各种句柄类型如窗口句柄 HWND 和设备上下文句柄 HDC 都是 void *。因此不存在类型安全,一个 HWND 可能会被错误地传递到期望一个 HDC 的位置。如果你这样做:

#define STRICT
#include <windows.h>

然后这些句柄被映射到彼此不兼容的类型,以捕获这些错误。


2

不透明,顾名思义,是指我们无法透过看到的东西。例如,木头是不透明的。不透明指针是指指向数据结构的指针,在其定义时其内容未被公开。

例子:

Original Answer翻译成"最初的回答"

struct STest* pSTest;

NULL 赋值给一个不透明指针是安全的。


pSTest = NULL; 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接