Позднее разрушение функциональных параметров
Согласно 5.2.2/4 "вызов функции" в n4640 (8.2.2/4 в n4659) создаются параметры функции и уничтожается в контексте вызывающего абонента. И реализациям разрешено задерживать разрушение функциональных параметров до конца охватывающего полного выражения (как функция, определяемая реализацией). Обратите внимание, что выбор не является неопределенным, а скорее определяется реализацией.
(Не совсем ясно, как это согласуется с 3.3.3 "Область блока" (6.3.3 в n4659), что, по-видимому, подразумевает, что функциональные параметры имеют область блока, а затем 3.7.3 "Автоматическое хранилище длительность" (6.7.3 в n4659), в которой говорится, что переменные области хранения для блока продолжаются до тех пор, пока не будет выведен блок, в котором они созданы. Но предположим, что я не вижу/не понял что-то в формулировке. теперь параметры функции будут иметь собственную область)
Насколько мне известно, ABI требует, чтобы GCC и Clang задерживали разрушение функциональных параметров до конца полного выражения, т.е. это поведение, определяемое реализацией этих компиляторов. Я бы предположил, что в таких реализациях должно быть нормально возвращать ссылки/указатели на функциональные параметры, если только эти ссылки/указатели используются только в вызывающем выражении.
Однако следующий пример segfaults в GCC и отлично работает в Clang
#include <iostream>
#include <string>
std::string &foo(std::string s)
{
return s;
}
int main()
{
std::cout << foo("Hello World!") << std::endl;
}
Оба компилятора выдает предупреждение о возврате ссылки на локальную переменную, что здесь совершенно уместно. Быстрый просмотр сгенерированного кода показывает, что оба компилятора действительно задерживают уничтожение параметра до конца выражения. Однако GCC по-прежнему намеренно возвращает "нулевую ссылку" из foo
, что приводит к сбою. Между тем, Clang ведет себя "как ожидалось", возвращая ссылку на его параметр s
, который выживает достаточно долго, чтобы произвести ожидаемый результат.
(GCC легко обмануть в этом случае, просто делая
std::string &foo(std::string s)
{
std::string *p = &s;
return *p;
}
который фиксирует segfault в GCC.)
Является ли поведение GCC оправданным в этом случае в предположении, что оно гарантирует "позднюю" разрушение параметров? Я пропустил какой-то другой отрывок в стандарте, в котором говорится, что возврат ссылок на функциональные параметры всегда undefined, даже если их время жизни расширяется реализацией?
Ответы
Ответ 1
Насколько я знаю, ABI требует, чтобы GCC и Clang задерживали разрушение функциональных параметров до конца полного выражения
Вопрос в значительной степени зависит от этого предположения. Посмотрим, правильно ли это. Itanium С++ ABI draft 3.1.1 Параметры значения говорит
Если тип имеет нетривиальный деструктор, вызывающий вызывает этот деструктор после того, как элемент управления возвращается к нему (в том числе, когда вызывающий вызывает исключение), в конце включения полного выражения.
ABI не определяет время жизни, поэтому давайте проверим стандартную черновик С++ N4659 [basic.life]
1.2... Время жизни объекта o типа T заканчивается, когда:
1.3, если T - тип класса с нетривиальным деструктором (15.4), начинается вызов деструктора или...
1.4 хранилище, которое занимает объект, освобождается или повторно используется объектом, который не вложен в o ([intro.object]).
В стандарте С++ говорится, что срок жизни заканчивается в этом случае, когда вызывается деструктор. Таким образом, ABI действительно требует, чтобы время жизни параметра функции расширило полное выражение вызова функции.
Предполагая, что это требование реализации определено, я не вижу UB в примере программы, поэтому он должен ожидать ожидаемого поведения в любой реализации, которая гарантирует следовать за Itanium С++ ABI. GCC, похоже, нарушает это.
Документы GCC утверждают, что
Начиная с версии 3 GCC, компилятор GNU С++ использует стандартный С++ ABI, Itanium С++ ABI.
Таким образом, продемонстрированное поведение можно считать ошибкой.
С другой стороны, неясно, преднамеренно ли это следствие измененной формулировки [expr.call]. Последствием можно считать дефект.
..., в котором говорится, что переменные области хранения для блока сохраняются до тех пор, пока не завершится создание блока, в котором они созданы.
Действительно. Но [expr.call]/4, который вы указали, говорит:" Параметры функции созданы и уничтожены в контекст вызывающего. Таким образом, хранилище сохраняется до конца блока вызова функции. Кажется, что не существует конфликта с продолжительностью хранения.
Обратите внимание, что стандартные ссылки С++ указывают на сайт, который периодически генерируется из текущего черновика и поэтому может отличаться от N4659, который я цитировал.
Ответ 2
Из 5.2.2/4 Вызов функции [expr.call], мне кажется, что GCC верен:
Время жизни параметра заканчивается, когда функция, в которой она находится определенных возвратов. Инициализация и уничтожение каждого параметра происходит в контексте вызывающей функции.
Ответ 3
Хорошо, что я плохо дал ответ ниже от прежнего стандарта до С++ 14, прочитав С++ 17, мне кажется, что GCC и Clang верны:
От: N4659 8.2.2/4 Функциональный вызов [expr.call]
Определяется реализацией, заканчивается ли время жизни параметра когда функция, в которой она определена, возвращает или в конце включая полное выражение. Инициализация и уничтожение каждого параметр встречается в контексте вызывающей функции.