Чи можу я знати використання та логіку концепції непрозорого вказівника в C?
Чи можу я знати використання та логіку концепції непрозорого вказівника в C?
Відповіді:
Непрозорий вказівник - це той, у якому не розкрито деталей базових даних (із визначення словника: непрозорий: прикметник; неможливо проглянути; не прозорий ).
Наприклад, ви можете оголосити у файлі заголовка (це з мого фактичного коду):
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 обмежується лише тим, що ви надаєте, і ваша свобода дій щодо внутрішніх органів зберігається.
xyzzy
так. Непрозорий покажчик типу є pmpi
і непрозорою структурою є struct pmpi
. Основне використання цього полягає в інкапсуляції, можливості приховувати деталі реалізації від користувачів. Я оновлю відповідь детально.
fopen
і fclose
для FILE *
. Чи FILE
справді непрозорий, залежить від того, що ви знайдете stdio.h
- якщо він там повністю задекларований, а не просто тип, подібний до того, що наведено у цій відповіді, користувачі можуть потрапити до внутрішніх органів, тож він непрозорий лише за взаємною згодою :-)
typedef struct pmpi_s *pmpi;
це не ідеально: хтось може припустити, що, наприклад, void f(const pmpi val)
не має побічних ефектів на аргументи, які не відповідають дійсності.
Непрозорі вказівники використовуються у визначеннях інтерфейсів програмування (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);
якщо клієнтська програма змішує порядок аргументів, буде виконана діагностика від компілятора, оскільки a struct gadget *
перетворюється на a struct widget *
без приведення.
Ось чому ми визначаємо struct
типи, які не мають членів; кожна struct
декларація з іншим новим тегом вводить новий тип, який не сумісний з раніше оголошеними struct
типами.
Що означає для клієнта стати залежним? Припустимо, що a 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);
Зверніть увагу, як 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);
API Microsoft Windows є прикладом системи, в якій ви можете мати її в обох напрямках. За замовчуванням це різні типи дескрипторів, такі як HWND
(дескриптор вікна) та HDC
(контекст пристрою) void *
. Отже, немає типу безпеки; a HWND
може бути передано там, де HDC
очікується a , помилково. Якщо ви зробите це:
#define STRICT
#include <windows.h>
тоді ці ручки відображаються на взаємосумісні типи, щоб виявити ці помилки.
Непрозорий, як випливає з назви, - це те, чого ми не можемо побачити. Наприклад, деревина непрозора. Непрозорий вказівник - це вказівник, який вказує на структуру даних, вміст якої не піддається на момент її визначення.
Приклад:
struct STest* pSTest;
Це безпечно призначити NULL
для непрозорого вказівника.
pSTest = NULL;