私は、3 つの関数すべてに未定義の動作があると確信しています。
f3
UB (またはf1
/でさえ)ではないと主張する人々へf2
: このコードを実行してみてください:
#include <iostream>
const char* const& f1() { return "hello1"; }
const char* const& f2() { return static_cast<const char*>("hello2"); }
const char* const& f3() { const char* const& r = "hello3"; return r; }
int main()
{
using namespace std;
//#define F f1
//#define F f2
#define F f3
const char* const& ret = F();
cerr << ret;
cerr << ",";
cerr << ret;
return 0;
}
(私はすぐにフラッシュするのcerr
ではなく、を使用しました。の2番目の出力の後にa に変更して追加できます。)cout
cerr
cout
cout << flush;
ret
私のGCCで私が印刷したものは次のとおりです。
- with
f1
: hello1,8??q?
(コンマの後のいくつかのランダムな文字)
- with
f2
: hello2,8j?y5
(コンマの後のいくつかのランダムな文字)
- with
f3
: hello3,,
(コンマの後の 2 つ目のコンマ)
それは私にはUBに非常に似ています...
(注:どちらかを削除するconst&
と、「機能します」。const&
実際に削除するのは、もちろん戻り型のものです。)
f3
それは、次のようなことが起こっているからだと思います。
const char* const& f3()
{
const char* __tmp001 = &("hello3"[0]); // "array decaying"
const char* const& r = __tmp001;
return r;
}
実際、文字列リテラル"hello3"
はaではなくconst char*
、 (static)const char [7]
です。コードconst char* const& r = "hello3";
では、参照をこの char 配列に直接バインドすることはできません。これは、型が同じではないためです。そのため、コンパイラは、暗黙的な変換 (配列からポインターへの参照がバインドされている ( demo )。このテンポラリconst char*
の有効期間は、参照の有効期間まで「延長」されるためr
、最初のセミコロンでは終了しませんが、関数が戻ると終了します ( demoおよびすべての最適化をオフにした出力)。したがって、 「ダングリング参照」f3
を返します. 私のテスト出力コードでは、スタックを上書きする後続の操作により、UB が表示されます。
jalf のコメントの後に編集:「2 番目の出力でガベージを出力する」ことは UB の証明ではないことを意識しています。UB を使用したプログラムは、期待どおりに動作することも、クラッシュすることも、何もしないこともできます。しかし、それにもかかわらず、明確に定義されたプログラム(UBなし)がそのようなゴミを出力するとは思いません...