与g ++编译的奇怪代码

下面的代码用g ++ 4.8.1编译成功:

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

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

 int(*f)(); 

它不能用clang 3.4和vc ++ 2013进行编译。

这是一个编译器错误还是标准的一个黑暗的地方?


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

  1. int(*)();

  2. int(*);

  3. int(*){};

  4. int(*());

这些奇怪的代码片段的生动的例子 。

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

所有4种情况都给出了一个编译错误,使用3.5中继(202594),并使用gcc 4.9中继(20140302)进行编译。 行为与-std=c++98 -pedantic ,除了int(*){}; 这是可以理解的; 扩展的初始化程序列表仅在-std=c++11可用-std=c++11

更新2:由于@CantChooseUsernames在他的答案中指出,即使在初始化时它们仍然可以很好地编译,而且即使没有任何启用的优化,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:正如@zwol在他的评论中指出的那样

这个和一些相关的语法问题正在作为gcc bug 68265被跟踪。

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

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

所以这只是一个编译器错误。

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

 int(*p){}; 

看来你用来testing的编译器允许没有声明符的声明。

还要考虑8.1节types名称中的以下段落

1要明确指定types转换,并作为sizeof,alignof,new或typeid的参数,应指定types的名称。 这可以通过type-id来完成,它在语法上是一个variables或函数的声明,省略了实体的名字。

我不知道这有多大的帮助,但我尝试了以下(铿锵3.3,克++ 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中,所有东西都编译得很好。 3.4,不幸的是,我没有叮当声。

非常粗略地说 ,一个声明[iso部分7]按顺序由以下部分组成:

  1. 可选的前缀说明符(如staticvirtual
  2. 基types(例如const doublevector<int>
  3. 声明符(例如n*pa[7]f(int)
  4. 可选的后缀函数说明符(如constnoexcept
  5. 可选的初始值或函数体(例如= {1,2,3}{ return 0; }

现在,一个声明器大致由一个名称和可选的一些声明符运算符组成[iso 8/4]。

前缀运算符,例如:

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

后缀运算符,例如:

  • [] (数组)
  • () (function)
  • -> (函数追踪返回types)

上面的操作符被devise来反映它们在expression式中的使用。 Postfix运算符的绑定比前缀更紧密,括号可以用来改变它们的顺序: int *f()是一个返回指向int的函数,而int (*f)()是指向返回int的函数的指针。

也许我错了,但我认为这些运营商不能在没有名字的声明中。 所以当我们写int *q; ,那么int是基types, *q是由前缀运算符*后缀名称q组成的声明符。 但是int *; 不能单独出现。

另一方面,当我们定义using Q = int*; ,然后申报Q; 由于Q是基本types,所以本身是很好的。 当然,因为我们没有声明任何东西,所以根据编译器选项我们可能会得到一个错误或警告,但是这是一个不同的错误。

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

每个声明符都包含一个声明符id ; 它命名声明的标识符。 在声明 id中出现的非限定 id应该是一个简单的标识符,除了某些特殊函数(12.3 [ 用户定义的转换 ],12.4 [析构函数],13.5 [重载操作符])的声明以及用于声明模板专业化或部分专业化(14.7)。

(方括号中的注释是我的)。 所以我明白int(*)(); 应该是无效的,我不能说为什么它在叮当声和不同版本的g ++中有不同的行为。

你可以使用这个: 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个字节。