web-dev-qa-db-ja.com

g ++でコンパイルされる奇妙なコード

次のコードは、g ++ 4.8.1で正常にコンパイルされます。

_int main()
{
    int(*)();
}
_

関数へのポインタの単純な宣言のように見えます。

_int(*f)();
_

Clang3.4およびvc ++ 2013ではコンパイルされません。

それはコンパイラのバグですか、それとも標準の暗い場所の1つですか?


G ++ 4.8.1(更新)で正常にコンパイルされる同様の奇妙なコード部分のリスト:

  1. int(*)();

  2. int(*);

  3. int(*){};

  4. int(*());

これらの奇妙なコード部分を含む実例

更新1:@ ALi いくつか追加コメントの興味深い情報:

4つのケースすべてで、clang 3.5トランク(202594)でコンパイルエラーが発生し、gcc 4.9トランク(20140302)で正常にコンパイルされます。動作は_-std=c++98 -pedantic_と同じですが、int(*){};は理解できます。拡張初期化子リストは、_-std=c++11_でのみ使用できます。

更新2:As @ CantChooseUsernames 注記in 彼の答え 初期化しても正常にコンパイルされ、最適化が有効になっていない場合でも、g ++によってアセンブリは生成されません(初期化の有無にかかわらず)。

  1. int(*)() = 0;

  2. int(*) = 0;

  3. int(*){} = 0;

  4. int(*()) = 0;

初期化を使用した実例

更新3:int(*)() = "Hello, world!";も正常にコンパイルされることに驚いた(int(*p)() = "Hello, world!";は正常にコンパイルされない)もちろん、コンパイルします)。

更新4:素晴らしいですが、int(*){} = Hello, world!;は正常にコンパイルされます。そして、次の非常に奇妙なコードもあります:int(*){}() = -+*/%&|^~.,:!?$()[]{};実例 )。

更新5:As @ zwol 注記で 彼のコメント

これと関連する構文上の問題の数は、gcc bug 68265 として追跡されています。

70
Constructor

C++標準(セクション7宣言のp。#6)によると

6 init-declarator-list内の各init-declarator 正確に1つのdeclarator-idを含む、これはそのinit-declaratorによって宣言された名前であり、したがって宣言によって宣言された名前の1つです。

したがって、これは単にコンパイラのバグです。

MS VC++ 2010でコンパイルすることはできませんが、有効なコードはたとえば(表示された関数ポインター宣言を除いて)次のようになります。

int(*p){};

テストに使用しているコンパイラは、declarator-idなしで宣言を許可しているようです。

セクション8.1タイプ名の次の段落も考慮に入れてください

1型変換を明示的に指定するには、sizeof、alignof、new、またはtypeidの引数として、型の名前を指定する必要があります。これは、タイプIDを使用して実行できます。これは、エンティティの名前を省略した、そのタイプの変数または関数の構文上の宣言です。

15

これがどれだけ役立つかはわかりませんが、次のことを試しました(clang 3.3、g ++ 4.8.1):

_using P = int(*)();
using Q = int*;
P; // warning only
Q; // warning only
int(*)(); // error (but only in clang)
int*;     // error
int(*p)(); // ok
int *q;    // ok
_

一方、g ++ 4.8.2および4.9.0ではすべてが正常にコンパイルされます。残念ながら、私はclang3.4を持っていません。

非常に大まかに、宣言[isoセクション7]は、次の部分で構成されています。

  1. オプションのプレフィックス指定子(例:staticvirtual
  2. 基本タイプ(例:_const double_、_vector<int>_)
  3. 宣言子(例:n、_*p_、_a[7]_、f(int)
  4. オプションのサフィックス関数指定子(例:constnoexcept
  5. オプションの初期化子または関数本体(例:_= {1,2,3}_または_{ return 0; }_

現在、declaratorは、おおよそ名前と、オプションでいくつかの宣言演算子[iso8/4]で構成されています。

プレフィックス演算子、例:

  • _*_(ポインター)
  • _*const_(定数ポインター)
  • _&_(左辺値参照)
  • _&&_(右辺値参照)
  • auto(関数の戻り値の型、末尾の場合)

接尾辞演算子、例:

  • _[]_(配列)
  • _()_(関数)
  • _->_(関数の末尾の戻り値の型)

上記の演算子は、式での使用を反映するように設計されています。接尾辞演算子は接頭辞よりも厳密にバインドされ、括弧を使用して順序を変更できます。int *f()intへのポインターを返す関数ですが、int (*f)()はへのポインターです。 intを返す関数。

私は間違っているかもしれませんが、これらの演算子は名前なしで宣言に含めることはできないと思います。したがって、_int *q;_と書くと、intが基本型であり、_*q_はプレフィックス演算子_*_とそれに続く名前qで構成される宣言子です。ただし、_int *;_を単独で表示することはできません。

一方、_using Q = int*;_を定義する場合、Qが基本型であるため、宣言_Q;_はそれ自体で問題ありません。もちろん、何も宣言していないので、コンパイラのオプションによってはエラーや警告が出ることがありますが、これは別のエラーです。

上記は私の理解です。標準(例:N3337)の内容は[iso 8.3/1]です。

各宣言子には、正確に1つが含まれますdeclarator-id;宣言されている識別子に名前を付けます。 nqualified-iddeclarator-idで発生するものは、いくつかの特殊関数の宣言を除いて、単純なidentifierでなければなりません(12.3 [ユーザー定義の変換]、12.4 [デストラクタ]、13.5 [オーバーロードされた演算子])、およびテンプレートの特殊化または部分的な特殊化の宣言(14.7)。

(角括弧内の注記は私のものです)。したがって、int(*)();は無効である必要があることを理解しており、clangやg ++のバージョンによって動作が異なる理由はわかりません。

7
iavr

これを使用できます: http://gcc.godbolt.org/ アセンブリを表示します。

_int main()
{
    int(*)() = 0;
    return 0;
}
_

生成:

_main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret
_

これは次と同等です:int main() {return 0;}したがって、最適化が行われていなくても、gccはそのアセンブリを生成しません。警告またはエラーを表示する必要がありますか?私には手がかりはありませんが、名前のないfuncポインターについては何も気にしません。

しかしながら:

_int main()
{
    int (*p)() = 0;
    return 0;
}
_

最適化を行わないと、以下が生成されます。

_main:
    pushq   %rbp
    movq    %rsp, %rbp
    movq    $0, -8(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret
_

スタックに8バイトを割り当てます。

6
Brandon