標籤:

編譯器內部是如何處理 C 語言 typedef 關鍵字的?

在Nginx源碼里經常可以看到如下結構體定義:

typedef struct ngx_command_s ngx_command_t;
...
struct ngx_command_s {
...
};

  1. 編譯器內部是如何處理這個結構體定義的呢?
  2. typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?
  3. 為什麼不直接寫成如下定義呢:

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 &<&&> &

|-TypedefDecl 0x1002d5a4380 &<&&> & implicit __int128_t "__int128"

|-TypedefDecl 0x1002d5a43e0 &<&&> & implicit __uint128_t "unsigned __int128"

|-TypedefDecl 0x1002d5a4470 &<&&> & implicit __builtin_va_list "char *"

|-RecordDecl 0x1002d5a44c0 & col:16 struct obj

|-TypedefDecl 0x1002d5a45b0 & col:20 x "struct obj":"struct obj"

|-RecordDecl 0x1002d5a4610 prev 0x1002d5a44c0 & line:2:8 struct obj definition

`-FunctionDecl 0x1002d5a46e0 & line:5:5 main "int ()"

`-CompoundStmt 0x1002d5a4780 &

可以重點看加粗的部分,我們發現這裡的處理流程是識別出來了這是Record Declaration,但是後面還有一次Record Declaration,但是這裡的Record Declaration是標記為2:1,即我們定義struct的地方,這裡的輸出也說了是struct obj definition,並且指明了先前聲明的地方是0x1002d5a44c0 ,RecordDecl 0x1002d5a44c0 & col:16 struct obj

所以,對於

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() {
return cast_or_null&(
static_cast&(this)-&>getPreviousDecl());
}
const RecordDecl *getPreviousDecl() const {
return const_cast&(this)-&>getPreviousDecl();
}
// .......

的確也如猜測的一般,有getPreviousDecl方法來獲取到聲明,並且這裡的注釋也闡明了有struct X這樣的前置聲明。

而你說為什麼不寫成 typedef struct obj{} x的格式,當然你可以,若我們dump此時的代碼:

TranslationUnitDecl 0x10022883e80 &<&&> &

|-TypedefDecl 0x10022884380 &<&&> & implicit __int128_t "__int128"

|-TypedefDecl 0x100228843e0 &<&&> & implicit __uint128_t "unsigned __int128"

|-TypedefDecl 0x10022884470 &<&&> & implicit __builtin_va_list "char *"

|-RecordDecl 0x100228844c0 & col:16 struct obj definition

|-TypedefDecl 0x100228845b0 & col:22 x "struct obj":"struct obj"

`-FunctionDecl 0x10022884660 & line:3:5 main "int ()"

`-CompoundStmt 0x10022884700 &

我們這裡只是在聲明的時候同時定義了而已,而先前的寫法是先聲明了,後面定義。


建議看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 {
//
};

C++ 程序員多寫

typedef struct cmd {
//
} XXXcmd;


2. typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?

typedef就是聲明了一個類型的別名,不需要類型是完整的


  1. 編譯器內部是如何處理這個結構體定義:就是在問編譯期間建立符號表的過程咯?typedef只是聲明,不是定義,有些實現暫時不填它索引的表號,當結構出現後再填入…
  2. typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?:c給的福利,編譯器都要實現可以提前聲明…
  3. 為什麼不直接寫成如下定義呢:如果結構體內要定義當前類型的指針時,這就能看出便利了…


第三個問題回答一下,主要是存在結構體某元素可能使用該結構體指針的情況(這種情況常見於鏈表中。),此時,你再試試定義就明白了。指向本類型的指針,其類型你是用struct common_s 還是用common_t呢?你後面的那個聲明,在最後完成前common_t還沒聲明呢,不能用。而用struct common_s的話,將一個common_t型結構體對象的指針賦值給它時就會有類型不匹配的warnning。


編譯器內部是如何處理這個結構體定義的呢?

  1. typedef定義在struct實際定義之前,為什麼沒有編譯錯誤呢?
  2. 為什麼不直接寫成如下定義呢:

大二的一個暑期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_specifiers

storage_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:編譯器 |