用 g++ 编译的奇怪代码

Posted

技术标签:

【中文标题】用 g++ 编译的奇怪代码【英文标题】:Strange code that compiles with g++ 【发布时间】:2014-05-26 19:35:27 【问题描述】:

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

int main()

    int(*)();

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

int(*f)();

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

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


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

    int(*)();

    int(*);

    int(*);

    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: 正如@CantChooseUsernames 在his answer 中指出的那样,即使进行了初始化,它们仍然可以正常编译,并且即使没有任何启用的优化,g++ 也不会为它们生成程序集(无论是否初始化) :

    int(*)() = 0;

    int(*) = 0;

    int(*) = 0;

    int(*()) = 0;

Live example with initializations.

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

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

更新 5:正如@zwol 在his comment 中指出的那样

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

【问题讨论】:

int(*)(); 就像输入 int;int*; ... 也就是说,您开始声明一个变量类型,但从不命名它。 fork(3) - 3 个人去 ideone 试图编译 int;。 :) @chris 我想这与它的解析有关。似乎这是 g++ 中的一个错误,而 clang 和 VS 提供了正确的错误。 目前正在搜索eelis.net/C++/grammar.png,但在那里找不到 @PlasmaHH 这张照片很棒,谢谢分享。 【参考方案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 节] 按顺序由以下部分组成:

    可选前缀说明符(例如staticvirtual) 基本类型(例如const doublevector<int>) 声明符(例如n*pa[7]f(int)) 可选的后缀函数说明符(例如constnoexcept) 可选的初始化器或函数体(例如= 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

以上是关于用 g++ 编译的奇怪代码的主要内容,如果未能解决你的问题,请参考以下文章

编译没有优化?尽可能安全?

g++:交叉编译时包含问题

linux 用g++编译c++代码的问题

cygwin安装G++编译器的问题!急求!!

默认情况下使用 g++ 而不是 arm 编译器进行构建?

gcc和g++的区别