用g ++编译的奇怪代码

Con*_*tor 70 c++ gcc g++ c++11

以下代码使用g ++ 4.8.1成功编译:

int main()
{
    int(*)();
}
Run Code Online (Sandbox Code Playgroud)

它看起来像一个函数指针的简单声明:

int(*f)();
Run Code Online (Sandbox Code Playgroud)

它不能用clang 3.4和vc ++ 2013编译.

它是编译器错误还是标准的黑暗之处?


使用g ++ 4.8.1编译好的类似奇怪代码的列表(更新):

  1. int(*)();

  2. int(*);

  3. int(*){};

  4. int(*());

这些奇怪的代码片段的实例.

更新1: @Ali在评论中添加了一些有趣的信息:

所有4个案例都使用clang 3.5 trunk(202594)进行编译错误,并使用gcc 4.9 trunk(20140302)进行编译.行为是一样的-std=c++98 -pedantic,除了int(*){};可以理解的; 扩展初始化程序列表仅适用于-std=c++11.

更新2:正如@CantChooseUsernames他的回答中指出的那样,即使没有任何启用的优化,即使初始化它们仍然可以正常编译并且没有为它们生成汇编(无论是否初始化):

  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:正如@zwol评论中指出的那样

这和许多相关的句法问题被跟踪为gcc bug 68265.

Vla*_*cow 15

根据C++标准(第7节声明中的第6页)

6 init-declarator-list 中的每个init-declarator只包含一个declarator-id,它是init-declarator声明的名称,因此声明声明了一个名称

所以它只是一个编译器错误.

有效的代码可能看起来像(除了你显示的函数指针声明),虽然我不能用我的MS VC++ 2010编译它.

int(*p){};
Run Code Online (Sandbox Code Playgroud)

您用于测试的编译器似乎允许声明而没有declarator-id.

还要考虑8.1节类型名称的以下段落

1要显式指定类型转换,并作为sizeof,alignof,new或typeid的参数, 应指定类型的名称.这可以使用type-id来完成,它在语法上是对该类型的变量或函数的声明,省略了实体的名称.


iav*_*avr 7

我不确定这有多大帮助,但我尝试了以下(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
Run Code Online (Sandbox Code Playgroud)

另一方面,g ++ 4.8.2和4.9.0中的所有内容都很好.不幸的是,我没有铿锵声3.4.

非常粗略地,声明[iso section 7]按顺序包含以下部分:

  1. 可选的前缀符(例如static,virtual)
  2. 基类型(例如const double,vector<int>)
  3. 说明符(例如n,*p,a[7],f(int))
  4. 可选后缀功能说明符(例如const,noexcept)
  5. 可选的初始化器或功能体(例如= {1,2,3}{ return 0; }

现在,说明符大致由名称和任选的一些说明符操作符[异8/4].

前缀运算符,例如:

  • * (指针)
  • *const (常量指针)
  • & (左值参考)
  • && (右值参考)
  • auto (函数返回类型,尾随时)

后缀运算符,例如:

  • [] (阵列)
  • () (功能)
  • -> (函数尾随返回类型)

上述运算符旨在反映它们在表达式中的用法.Postfix运算符绑定比前缀更紧密,括号可用于更改它们的顺序:int *f()是一个返回指针的函数int,而是int (*f)()一个返回函数的指针int.

也许我错了,但我认为这些运营商不能在声明中没有名字.因此,当我们编写时int *q;,则int是基类型,并且*q是由前缀运算符*后跟名称组成的声明符q.但int *;不能单独出现.

另一方面,当我们定义时using Q = int*;,声明Q;本身就很好,因为Q它是基类型.当然,因为我们没有声明任何内容,我们可能会根据编译器选项获得错误或警告,但这是一个不同的错误.

以上只是我的理解.标准(例如N3337)所说的是[iso 8.3/1]:

每个声明符只包含一个declarator-id ; 它命名声明的标识符.声明器id中出现的unqualified-id应该是一个简单的标识符,除了声明一些特殊函数(12.3 [ 用户定义的转换 ],12.4 [析构函数],13.5 [重载运算符])和模板特化声明或部分专业化(14.7).

(方括号中的注释是我的).所以我理解int(*)();应该是无效的,我不能说为什么它在clang和不同版本的g ++中有不同的行为.

  • 我刚刚发现`int(*){}()= - +*/%&| ^〜.,:!?$()[] {};`编译也很好!你怎么看待这件事?在新问题更新中查看实时示例. (3认同)

Bra*_*don 6

你可以使用这个:http://gcc.godbolt.org/来查看程序集..

int main()
{
    int(*)() = 0;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

产生:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret
Run Code Online (Sandbox Code Playgroud)

这相当于: int main() {return 0;} 所以即使没有优化,gcc也不会为它生成汇编.它应该发出警告或错误吗?我没有任何线索,但它不关心或做任何未命名的func指针.

然而:

int main()
{
    int (*p)() = 0;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

没有优化会产生:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movq    $0, -8(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret
Run Code Online (Sandbox Code Playgroud)

在堆栈上分配8个字节..

  • 这很奇怪但是`int(*)()="Hello,world!";`编译也很好.虽然`int(*p)()="Hello,world!";`却没有. (2认同)