Was ist ein undurchsichtiger Zeiger in C?

75

Darf ich die Verwendung und Logik des undurchsichtigen Zeigerkonzepts in C kennen?

Renjith G.
quelle
5
Ja, Sie können: en.wikipedia.org/wiki/Opaque_pointer
David Heffernan
24
Ich habe nichts von der Wiki-Seite verstanden.
Renjith G
1
Hmmm; Diese Frage zu C wurde in ein getaggtes C ++ dupliziert. Das ist nicht ideal. Es gibt genug Gemeinsamkeiten, dass es kein großes Problem ist, aber C ++ hat Optionen und Funktionen, die in C nicht verfügbar sind.
Jonathan Leffler

Antworten:

121

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, pmpider ein Zeiger auf die undurchsichtige Struktur ist struct pmpi_s , daher ist alles, was Sie als pmpiundurchsichtigen 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 pmpiBehandlung 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 szan 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.

paxdiablo
quelle
1
Nun, was nützt das?
Renjith G
6
Der undurchsichtige Zeiger ist xyzzyja. Der opake Zeiger - Typ ist pmpiund 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.
Paxdiablo
1
Vielen Dank für die Hilfe. Wir danken Ihnen für Ihre Zusammenarbeit.
Renjith G
2
@VinothKumar, das einzige, was ein Benutzer mit dem undurchsichtigen Zeiger tun sollte, ist, ihn von Funktionen abzurufen oder an Funktionen zu übergeben, die über seine Interna Bescheid wissen, genau wie fopenoder fclosefür FILE *. Ob FILEes wirklich undurchsichtig ist, hängt davon ab, was Sie darin finden. stdio.hWenn es dort vollständig deklariert ist und nicht nur ein Typ, der dem in dieser Antwort ähnelt, können Benutzer auf die Interna
zugreifen,
3
Beachten Sie, dass dies 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.
Dmitry Grigoryev
7

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 structTypen, die keine Mitglieder haben. Jede structDeklaration mit einem anderen neuen Tag führt einen neuen Typ ein, der nicht mit zuvor deklarierten structTypen kompatibel ist .

Was bedeutet es für einen Kunden, abhängig zu werden? Angenommen, a widget_that 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 widgetAutoren den shortTyp 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, shortund 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 inteingegebenen 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 HWNDkönnte versehentlich dort übergeben werden, wo a HDCerwartet wird. Wenn du das tust:

#define STRICT
#include <windows.h>

Anschließend werden diese Handles gegenseitig inkompatiblen Typen zugeordnet, um diese Fehler abzufangen.

Kaz
quelle
2

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, NULLeinem undurchsichtigen Zeiger zuzuweisen .

pSTest = NULL; 
Rahul
quelle