Обидва повинні приводити до однакової зручності використання, навіть якщо одна з них пов'язана, чи не так?
Ні, якщо не враховувати інші файли .c, включаючи той самий заголовок. Якщо визначення структури не видно компілятору, деталі цього визначення використовувати не можна. Декларація без визначення (наприклад, просто struct s;
) призводить до відмови компілятора, якщо що-небудь намагається заглянути всередину struct s
, при цьому все ж дозволяє йому, наприклад, компілювати struct s *foo;
(до тих пір, foo
поки пізніше не буде відменено).
Порівняйте ці версії api.h
та api.c
:
Definition in header: Definition in implementation:
+---------------------------------+ +---------------------------------+
| struct s { | | struct s; |
| int internal; | | |
| int other_stuff; | | extern void |
| }; | | api_func(struct s *foo, int x); |
| | +---------------------------------+
| extern void | +---------------------------------+
| api_func(struct s *foo, int x); | | #include "api.h" |
+---------------------------------+ | |
+---------------------------------+ | struct s { |
| #include "api.h" | | int internal; |
| | | int other_stuff; |
| void | | }; |
| api_func(struct s *foo, int x) | | |
| { | | void |
| foo->internal = x; | | api_func(struct s *foo, int x) |
| } | | { |
+---------------------------------+ | foo->internal = x; |
| } |
+---------------------------------+
Цей клієнт API працює з будь-якою версією:
#include "api.h"
void good(struct s *foo)
{
api_func(foo, 123);
}
Це стосується деталей щодо впровадження:
#include "api.h"
void bad(struct s *foo)
{
foo->internal = 123;
}
яка буде працювати з версією "визначення в заголовку", але не з версією "визначення в реалізації", оскільки в останньому випадку компілятор не має видимості компонування структури:
$ gcc -Wall -c bad.c
bad.c: In function 'bad':
bad.c:5: error: dereferencing pointer to incomplete type
$
Отже, версія "визначення у впровадженні" захищає від випадкових чи навмисних зловживань деталями приватного впровадження.