【问题标题】:Strange code that compiles with g++用 g++ 编译的奇怪代码
【发布时间】:2014-05-25 18:42:46
【问题描述】:

以下代码使用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(*());

Live example with these strange code pieces.

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

所有 4 种情况在使用 clang 3.5 主干 (202594) 时都会出现编译错误,而使用 gcc 4.9 主干 (20140302) 时编译良好。行为与-std=c++98 -pedantic 相同,但int(*){}; 除外,这是可以理解的;扩展初始值设定项列表仅适用于 -std=c++11

更新 2: 正如@CantChooseUsernameshis answer 中指出的那样,即使进行了初始化,它们仍然可以正常编译,并且即使没有任何启用的优化,g++ 也不会为它们生成程序集(无论是否初始化) :

  1. int(*)() = 0;

  2. int(*) = 0;

  3. int(*){} = 0;

  4. int(*()) = 0;

Live example with initializations.

更新 3:我很惊讶地发现 int(*)() = "Hello, world!"; 也可以正常编译(当然 int(*p)() = "Hello, world!"; 不能编译)。

更新 4: 很棒,但 int(*){} = Hello, world!; 编译得很好。还有下面这段极其奇怪的代码:int(*){}() = -+*/%&|^~.,:!?$()[]{}; (live example)。

更新 5:正如@zwolhis comment 中指出的那样

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

【问题讨论】:

  • int(*)(); 就像输入 int;int*; ... 也就是说,您开始声明一个变量类型,但从不命名它。
  • fork(3) - 3 个人去 ideone 试图编译 int;。 :)
  • @chris 我想这与它的解析有关。似乎这是 g++ 中的一个错误,而 clang 和 VS 提供了正确的错误。
  • 目前正在搜索eelis.net/C++/grammar.png,但在那里找不到
  • @PlasmaHH 这张照片很棒,谢谢分享。

标签: c++ gcc c++11 g++


【解决方案1】:

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

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

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

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

int(*p){};

您用于测试的编译器似乎允许不带 declarator-id 的声明。

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

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

【讨论】:

  • 难道不是初始化语句而是其他东西所以 g++ 编译它?
  • @Constructor 在我看来这是一个声明。
  • @BЈовић 不,我只是碰巧遇到了他们。 :-) 我想了解它们是否真的是错误。
  • @VladfromMoscow 我刚刚发现了另一段奇怪的代码(列表中的第 3 位,请参阅问题的编辑)。你能说什么呢?
  • gcc 扩展以允许在 cmets 之外进行审查发誓。
【解决方案2】:

我不确定这有多大帮助,但我尝试了以下方法(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 中,一切都可以正常编译。不幸的是,我没有 clang 3.4。

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

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

现在,声明符大致由名称和可选的一些声明符运算符 [iso 8/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]:

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

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

【讨论】:

  • 谢谢。你对更新的奇怪代码列表有什么看法?
  • 恐怕不多。除了它们都在我拥有的所有版本的 g++ 中编译,但不是铿锵声。同样,如果您先添加名称或定义类型别名,它们就会编译(当然,{} 是初始化器,而不是类型的一部分)。
  • 如果添加一个名称,我认为它们显然会成为有效的 C++ 结构。
  • 我刚刚发现int(*){}() = -+*/%&|^~.,:!?$()[]{}; 也可以正常编译!你怎么看待这件事?查看新问题更新中的实时示例。
  • @Constructor 越来越好!而且我认为 cheats 仅适用于游戏……现在看来编译器也可以拥有它们!太糟糕了,我不能再投票了:-)
【解决方案3】:

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

【讨论】:

  • 好主意!谢谢你。那么int(*)() = 0;呢?
  • 也没有组装。它不会分配给未命名的 func ptr。如果您尝试分配 1,同样的事情.. 没有生成程序集。
  • 这很奇怪,但int(*)() = "Hello, world!"; 也编译得很好。而int(*p)() = "Hello, world!"; 没有。
  • 我刚刚发现int(*){}() = -+*/%&|^~.,:!?$()[]{}; 也编译得很好!你怎么看待这件事?查看新问题更新中的实时示例。
  • 哈哈?如果我曾经看到过,这看起来像是一个完全搞砸的解析器问题。毫无疑问,这应该会给出一个错误。C 不接受任何这些东西:ideone.com/GaxlGI
猜你喜欢
  • 2023-04-01
  • 1970-01-01
  • 2014-10-29
  • 2015-11-05
  • 1970-01-01
  • 2013-11-26
  • 2013-10-28
  • 1970-01-01
相关资源
最近更新 更多