用g ++编译的奇怪代码

时间:2014-04-11 14:30:42

标签: c++ gcc c++11 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(*());

  5. Live example with these strange code pieces

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

      

    所有4个案例都使用clang 3.5 trunk(202594)进行编译错误,并使用gcc 4.9 trunk(20140302)进行编译。行为与-std=c++98 -pedantic相同,但int(*){};除外,这是可以理解的;扩展初始值设定项列表仅适用于-std=c++11

    更新2:正如@CantChooseUsernames中提到的his answer一样,即使初始化它们仍然可以正常编译,并且g ++(无论是否初始化)都没有为它们生成汇编没有任何启用的优化:

    1. int(*)() = 0;

    2. int(*) = 0;

    3. int(*){} = 0;

    4. int(*()) = 0;

    5. 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

3 个答案:

答案 0 :(得分:15)

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

  

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

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

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

int(*p){};

您用于测试的编译器似乎允许声明而没有声明者ID。

还要考虑8.1节类型名称

的以下段落
  

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

答案 1 :(得分:7)

我不确定这有多大帮助,但我尝试了以下内容(铿锵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中的所有内容都很好。不幸的是,我没有铿锵声.3。

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

  1. 可选前缀说明符(例如staticvirtual
  2. 基本类型(例如const doublevector<int>
  3. 声明者(例如n*pa[7]f(int)
  4. 可选后缀函数说明符(例如constnoexcept
  5. 可选的初始值设定项或函数正文(例如= {1,2,3}{ return 0; }
  6. 现在,声明符大致由一个名称和一些声明符运算符[iso 8/4]组成。

    前缀运算符,例如:

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

    后缀运算符,例如:

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

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

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

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

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

      

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

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

答案 2 :(得分:6)

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