c struct 結構體無定義, 但只用作指針, 這種用法怎麼理解?

今天看sqlite 源碼, 發現這樣一個用法:

typedef struct sqlite3_blob sqlite3_blob;

這裡的 sqlite3_blob 在源碼里根本沒有定義, 跟我印象中的前置聲明有些不一樣,我一直認為前置聲明最後都會在代碼某處做定義。但也發現sqlite3_blob的用法只是用作指針, 這個指針會被轉型(cast)到 另外一個定義了的結構體

Incrblob *p = (Incrblob *)pBlob; // sqlite3_blob *pBlob

是不是這種用法就是個 c 的 interface? 或者說是

typedef void sqlite3_blob 的一個等效? 這麼做會有什麼好處嗎?

想請教一下大家,謝謝!


1、不需要調用者知道具體的數據結構

2、不同類型不能混淆。用錯了會被編譯器檢查出來,如果都是用void*,則不能。


知乎上的第一答,大家輕噴……

聲明不一定要有具體的定義。就好比我根本不懂生物,可以我還可以作為民科瞎扯轉基因的害處……但是……一旦問到具體關於生物的問題,額……

struct SomeType;
SomeType* p; //雖然你不知道這貨是什麼,就引用一下又不會死
SomeType i; //你連這貨是什麼都不知道,怎麼分配內存!
p = NULL; //合法(只是引用個內存而已)
*p //不合法(結果到底是什麼?)

指針其實包含兩個要素:dereference後的類型和開始地址。這個類型可以是一個未知的類型,void* 和 SomeType* 有一個相似的地方,就是編譯器都不知道dereference以後是什麼東西(但是不同的是編譯器至少知道SomeType* dereference 以後的類型名叫SomeType。void* 是個例外,void* 不代表dereference了以後的結果是void類型),這就是為什麼void* 和 SomeType都不允許dereference

既然都不能 dereference 為什麼不用void代替SomeType呢?因為void* 表示告訴編譯器我完全不關心指針的類型

int a;
double b;
void* x = a; // 合法
x = b; //合法
x = (SomeType*)0x12345; //合法
void foo(void*);
foo(a); foo(b); //都合法

但是SomeType* 只接受指向SomeType

int a;
double b;
SomeType* x = a; // 非法
x = b; //非法
x = (SomeType*)0x12345; //合法
void foo(SomeType*);
foo(a); foo(b); //都非法

至於為什麼這麼用,沒有看過sqlite的源碼,但是原因可能有很多,比如可以讓邏輯清晰,而且讓編譯器在編譯時發現一些錯誤(比如上面的例子,void* 編譯器就完全不類型檢查了)

然後另一方面,這種技術可以實現類似C++里的私有成員的保護機制,不讓調用著隨便修改關鍵的內存變數。假設我在開發一個lib,我在header里只提供相關數據類型的聲明,而不是定義。調用lib的程序中如果有人嘗試修改lib相關的數據結構的內容,編譯器就會報錯因為沒法deference。


沒看過題主講的sqlite源碼。
不過無論什麼類型的結構體,它的指針在內存中都只是一個指定位數的無符號數(一般位數與操作系統位數相同,即64位系統對應64位指針)。

也就是說將不同類型的指針相互轉換,這個操作本身是沒有問題的。容易出問題的是讀寫成員數據的操作。這裡為演示我們定義兩個結構體:

struct A
{
int x;
int y;
};
struct B
{
int x;
};

首先訪問成員變數這個操作是怎麼實現的呢?舉例說明,此處我們要訪問A結構體的成員y:

struct A a;
a.y = 233;

/*
struct A // 假設在內存中的地址為 p_a
{
int x; // 則地址為 p_a
int y; // 地址為 p_a + sizeof(A.x)
};
*/

很顯然,訪問 A.y 是通過訪問 A.y 的指針(也就是A的指針+x的位長)來實現的。那麼下例就很好理解了:

int test()
{
struct B b; // OK
struct A * a = b; // OK
a-&>y = 233; // CRASH
return 0;
}

很顯然,這裡內存越界了。但是假如我們重寫這個測試函數:

int test_new()
{
struct A a; //OK
struct B * b = a; //OK
b-&>x = 233; //OK
return 0;
}

這樣是不會有任何問題的。而且這樣一來,B指針的使用者正常情況下只能操作到A.x,保護了A.y。

所以題主把它理解為介面是可行的。


Incomplete type


typedef struct A A;

其中,struct A是一種類型,可以定義struct A類型的變數,不過這麼寫太麻煩,就給"struct A"起了個別名"A"方便使用。

我是這麼理解的。


推薦閱讀:

Sqlite資料庫最大可以多大呀?會不會像acc資料庫那樣,幾十MB就暴掉了?

TAG:C編程語言 | C | SQLite | CC | SQLite3 |