Darf ich die Verwendung und Logik des undurchsichtigen Zeigerkonzepts in C kennen?
Darf ich die Verwendung und Logik des undurchsichtigen Zeigerkonzepts in C kennen?
Antworten:
Ein undurchsichtiger Zeiger ist ein Zeiger, in dem keine Details der zugrunde liegenden Daten enthüllt werden (aus einer Wörterbuchdefinition: undurchsichtig: Adjektiv; nicht durchsichtig; nicht transparent ).
Zum Beispiel können Sie in einer Header-Datei deklarieren (dies ist aus einem Teil meines tatsächlichen Codes):
typedef struct pmpi_s *pmpi;
Dies deklariert einen Typ, pmpi
der ein Zeiger auf die undurchsichtige Struktur ist struct pmpi_s
, daher ist alles, was Sie als pmpi
undurchsichtigen Zeiger deklarieren , ein undurchsichtiger Zeiger.
Benutzer dieser Deklaration können frei Code schreiben wie:
pmpi xyzzy = NULL;
ohne die tatsächliche "Definition" der Struktur zu kennen.
Dann können Sie in dem Code, der die Definition kennt (dh dem Code, der die Funktionalität für die pmpi
Behandlung bereitstellt ), die Struktur "definieren":
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).
};
und einfach auf die einzelnen Felder zugreifen, was Benutzer der Header-Datei nicht können.
Weitere Informationen zu undurchsichtigen Zeigern finden Sie auf der Wikipedia-Seite .
Die Hauptanwendung besteht darin, Implementierungsdetails vor Benutzern Ihrer Bibliothek zu verbergen. Encapsulation (trotz allem, was die C ++ - Menge Ihnen sagen wird) gibt es schon lange :-)
Sie möchten gerade genug Details in Ihrer Bibliothek veröffentlichen, damit Benutzer sie effektiv nutzen können, und nicht mehr. Wenn Sie mehr veröffentlichen, erhalten Benutzer Details, auf die sie sich möglicherweise verlassen können (z. B. die Tatsache, dass sich die Größenvariable sz
an einer bestimmten Stelle in der Struktur befindet, was dazu führen kann, dass sie Ihre Steuerelemente umgehen und direkt bearbeiten.
Dann werden sich Ihre Kunden bitter beschweren, wenn Sie die Interna wechseln. Ohne diese Strukturinformationen ist Ihre API nur auf das beschränkt, was Sie bereitstellen, und Ihre Handlungsfreiheit in Bezug auf die Interna bleibt erhalten.
xyzzy
ja. Der opake Zeiger - Typ ist pmpi
und die opake Struktur ist struct pmpi
. Der Haupt Gebrauch davon ist mit Kapselung, die Fähigkeit , sich zu verstecken Details der Implementierung von den Benutzern zu tun. Ich werde die Antwort mit Details aktualisieren.
fopen
oder fclose
für FILE *
. Ob FILE
es wirklich undurchsichtig ist, hängt davon ab, was Sie darin finden. stdio.h
Wenn es dort vollständig deklariert ist und nicht nur ein Typ, der dem in dieser Antwort ähnelt, können Benutzer auf die Interna
typedef struct pmpi_s *pmpi;
nicht ideal ist: Jemand kann annehmen, dass z. B. void f(const pmpi val)
keine Nebenwirkungen auf sein Argument haben, die nicht wahr wären.
Undurchsichtige Zeiger werden in den Definitionen von Programmierschnittstellen (APIs) verwendet.
In der Regel sind sie Zeiger auf unvollständige Strukturtypen, die wie folgt deklariert sind:
typedef struct widget *widget_handle_t;
Ihr Zweck besteht darin, dem Client-Programm eine Möglichkeit zu bieten, einen Verweis auf ein von der API verwaltetes Objekt zu speichern, ohne etwas über die Implementierung dieses Objekts außer seiner Adresse im Speicher (dem Zeiger selbst) preiszugeben.
Der Client kann das Objekt weitergeben, in seinen eigenen Datenstrukturen speichern und zwei solche Zeiger vergleichen, unabhängig davon, ob sie gleich oder verschieden sind. Er kann jedoch die Zeiger nicht dereferenzieren, um einen Blick auf das Objekt zu werfen.
Der Grund dafür ist, dass das Client-Programm nicht von diesen Details abhängig wird, sodass die Implementierung aktualisiert werden kann, ohne dass Client-Programme neu kompiliert werden müssen.
Da die undurchsichtigen Zeiger typisiert sind, gibt es ein gutes Maß an Typensicherheit. Wenn wir haben:
typedef struct widget *widget_handle_t;
typedef struct gadget *gadget_handle_t;
int api_function(widget_handle_t, gadget_handle_t);
Wenn das Client-Programm die Reihenfolge der Argumente verwechselt, gibt es eine Diagnose vom Compiler, da struct gadget *
a struct widget *
ohne Umwandlung in a konvertiert wird .
Aus diesem Grund definieren wir struct
Typen, die keine Mitglieder haben. Jede struct
Deklaration mit einem anderen neuen Tag führt einen neuen Typ ein, der nicht mit zuvor deklarierten struct
Typen kompatibel ist .
Was bedeutet es für einen Kunden, abhängig zu werden? Angenommen, a widget_t
hat die Eigenschaften width und height. Wenn es nicht undurchsichtig ist und so aussieht:
typedef struct widget {
short width;
short height;
} widget_t;
dann kann der Client dies einfach tun, um die Breite und Höhe zu erhalten:
int widget_area = whandle->width * whandle->height;
Unter dem undurchsichtigen Paradigma müssten Zugriffsfunktionen (die nicht inline sind) verwendet werden:
// 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);
Beachten Sie, wie die widget
Autoren den short
Typ verwendet haben, um Platz in der Struktur zu sparen, und dass dies dem Client der nicht undurchsichtigen Schnittstelle zugänglich gemacht wurde. Angenommen, Widgets können jetzt Größen haben, in die sie nicht passen, short
und die Struktur muss sich ändern:
typedef struct widget {
int width;
int height;
} widget_t;
Der Client-Code muss jetzt neu kompiliert werden, um diese neue Definition zu übernehmen. Abhängig vom Tooling- und Bereitstellungsworkflow besteht möglicherweise sogar das Risiko, dass dies nicht erfolgt: Der alte Clientcode versucht, die neue Bibliothek zu verwenden, und verhält sich schlecht, indem er mit dem alten Layout auf die neue Struktur zugreift. Dies kann bei dynamischen Bibliotheken leicht passieren. Die Bibliothek wird aktualisiert, die abhängigen Programme jedoch nicht.
Der Client, der die undurchsichtige Oberfläche verwendet, arbeitet unverändert weiter und muss daher nicht neu kompiliert werden. Es wird nur die neue Definition der Accessor-Funktionen aufgerufen. Diese befinden sich in der Widget-Bibliothek und rufen die neu int
eingegebenen Werte korrekt aus der Struktur ab.
Beachten Sie, dass es historisch (und immer noch hier und da) auch eine mangelhafte Praxis gegeben hat, den void *
Typ als undurchsichtigen Grifftyp zu verwenden:
typedef void *widget_handle_t;
typedef void *gadget_handle_t;
int api_function(widget_handle_t, gadget_handle_t);
Mit diesem Schema können Sie dies ohne Diagnose tun:
api_function("hello", stdout);
Die Microsoft Windows-API ist ein Beispiel für ein System, in dem Sie beide Möglichkeiten haben können. Standardmäßig gibt es verschiedene Handle-Typen wie HWND
(Fenster-Handle) und HDC
(Gerätekontext) void *
. Es gibt also keine Typensicherheit; a HWND
könnte versehentlich dort übergeben werden, wo a HDC
erwartet wird. Wenn du das tust:
#define STRICT
#include <windows.h>
Anschließend werden diese Handles gegenseitig inkompatiblen Typen zugeordnet, um diese Fehler abzufangen.
Undurchsichtig, wie der Name schon sagt, können wir nicht durchschauen. ZB ist Holz undurchsichtig. Ein undurchsichtiger Zeiger ist ein Zeiger, der auf eine Datenstruktur verweist, deren Inhalt zum Zeitpunkt seiner Definition nicht verfügbar ist.
Beispiel:
struct STest* pSTest;
Es ist sicher, NULL
einem undurchsichtigen Zeiger zuzuweisen .
pSTest = NULL;