Странный код, который компилируется с g++
Следующий код успешно компилируется с g++ 4.8.1:
int main()
{
int(*)();
}
Это похоже на простое объявление указателя на функцию:
int(*f)();
Он не компилируется с clang 3.4 и vС++ 2013.
Является ли это ошибкой компилятора или одним из темных мест стандарта?
Список похожих странных фрагментов кода, которые компилируются с помощью g++ 4.8.1 (обновлено):
-
int(*)();
-
int(*);
-
int(*){};
-
int(*());
Живой пример с этими странными фрагментами кода.
Обновление 1: @Ali добавило интересную информацию в комментарии:
Все 4 случая дают ошибку компиляции с соединительной шиной 3.5 (202594) и компилируются с помощью gcc 4.9 trunk (20140302). Поведение одинаково с -std=c++98 -pedantic
, за исключением int(*){};
, что понятно; расширенные списки инициализаторов доступны только с -std=c++11
.
Обновить 2: Как @CantChooseUsernames, отмеченный в его answer они по-прежнему компилируются даже при инициализации, и для них не создается сборка g++ (ни с инициализацией, ни без нее) даже без какой-либо активированной оптимизации:
-
int(*)() = 0;
-
int(*) = 0;
-
int(*){} = 0;
-
int(*()) = 0;
Живой пример с инициализацией.
Обновление 3: Я был очень удивлен, обнаружив, что int(*)() = "Hello, world!";
тоже компилируется (пока int(*p)() = "Hello, world!";
не компилируется, конечно).
Обновление 4: Это фантастика, но int(*){} = Hello, world!;
компилируется отлично. И следующий чрезвычайно странный фрагмент кода: int(*){}() = -+*/%&|^~.,:!?$()[]{};
(живой пример).
Обновить 5:. @zwol отмечен в его комментарии
Это и ряд связанных синтаксических проблем отслеживаются как gcc ошибка 68265.
Ответы
Ответ 1
В соответствии со стандартом С++ (стр. 6 раздела 7 Декларации)
6 Каждый init-declarator в списке init-declarator содержит точно один идентификатор-id, который является именем, объявленным этим init-declarator и, следовательно, одно из имен, объявленных объявлением
Так что это просто ошибка компилятора.
Действительный код может выглядеть как, например, (кроме объявления указателя функции, показанного вами), хотя я не могу его скомпилировать с помощью моего MS VС++ 2010.
int(*p){};
Кажется, что компилятор, который вы используете для тестирования, допускает объявления без идентификатора-декларатора.
Также учтите следующий параграф раздела 8.1. Названия типов
1 Чтобы явно указать преобразования типов и как аргумент sizeof, alignof, new или typeid, имя типа должно быть указано. Это можно сделать с помощью идентификатора типа, который является синтаксически объявление для переменной или функции этого типа, которая опускает имя объекта.
Ответ 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 section 7] состоит из следующих частей в порядке:
- необязательные спецификаторы префикса (например,
static
, virtual
)
- базовый тип (например,
const double
, vector<int>
)
- (например,
n
, *p
, a[7]
, f(int)
)
- необязательные спецификаторы функции суффикса (например,
const
, noexcept
)
- необязательный инициализатор или тело функции (например,
= {1,2,3}
или { return 0; }
Теперь декларатор грубо состоит из имени и необязательно некоторых операторов-деклараторов [iso 8/4].
Операторы префикса, например:
-
*
(указатель)
-
*const
(постоянный указатель)
-
&
(ссылка lvalue)
-
&&
(ссылка rvalue)
-
auto
(возвращаемый тип функции, при завершении)
Операторы Postfix, например:
-
[]
(массив)
-
()
(функция)
-
->
(возвращаемый тип возвращаемого значения)
Вышеуказанные операторы были разработаны для отражения их использования в выражениях. Операторы Postfix привязываются более жестко, чем префикс, а круглые скобки могут использоваться для изменения их порядка: int *f()
- это функция, возвращающая указатель на int
, тогда как int (*f)()
является указателем на функцию, возвращающую int
.
Возможно, я ошибаюсь, но я думаю, что эти операторы не могут быть в объявлении без имени. Поэтому, когда мы пишем int *q;
, тогда int
является базовым типом, а *q
является декларатором, состоящим из префиксного оператора *
, за которым следует имя q
. Но int *;
не может отображаться сам по себе.
С другой стороны, когда мы определяем using Q = int*;
, объявление Q;
отлично само по себе, потому что q
является базовым типом. Конечно, поскольку мы ничего не объявляем, мы можем получить сообщение об ошибке или предупреждение в зависимости от параметров компилятора, но это другая ошибка.
Выше всего лишь мое понимание. Что означает стандарт (например, N3337), это [iso 8.3/1]:
Каждый декларатор содержит ровно один идентификатор объявления; он называет объявленный идентификатор. Неквалифицированный идентификатор, встречающийся в идентификаторе-деклараторе, должен быть простым идентификатором, за исключением объявления некоторых специальных функций (12.3 [пользовательские преобразования], 12.4 [деструкторы], 13.5 [перегруженные операторы]) и для объявления специализированных шаблонов или частичные специализации (14.7).
(примечания в квадратных скобках мои). Поэтому я понимаю, что int(*)();
должен быть недействительным, и я не могу сказать, почему он имеет другое поведение в clang и разных версиях g++.
Ответ 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 байтов в стеке.