GCC attribute 語法
GCC 的attribute語法可以爲函數,結構體,類,枚舉, 變量,標籤等添加屬性。 例如爲函數添加屬性__attribute__((noreturn)),可以讓編譯器知道該函數不會返回給調用者,並作出相應優化。
在Windows專用修飾符中也有類似gcc attribute語法的關鍵字 —— __declspec. 在追求跨平臺通用性的時候,通常會同時加入gcc __attribute__ 和windows __declspec,在編譯時判斷平臺,決定使用哪一種方式
__declspec( 屬性 ) 聲明語句 // 舉例 __declspec( dllimport ) int i; __declspec( dllexport ) void func();
Q: __attribute__((屬性列表)) 到底寫在什麼位置?
A: 相同作用對象添加不同屬性,或相同屬性作用於不同對象,有的緊跟關鍵字,有的在整個聲明之後。具體可查閱官方文檔
//可以緊跟在 struct, class, union, enum 關鍵字之後,也可以在右括號之後,首選前者
struct __attribute__ ((aligned (8))) S { short f[3]; }; //爲結構體S添加屬性 aligned (8)
//在typdef聲明中使用
typedef int T1 __attribute__ ((deprecated)); //T1在任意地方被使用時,deprecated屬性會產生告警
//函數
__attribute__((屬性列表)) void foo1 (void);
void foo2 (void) __attribute__((屬性列表));
Q: 有哪些屬性可以用?
A: 對於函數,結構體,類等不同的作用對象,都有各自支持的屬性,其中又分爲最常用的公共屬性和針對特定CPU架構的特殊屬性,詳情請查閱官方文檔。
visibility 屬性
visibility 屬性用於指定可見性,可以用於 函數, class, struct, union, enum,使用語法一致。
void __attribute__ ((visibility ("protected"))) f () { /* Do something. */; }
int i __attribute__ ((visibility ("hidden")));
屬性值:
- default:具有外部鏈接性 (external linkage),可以被外部其它模塊引用,並且有可能被重寫
- hidden:只能在同一共享對象(可簡單理解爲庫文件)中被引用
- internal:無法被其它模塊直接引用,但是可以通過指針間接引用
- protected:可以被引用,但無法被重寫
visibility 屬性使用方法
在講visibility屬性用法之前 ,我們先了解一下,
__declspec( 屬性 ) 聲明語句
// 舉例
__declspec( dllimport ) int i;
__declspec( dllexport ) void func();
此處我們看gcc wiki中的一例經典模板,可以用於定義共享庫
/* 定義宏:
標識符: FOX_HELPER_DLL_IMPORT default,指定函數, class, struct 等爲公開可重寫的。不編譯,直接導入。
標識符: FOX_HELPER_DLL_EXPORT default,指定函數, class, struct 等爲公開可重寫的。編譯並導出爲動態庫(DLL或so)
標識符: FOX_HELPER_DLL_LOCAL hidden,指定函數, class, struct 等只能在同一共享對象中被引用 */
/* 對於win32和Cygwin,使用__declspec()指定屬性 */
#if defined _WIN32 || defined __CYGWIN__
#define FOX_HELPER_DLL_IMPORT __declspec(dllimport) //從dll導入。其它模塊可見
#define FOX_HELPER_DLL_EXPORT __declspec(dllexport) //導出到dll。其它模塊可見
#define FOX_HELPER_DLL_LOCAL
#else
/*對於gcc,使用__attribute__ ((visibility ("屬性值"))) 指定可見性*/
#if __GNUC__ >= 4
#define FOX_HELPER_DLL_IMPORT __attribute__ ((visibility ("default")))
#define FOX_HELPER_DLL_EXPORT __attribute__ ((visibility ("default")))
#define FOX_HELPER_DLL_LOCAL __attribute__ ((visibility ("hidden")))
#else
/*代碼中已經引用了宏,即使什麼屬性都不加,也要定義宏*/
#define FOX_HELPER_DLL_IMPORT
#define FOX_HELPER_DLL_EXPORT
#define FOX_HELPER_DLL_LOCAL
#endif
#endif
/*定義宏 FOX_API 與 FOX_LOCAL
標識符: FOX_API default,指定函數, class, struct 等爲公開可重寫的,要麼編譯導出,要麼直接導入
標識符: FOX_LOCAL hidden,指定函數, class, struct 等只能在同一共享對象中被引用 */
#ifdef FOX_DLL // 如果FOX需要被編譯爲動態庫
#ifdef FOX_DLL_EXPORTS // 如果是正在構建 (而非使用) FOX
#define FOX_API FOX_HELPER_DLL_EXPORT //導出動態庫
#else
#define FOX_API FOX_HELPER_DLL_IMPORT //不構建,直接導入動態庫
#endif // FOX_DLL_EXPORTS
#define FOX_LOCAL FOX_HELPER_DLL_LOCAL //定義FOX_LOCAL
#else // 沒有定義 FOX_DLL 宏,則說明FOX是靜態庫,不添加任何屬性
#define FX_API
#define FOX_LOCAL
#endif // FOX_DLL
引用方式:
//聲明一個公開可重寫(default)的函數
extern FOX_API PublicFunc1(); //extern __attribute__ ((visibility ("default"))) PublicFunc1();
//聲明一個僅內部使用(hidden)的函數
extern FOX_LOCAL PublicFunc2(); //extern __attribute__ ((visibility ("hidden"))) PublicFunc2();
//聲明一個公開可重寫(default)的類
class FOX_API PublicClass1; //class __attribute__ ((visibility ("default"))) PublicClass1;
//聲明一個僅內部使用(hidden)的類
class FOX_LOCAL PublicClass2; //class __attribute__ ((visibility ("hidden"))) PublicClass2;
實際應用 - cpython
研究過cpython的朋友看到以上demo可能會有點眼熟。是的,cpython中有一段相似的代碼。當然,cpython中還兼容了clang。
#ifndef Py_EXPORTS_H
#define Py_EXPORTS_H
/* 定義宏:
標識符: Py_IMPORTED_SYMBOL default,指定函數, class, struct 等爲公開可重寫的。不編譯,直接導入
標識符: Py_EXPORTED_SYMBOL default,指定函數, class, struct 等爲公開可重寫的。編譯導出到動態庫(DLL或so)
標識符: Py_LOCAL_SYMBOL hidden,指定函數, class, struct 等只能在同一共享對象中被引用 */
/* 對於win32和Cygwin,使用__declspec()指定屬性 */
#if defined(_WIN32) || defined(__CYGWIN__)
#define Py_IMPORTED_SYMBOL __declspec(dllimport) //從dll導入。其它模塊可見
#define Py_EXPORTED_SYMBOL __declspec(dllexport) //導出到dll。其它模塊可見
#define Py_LOCAL_SYMBOL
#else
//__has_attribute參數爲屬性名,可以評估當前編譯目標是否支持該屬性,支持爲1,不支持爲0
//下面三行代碼用於兼容non-clang編譯器。對於non-clang編譯器,__has_attribute(x)表達式直接轉0
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
//如果 gcc版本>=4 或 clang判定編譯目標支持visibility屬性,則添加屬性
#if (defined(__GNUC__) && (__GNUC__ >= 4)) ||
(defined(__clang__) && __has_attribute(visibility))
#define Py_IMPORTED_SYMBOL __attribute__ ((visibility ("default")))
#define Py_EXPORTED_SYMBOL __attribute__ ((visibility ("default")))
#define Py_LOCAL_SYMBOL __attribute__ ((visibility ("hidden")))
#else
#define Py_IMPORTED_SYMBOL
#define Py_EXPORTED_SYMBOL
#define Py_LOCAL_SYMBOL
#endif
#endif
#endif /* Py_EXPORTS_H */
Py_LOCAL_SYMBOL
:在源碼之中暫未發現引用之處Py_IMPORTED_SYMBOL
:用於在Windows編譯器或cygwin中,構建非核心模塊時定義宏PyAPI_FUNC
,PyAPI_DATA
,意義是直接導入核心模塊,防止編譯器再次編譯。原因參考此處。Py_EXPORTED_SYMBOL
:定義宏PyAPI_FUNC
,PyAPI_DATA
,PyMODINIT_FUNC
以下代碼中會用到的一些宏標識符,先進行一下說明:
Py_ENABLE_SHARED
值爲1 ,windows平臺下,Python核默認在DLL中,允許外部鏈接性HAVE_DECLSPEC_DLL
所有windows編譯器和cygwin均會定義,用於支持__declspec().Py_BUILD_CORE
構建Python內核。提供對Python內部構件的訪問權,但不應被第三方模塊使用。Py_BUILD_CORE_MODULE
構建一個Python stdlib模塊作爲一個動態庫,Windows上導出“PyInit_xxx”符號。
/*不同平臺構建的差異請參考:https://docs.python.org/zh-cn/3/extending/windows.html#a-cookbook-approach*/
/* 首先處理windows平臺及cygwin */
#if defined(Py_ENABLE_SHARED) || defined(__CYGWIN__)
# if defined(HAVE_DECLSPEC_DLL)
//構建Python內核,而非Python stdlib模塊
# if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
# define PyAPI_FUNC(RTYPE) Py_EXPORTED_SYMBOL RTYPE
# define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE
//核心模塊初始化函數不需要外部鏈接性,除非Cygwin來處理嵌入
# if defined(__CYGWIN__)
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
# else /* __CYGWIN__ */
# define PyMODINIT_FUNC PyObject*
# endif /* __CYGWIN__ */
# else /* Py_BUILD_CORE */
//核心構建結束,接下來構建擴展模塊(非核心模塊)或嵌入式環境,需要先導入公共函數及數據
//使用cygwin會自動導入公共函數防止編譯,如果沒用使用cygwin,則需顯式導入
# if !defined(__CYGWIN__)
# define PyAPI_FUNC(RTYPE) Py_IMPORTED_SYMBOL RTYPE
# endif /* !__CYGWIN__ */
# define PyAPI_DATA(RTYPE) extern Py_IMPORTED_SYMBOL RTYPE
//按C語言的語法,編譯導出非核心模塊的初始化函數
# if defined(__cplusplus)
# define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject*
# else /* __cplusplus */
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
# endif /* __cplusplus */
# endif /* Py_BUILD_CORE */
# endif /* HAVE_DECLSPEC_DLL */
#endif /* Py_ENABLE_SHARED */
/*如果任然未定義PyAPI_FUNC, PyAPI_DATA, PyMODINIT_FUNC, 說明不是windows平臺或cygwin。默認採用gcc方式定義*/
#ifndef PyAPI_FUNC
//__attribute__ ((visibility ("default"))) RTYPE
# define PyAPI_FUNC(RTYPE) Py_EXPORTED_SYMBOL RTYPE
#endif
#ifndef PyAPI_DATA
//extern __attribute__ ((visibility ("default"))) RTYPE
# define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE
#endif
//無論是核心還是非核心模塊,都需要外部鏈接性
#ifndef PyMODINIT_FUNC
# if defined(__cplusplus)
//extern "C" __attribute__ ((visibility ("default"))) PyObject*
# define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject*
# else /* __cplusplus */
//__attribute__ ((visibility ("default"))) PyObject*
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
# endif /* __cplusplus */
#endif
注:以上說的Windows平臺/編譯器,默認指Microsoft Visual C++,但理論上其它編譯器也可以支持
今天研究的是gcc,接下來按照gcc處理方式分析PyAPI_FUNC
, PyAPI_DATA
, PyMODINIT_FUNC
是如何使用的。
PyAPI_FUNC
以pycore_hashtable.h
中的_Py_hashtable_compare_direct方法爲例
extern "C" PyAPI_FUNC(int) _Py_hashtable_compare_direct {//函數體略};
根據define的使用方法替換一下,此處就相當於
extern "C" Py_EXPORTED_SYMBOL int _Py_hashtable_compare_direct {//函數體略};
繼續替換
extern "C" __attribute__ ((visibility ("default"))) int _Py_hashtable_compare_direct {//函數體略};
到這一步我們就清晰了,這裏其實是定義了一個公開的可以被其它模塊調用或重寫的函數int _Py_hashtable_compare_direct
結論:PyAPI_FUNC
指定函數可以被各個模塊訪問。
PyAPI_DATA
以boolobject.h
中的PyBool_Type
爲例
PyAPI_DATA(PyTypeObject) PyBool_Type; //PyTypeObject是一個結構體
替換
extern Py_EXPORTED_SYMBOL PyTypeObject PyBool_Type;
繼續替換
extern __attribute__ ((visibility ("default"))) PyTypeObject PyBool_Type;
結果是聲明瞭一個可以被其它模塊訪問的PyTypeObject結構體變量。
結論:PyAPI_DATA
指定變量可以被其它模塊訪問
PyMODINIT_FUNC
以arraymodule.h
的中的模塊初始化函數爲例
PyMODINIT_FUNC
PyInit_array(void)
{
return PyModuleDef_Init(&arraymodule);
}
替換
//c語言
Py_EXPORTED_SYMBOL PyObject* PyInit_array(void)
{
return PyModuleDef_Init(&arraymodule);
}
//C++
extern "C" Py_EXPORTED_SYMBOL PyObject* PyInit_array(void)
{
return PyModuleDef_Init(&arraymodule);
}
到這裏可以看出來,PyInit_array()
函數調用了PyModuleDef_Init()
方法對arraymodule
進行了初始化,然後然返回一個PyObject
結構體的指針變量
繼續替換
//c語言
__attribute__ ((visibility ("default"))) PyObject* PyInit_array(void)
{
return PyModuleDef_Init(&arraymodule);
}
//C++
extern "C" __attribute__ ((visibility ("default"))) PyObject* PyInit_array(void)
{
return PyModuleDef_Init(&arraymodule);
}
結果是定義了一個其它模塊可以訪問的模塊初始化函數,用於初始化arraymodule
,並返回PyObject
結構體的指針變量
結論:PyMODINIT_FUNC
指定模塊初始化函數可以被其它模塊訪問,並返回PyObject
結構體的指針變量
總結
-
GCC 的attribute語法可以爲函數,結構體,類,枚舉, 變量,標籤等添加屬性。語法:
__attribute__ ((屬性列表))
位置一般在關鍵字之後,或整個聲明之後,具體查閱官方文檔
-
visibility屬性
- default:具有外部鏈接性 (external linkage),可以被外部其它模塊引用,並且有可能被重寫
- hidden:只能在同一共享對象(可簡單理解爲庫文件)中被引用
- internal:無法被其它模塊直接引用,但是可以通過指針間接引用
- protected:可以被引用,但無法被重寫
extern __attribute__ ((visibility ("default"))) PublicFunc1(); class __attribute__ ((visibility ("default"))) PublicClass1;
-
visibility屬性實際應用之cpython
-
PyAPI_FUNC
:指定函數可以被各個模塊訪問 -
PyAPI_DATA
: 指定變量可以被其它模塊訪問 -
PyMODINIT_FUNC
: 指定模塊初始化函數可以被其它模塊訪問,並返回PyObject
結構體的指針變量
-