標籤:

typeid如何得出變數的類型?

比如一個int i=1;的代碼,使用typeid(i).name()就可以知道i是什麼類型,感覺好神奇,因為我的理解是程序只是給typeid傳遞了那個i變數的地址而已,但最後程序是如何通過這個地址求出i的類型呢?原理是什麼…


題主先好好理解一下C++的typeid運算符到底是什麼意思,再問「原理是什麼」會比較好。

先看這裡學習typeid是什麼意思:typeid operator

針對題主給的例子:

int i = 1;
const char* name = typeid(i).name();

這裡的typeid(i)根本不需要做任何運行時動作,而是純編譯時行為——它使用變數i的靜態類型直接就知道這是對int類型做typeid運算,於是可以直接找出int對應的std::type_info對象返回出來。

If expression is not a glvalue expression of polymorphic type, typeid does not evaluate the expression, and the std::type_info object it identifies represents the static type of the expression. Lvalue-to-rvalue, array-to-pointer, or function-to-pointer conversions are not performed.

此時的typeid運算符就跟sizeof運算符的大部分情況一樣,只需要編譯器算出表達式的靜態類型就足夠了。

算出表達式的靜態類型是C++編譯器的基本功能了,類型檢查、類型推導等許多功能都依賴它。

而當typeid運算符應用在一個指向多態類型對象的指針上的時候,typeid的實現才需要有運行時行為。

If expression is a glvalue expression that identifies an object of a polymorphic type (that is, a class that declares or inherits at least one virtual function), the typeid expression evaluates the expression and then refers to the std::type_info object that represents the dynamic type of the expression. If the glvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value, an exception of type std::bad_typeid or a type derived from std::bad_typeid is thrown.

實際實現的時候,通常是在類的vtable里會有個slot保存著指向該類對應的std::type_info對象的指針。

要形象的理解的話,請參考我在另一個回答里畫的圖:為什麼bs虛函數表的地址(int*)(bs)與虛函數地址(int*)*(int*)(bs) 不是同一個? - RednaxelaFX 的回答

可以看到Clang++在LP64上用的vtable布局,不禁用RTTI時,在-8偏移量上的slot就是存typeinfo指針的。


其本質在於了std::type_info,無論是編譯還是運行。R大已經說了理論了,我補充一些源代碼給樓主參考,若題主感興趣的話。

在Clang中,前端的Parser會調用ParseCXXTypeId方法去識別typeid,clang/ParseExprCXX.cpp at master · llvm-mirror/clang · GitHub,而在這當中,除了識別keyword typeid,括弧等信息,關鍵則在於了Actions.ActOnCXXTypeid: clang/SemaExprCXX.cpp at master · llvm-mirror/clang · GitHub來執行返回的結果,而在Actions.ActOnCXXTypeid裡面則會通過PP.getIdentifierTable().get("type_info")從IdentifierTable去尋找有關std::type_info的有關東西,然後你也能從這個方法找到有關運行時多態的東西 clang/SemaExprCXX.cpp at master · llvm-mirror/clang · GitHub

// C++ [expr.typeid]p3:
// When typeid is applied to an expression other than an glvalue of a
// polymorphic class type [...] [the] expression is an unevaluated
// operand. [...]
if (RecordD-&>isPolymorphic() E-&>isGLValue()) {
// The subexpression is potentially evaluated; switch the context
// and recheck the subexpression.
ExprResult Result = TransformToPotentiallyEvaluated(E);
if (Result.isInvalid()) return ExprError();
E = Result.get();

// We require a vtable to query the type at run time.
MarkVTableUsed(TypeidLoc, RecordD);
WasEvaluated = true;
}
}

從而可以看出,Clang實現運行時的動態是通過vtable裡面插入的std::type_info來做的,然後在運行時通過取vtable的typeinfo來達到目的。


這裡的typeid是一個操作符,不是函數。所以和sizeof一樣,是編譯期就確定的。同時,typeid和sizeof另一個共同點是,接收的與其說是一個變數,不如說是一個類型。typeid(int)也行。


推薦閱讀:

最好的編譯器課程
動手寫一門簡單語言吧喵 從計算姬開始
Anders Hejlsberg講解現代編譯器構造
人生第一個解釋器, Lisp

TAG:C | 編譯原理 | CC |