[英]Late destruction of function parameters
根據n4640中的5.2.2 / 4“函數調用”(n4659中的8.2.2 / 4 ),在調用者的上下文中創建和銷毀函數參數。 並且允許實現將函數參數的破壞延遲到封閉的完整表達式的末尾(作為實現定義的特征)。 請注意,選擇不是未指定的 ,而是實現定義的 。
(
目前還不完全清楚這與3.3.3“塊范圍”(n4659中的6.3.3)是否一致,這似乎意味着函數參數具有塊范圍,然后是3.7.3“自動存儲持續時間”(6.7.3)在n4659)中,它表示塊作用域變量的存儲一直存在,直到它們被創建的塊退出。但是我們假設我在措辭中缺少/誤解了某些東西。
顯然現在函數參數將有自己的范圍 )
據我所知,ABI要求GCC和Clang將函數參數的銷毀延遲到完全表達式的末尾,即這是這些編譯器的實現定義行為。 我猜想在類似的實現中,只要在調用表達式中使用這些引用/指針,就可以返回對函數參數的引用/指針。
但是,以下示例在GCC中的段錯誤並且在Clang中正常工作
#include <iostream>
#include <string>
std::string &foo(std::string s)
{
return s;
}
int main()
{
std::cout << foo("Hello World!") << std::endl;
}
兩個編譯器都發出關於返回對局部變量的引用的警告,這在這里是完全合適的。 快速檢查生成的代碼表明,兩個編譯器確實將參數的破壞延遲到表達式的末尾。 但是,GCC仍然故意從foo
返回一個“空引用”,這會導致崩潰。 同時,Clang表現為“按預期”,返回對其參數s
的引用,該參數存活足夠長以產生預期輸出。
(簡單來說,GCC在這種情況下很容易愚弄
std::string &foo(std::string s)
{
std::string *p = &s;
return *p;
}
它修復了GCC下的段錯誤。)
在這種情況下GCC的行為是否合理,假設它保證參數的“晚期”破壞? 我是否遺漏了標准中的其他段落,即返回對函數參數的引用始終未定義,即使它們的生命周期是由實現擴展的?
據我所知,ABI要求GCC和Clang將函數參數的破壞延遲到完整表達式的末尾
這個問題在很大程度上依賴於這個假設。 讓我們看看它是否正確。 Itanium C ++ ABI草案3.1.1值參數說
如果類型具有非平凡的析構函數,則在封閉full-expression結束時,調用者在控制返回之后調用該析構函數(包括調用者拋出異常時)。
ABI沒有定義生命周期 ,所以讓我們檢查一下C ++標准草案N4659 [basic.life]
1.2 ...類型T的對象o的生命周期結束時:
1.3如果T是具有非平凡析構函數(15.4)的類類型,則析構函數調用開始,或者......
1.4對象占用的存儲被釋放,或被未嵌套在o中的對象重用([intro.object])。
C ++標准表示,在調用析構函數時,生命周期結束。 因此,ABI確實要求函數參數的生命周期擴展函數調用的完整表達式。
假設實現定義了需求,我在示例程序中看不到UB,因此它應該在任何保證遵循Itanium C ++ ABI的實現上具有預期的行為。 海灣合作委員會似乎違反了這一點。
GCC文檔確實說明了這一點
從GCC版本3開始,GNU C ++編譯器使用行業標准的C ++ ABI,即Itanium C ++ ABI。
因此,證明的行為可能被視為一個錯誤。
另一方面,不清楚[expr.call]改變措辭的后果是否是故意的。 結果可能被認為是一個缺陷。
...表示塊作用域變量的存儲一直持續到創建它們的塊為止。
確實。 但是你引用的[expr.call] / 4表示“函數參數是在 調用者的上下文中創建和銷毀的 ” 。 因此,存儲一直持續到函數調用塊的結尾。 似乎與存儲持續時間沒有沖突。
請注意,C ++標准鏈接指向從當前草稿定期生成的站點,因此可能與我引用的N4659不同。
從5.2.2 / 4函數調用[expr.call],在我看來GCC是正確的:
參數的生命周期在定義它的函數返回時結束。 每個參數的初始化和銷毀發生在調用函數的上下文中。
好吧,我從前C ++ 14標准中給出以下答案,閱讀C ++ 17,我認為GCC和Clang都是正確的:
來自:N4659 8.2.2 / 4函數調用[expr.call]
實現定義參數的生命周期是在定義它的函數返回時還是在封閉的完整表達式的末尾結束。 每個參數的初始化和銷毀發生在調用函數的上下文中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.