用 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 节] 按顺序由以下部分组成:
-
可选前缀说明符(例如
static
、virtual
)
基本类型(例如const double
、vector<int>
)
声明符(例如n
、*p
、a[7]
、f(int)
)
可选的后缀函数说明符(例如const
、noexcept
)
可选的初始化器或函数体(例如= 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++ 编译的奇怪代码的主要内容,如果未能解决你的问题,请参考以下文章