編譯器內部是如何處理 C 語言 typedef 關鍵字的?
在Nginx源碼里經常可以看到如下結構體定義:
typedef struct ngx_command_s ngx_command_t;
...
struct ngx_command_s {
...
};
- 編譯器內部是如何處理這個結構體定義的呢?
- typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?
- 為什麼不直接寫成如下定義呢:
typedef struct ngx_command_s {
...
} ngx_command_t;
這個問題我最開始看見也疑惑了一下,然後後面我想通了,我把我的思路展現一下。
這個問題可以這樣來解釋,首先我寫一個簡單的Case:
typedef struct obj x;
struct obj
{};
int main()
{}
然後我們來利用clang dump出來它的ast樹
clang -cc1 -ast-dump test.c
然後結果輸出是這樣的
TranslationUnitDecl 0x1002d5a3e80 &<&
|-RecordDecl 0x1002d5a4610 prev 0x1002d5a44c0 &
可以重點看加粗的部分,我們發現這裡的處理流程是識別出來了這是Record Declaration,但是後面還有一次Record Declaration,但是這裡的Record Declaration是標記為2:1,即我們定義struct的地方,這裡的輸出也說了是struct obj definition,並且指明了先前聲明的地方是0x1002d5a44c0 ,即RecordDecl 0x1002d5a44c0 &
typedef struct obj x;
來說的話,這裡的struct obj完成了struct obj的聲明。而對於typedef來說,所需要的格式也是typedef T type_ident;這樣子而已 ,只要我們後面可以保證T是一個完整的類型即可(如果你要使用這個type_ident的話)。
而對於我來說,我也順便去看了RecodeDecl類
/// RecordDecl - Represents a struct/union/class. For example:
/// struct X; // Forward declaration, no "body".
/// union Y { int A, B; }; // Has body with members A and B (FieldDecls).
/// This decl will be marked invalid if *any* members are invalid.
///
class RecordDecl : public TagDecl {
// ........
RecordDecl *getPreviousDecl() { 的確也如猜測的一般,有getPreviousDecl方法來獲取到聲明,並且這裡的注釋也闡明了有struct X這樣的前置聲明。
return cast_or_null&
static_cast&
}
const RecordDecl *getPreviousDecl() const {
return const_cast&
}
// .......
而你說為什麼不寫成 typedef struct obj{} x的格式,當然你可以,若我們dump此時的代碼:
TranslationUnitDecl 0x10022883e80 &<&
我們這裡只是在聲明的時候同時定義了而已,而先前的寫法是先聲明了,後面定義。
建議看c標準中關於define和declaration一節typedef是聲明不是定義,只是告訴編譯器:以後看到這個名字就用這個類型啊。。。比如,typedef map m,編譯器看到這裡是不會實例化這個模板的,只是記錄下名字對應關係,假如你這行和m實際起作用的位置的位元組對齊設置有區別,以後者為準(這是一個坑,可能碰到的哦)
依次來回答你的三個問題吧
1.編譯器看到struct關鍵字後,會嘗試下一個identifier,也就是這裡的ngx_command_s。如果有,那麼這就是一個帶tag的struct聲明。編譯器會在符號表中查找這個tag是不是已有聲明。若是,則取用之,否則創建新的struct tag聲明,並插入符號表。然後檢查是否有結構體的"體",也就是大括弧部分。若有,則在}後面,會將剛才這個struct tag的類型置為complete的。
2.typedef 只是聲明一個類型名字。沒有定義變數。因此ngx_command_t可以是incomplete類型。
3.可以。這只是nginx作者的習慣。你知道貝塞斯達嗎?就是做輻射系列和上古捲軸的那個遊戲公司。他裡面專門有一個招的職位就是C工程師。之前還嘗試過STM8標準下的C語言開發(英文直翻不知道這樣稱呼是否準確),做一個能自動包裝的機器。這個project現在還在做。做下來的感覺就是在操控硬體…
顯示全部
這兩種寫法並沒有什麼區別,具體寫哪種取決於作者學的時候看的書的寫法。
一般是跟著初學的書的寫法,後期改變習慣的也有,但是不多
根據我的經驗,C 程序員多寫struct cmd; // 很多還會多這一行聲明
typedef struct cmd XXXcmd;
struct cmd {
//
};
typedef struct cmd {
//
} XXXcmd;
2. typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?typedef就是聲明了一個類型的別名,不需要類型是完整的
- 編譯器內部是如何處理這個結構體定義:就是在問編譯期間建立符號表的過程咯?typedef只是聲明,不是定義,有些實現暫時不填它索引的表號,當結構出現後再填入…
- typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?:c給的福利,編譯器都要實現可以提前聲明…
- 為什麼不直接寫成如下定義呢:如果結構體內要定義當前類型的指針時,這就能看出便利了…
第三個問題回答一下,主要是存在結構體某元素可能使用該結構體指針的情況(這種情況常見於鏈表中。),此時,你再試試定義就明白了。指向本類型的指針,其類型你是用struct common_s 還是用common_t呢?你後面的那個聲明,在最後完成前common_t還沒聲明呢,不能用。而用struct common_s的話,將一個common_t型結構體對象的指針賦值給它時就會有類型不匹配的warnning。
編譯器內部是如何處理這個結構體定義的呢?
- typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?
- 為什麼不直接寫成如下定義呢:
大二的一個暑期project是寫的C語言子集的一個簡易解釋器。
使用的技術是,利用GNU的flex和bison,生成中間的語法樹結構,然後自己解析語義,執行。其中使用bison的關鍵是,要屬於C語言的BNF範式描述文檔(見編譯原理類的書),當初還沒開始學編譯原理(大三才開始學),各種尾遞歸看的各種頭疼,還見過很多從來沒用過的語法結構(你在C語言的教材里也看不到的,不常用的等)。。。----------------------------------------------------言歸正傳,Negin中的那種先寫聲明再定義的方式貌似對應C grammer里的「半結構定義」 的grammer。具體可查找ANSI C grammar 最新的 Lex 和 Yacc 描述ANSI C grammar (Yacc)---------ANSI C grammer 部分declaration_specifiers: storage_class_specifier
| storage_class_specifier declaration_specifiers | type_specifier | type_specifier declaration_specifiers | type_qualifier | type_qualifier declaration_specifiers | function_specifier | function_specifier declaration_specifiersstorage_class_specifier
: TYPEDEF| EXTERN
| STATIC | AUTO | REGISTER ;type_specifier
: VOID | CHAR | SHORT | INT| LONG
| FLOAT | DOUBLE | SIGNED | UNSIGNED | BOOL | COMPLEX | IMAGINARY | struct_or_union_specifier | enum_specifier | TYPE_NAME ;---struct的定義有如下三種方式
struct_or_union_specifier : struct_or_union IDENTIFIER "{" struct_declaration_list "}" | struct_or_union "{" struct_declaration_list "}" | struct_or_union IDENTIFIER ;struct_or_union : STRUCT | UNION ;----------------------------------------------------------------------------------------------對於問題2,符合grammer的話,就沒有錯。對於問題1,如何處理的問題,編譯器具體實現不知道,我能想到的也就是先register出現的struct名,等到掃描到定義的地方再加上定義了。對於問題3,個人習慣問題吧。推薦閱讀:
※C++ 中的 std::vector 為什麼可以越界訪問?
※以後的C++編譯器有沒有可能使用C#來編寫?
※編譯器或解釋器前端對於作用域的處理方法?
※GitHub 上有沒有什麼簡單精緻的編譯器源碼適合初學者研讀??
※為什麼大部分傑出的程序員都在內心傾向於研究操作系統和編譯器?
TAG:編譯器 |