簡體   English   中英

沒有“靜態”或“外部”的“內聯”在 C99 中有用嗎?

[英]Is “inline” without “static” or “extern” ever useful in C99?

當我嘗試構建此代碼時

inline void f() {}

int main()
{
    f();
}

使用命令行

gcc -std=c99 -o a a.c

我收到 linker 錯誤(未定義對f的引用)。 如果我使用static inlineextern inline而不是僅inline ,或者如果我使用-O編譯(因此 function 實際上是內聯的),錯誤就會消失。

這種行為似乎在 C99 標准的第 6.7.4 (6) 段中定義:

如果翻譯單元中 function 的所有文件 scope 聲明都包含inline extern說明符,則該翻譯單元中的定義是內聯定義。 內聯定義不為 function 提供外部定義,也不禁止在另一個翻譯單元中進行外部定義。 內聯定義提供了外部定義的替代方案,翻譯器可以使用它在同一翻譯單元中實現對 function 的任何調用。 未指定對 function 的調用是使用內聯定義還是外部定義。

If I understand all this correctly, a compilation unit with a function defined inline as in the above example only compiles consistently if there is also an external function with the same name, and I never know if my own function or the external function is called.

這種行為是不是完全愚蠢? 在 C99 中定義沒有staticexterninline function 是否有用? 我錯過了什么嗎?

答案摘要

當然,我錯過了一些東西,而且這種行為並不愚蠢。 :)

正如Nemo 解釋的那樣,這個想法是把 function 的定義

inline void f() {}

在 header 文件中,只有一個聲明

extern inline void f();

在對應的.c文件中。 只有extern聲明會觸發外部可見二進制代碼的生成。 在 a.c 文件中確實沒有使用inline ——它只在標題中有用。

正如喬納森的回答中引用的 C99 委員會的基本原理所解釋的那樣, inline是關於編譯器優化的,需要定義 function 才能在調用現場可見。 這只能通過將定義放在 header 中來實現,當然 header 中的定義不能每次被編譯器看到時都發出代碼。 但由於編譯器並未強制內聯 function,因此外部定義必須存在於某處。

實際上,我認為這個出色的答案也回答了您的問題:

外部內聯做什么?

這個想法是“內聯”可以在 header 文件中使用,然后在 a.c 文件中使用“外部內聯”。 “extern inline”就是您如何指示編譯器哪個 object 文件應包含(外部可見)生成的代碼。

[更新,詳細]

我認為 a.c 文件中的“內聯”(沒有“靜態”或“外部”)沒有任何用處。 但是在 header 文件中它是有意義的,並且它需要在 some.c 文件中相應的“外部內聯”聲明才能實際生成獨立代碼。

從標准 (ISO/IEC 9899:1999) 本身來看:

附錄 J.2 未定義行為

  • ...
  • 具有外部鏈接的 function 使用inline function 說明符聲明,但未在同一翻譯單元 (6.7.4) 中定義。
  • ...

C99 委員會寫了一個理由,它說:

6.7.4 Function 說明符

C99 的一個新特性: inline關鍵字,改編自 C++,是一個函數說明符,只能在 function 聲明中使用。 它對於需要在調用現場可見 function 的定義的程序優化很有用。 (請注意,標准並未嘗試指定這些優化的性質。)

如果 function 具有內部鏈接,或者如果它具有外部鏈接並且調用與外部定義位於同一翻譯單元中,則可以確保可見性。 在這些情況下, inline關鍵字在 function 的聲明或定義中的存在沒有任何影響,只是表明應該優先優化對 function 的調用,而不是對沒有inline關鍵字聲明的其他函數的調用。

可見性是調用具有外部鏈接的 function 的問題,其中調用位於與函數定義不同的翻譯單元中。 在這種情況下, inline關鍵字允許包含調用的翻譯單元也包含 function 的本地或內聯定義。

一個程序可以包含一個帶有外部定義的翻譯單元,一個帶有內聯定義的翻譯單元,以及一個帶有聲明但沒有 function 定義的翻譯單元。 后一個翻譯單元中的調用將照常使用外部定義。

function 的內聯定義被認為是與外部定義不同的定義。 如果在可見內聯定義的情況下使用外部鏈接調用某些 function func時,行為與調用另一個 function(例如__func ,具有內部鏈接)相同。 符合要求的程序不得依賴於調用哪個 function。 這是標准中的內聯 model。

符合標准的程序不得依賴於使用內聯定義的實現,也不得依賴於使用外部定義的實現。 function的地址始終是外部定義對應的地址,但是當該地址用於調用function時,可能會使用內聯定義。 因此,以下示例的行為可能與預期不同。

 inline const char *saddr(void) { static const char name[] = "saddr"; return name; } int compare_name(void) { return saddr() == saddr(); // unspecified behavior }

由於實現可能對saddr的調用之一使用內聯定義,而對另一個調用使用外部定義,因此不能保證相等操作的計算結果為1(真)。 這表明在內聯定義中定義的 static 對象與其在外部定義中對應的 object 不同。 這激發了對甚至定義這種類型的非常量const的約束。

內聯添加到標准中的方式是,它可以使用現有的 linker 技術實現,並且 C99 內聯的子集與 C++ 兼容。 這是通過要求將一個包含內聯 function 定義的翻譯單元指定為提供 function 外部定義的翻譯單元來實現的。 由於該規范僅包含缺少inline關鍵字或同時包含inlineextern的聲明,因此 C++ 翻譯器也將接受它。

C99 中的內聯確實以兩種方式擴展了 C++ 規范。 首先,如果 function 在一個翻譯單元中被聲明為內inline ,則不需要在每個其他翻譯單元中聲明為inline 例如,這允許庫 function 內聯在庫中,但只能通過其他地方的外部定義獲得。 為外部 function 使用包裝器 function 的替代方法需要一個額外的名稱; 如果翻譯器實際上不進行內聯替換,它也可能對性能產生不利影響。

其次,內聯 function 的所有定義“完全相同”的要求被以下要求取代:程序的行為不應依賴於調用是用可見的內聯定義還是外部定義實現的function。 這允許內聯定義專門用於在特定翻譯單元中使用。 例如,庫 function 的外部定義可能包括一些參數驗證,這些參數驗證對於從同一庫中的其他函數進行的調用來說是不需要的。 這些擴展確實提供了一些優勢; 關心兼容性的程序員可以簡單地遵守更嚴格的 C++ 規則。

請注意,實現在標准頭文件中提供標准庫函數的內聯定義是不合適的,因為這可能會破壞一些在包含其頭文件后重新聲明標准庫函數的遺留代碼。 inline關鍵字僅旨在為用戶提供一種建議內聯函數的可移植方式。 因為標准頭文件不需要是可移植的,所以實現有其他選項:

 #define abs(x) __builtin_abs(x)

或其他用於內聯標准庫函數的不可移植機制。

> 我收到 linker 錯誤(未定義對f的引用)

在這里工作:Linux x86-64,GCC 4.1.2。 可能是您的編譯器中的錯誤; 我在引用的段落中沒有看到禁止給定程序的標准中的任何內容。 注意使用if而不是iff

內聯定義提供了外部定義的替代方案,翻譯器可以使用它在同一翻譯單元中實現對 function 的任何調用。

所以,如果你知道 function f的行為並且你想在一個緊密的循環中調用它,你可以將它的定義復制粘貼到一個模塊中以防止 function 調用; 或者,對於當前模塊,您可以提供一個等效的定義(但跳過輸入驗證或您可以想象的任何優化)。 然而,編譯器編寫者可以選擇優化程序大小。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM