Согласно 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 в этом случае, если предположить, что оно гарантирует «позднее» уничтожение параметров? Я пропускаю какой-то другой отрывок в стандарте, в котором говорится, что возвращаемые ссылки на параметры функции всегда неопределены, даже если их время жизни увеличивается реализацией?
Насколько я знаю, ABI требует, чтобы GCC и Clang задерживали уничтожение параметров функции до конца полного выражения.
Вопрос сильно зависит от этого предположения. Посмотрим, правильно ли это. Проект Itanium C ++ ABI 3.1.1 Значения параметров говорит
Если у типа есть нетривиальный деструктор, вызывающая сторона вызывает этот деструктор после контроля, возвращается к нему (в том числе, когда вызывающая сторона выдает исключение) в конце включения полного выражения.
ABI не определяет продолжительность жизни, так что давайте проверим стандартную версию C ++ N4659 [Basic.life]
1.2 … Время жизни объекта o типа T заканчивается, когда:
1.3 если T является типом класса с нетривиальным деструктором (15.4), начинается вызов деструктора, или …
1.4 память, занимаемая объектом, освобождается или повторно используется объектом, который не вложен в o ([intro.object]).
Стандарт C ++ говорит, что время жизни в этом случае заканчивается, когда вызывается деструктор. Таким образом, ABI действительно требует, чтобы время жизни параметра функции расширяло полное выражение вызова функции.
Предполагая, что требования реализации определены, я не вижу UB в примере программы, поэтому он должен был ожидать ожидаемого поведения в любой реализации, которая гарантирует соблюдение ABI Itanium C ++. GCC, кажется, нарушает это.
GCC документы заявить, что
Начиная с версии 3 GCC, компилятор GNU C ++ использует промышленный стандарт C ++ ABI, Itanium C ++ ABI.
Таким образом, продемонстрированное поведение можно считать ошибкой.
С другой стороны, неясно, является ли это следствие измененной формулировки [expr.call] преднамеренным. Следствием может считаться дефект.
… Это говорит о том, что хранение переменных области видимости продолжается до тех пор, пока не завершится блок, в котором они созданы.
В самом деле. Но [Expr.call]/ 4, что вы цитируете, говорит «Параметры функции созданный и уничтожен в контекст звонящего«. Таким образом, хранение длится до конца блока вызова функции. Кажется, нет никакого конфликта с продолжительностью хранения.
Обратите внимание, что стандартные ссылки C ++ указывают на сайт, который периодически генерируется из ток черновик и поэтому может отличаться от N4659, который я цитировал.
Начиная с 5.2.2 / 4 вызов функции [expr.call], мне кажется, GCC верен:
Время жизни параметра заканчивается, когда функция, в которой он находится
определенные доходы. Инициализация и уничтожение каждого параметра
происходит в контексте вызывающей функции.
Хорошо, мой плохой ответ на приведенный ниже ответ из прежнего стандарта до C ++ 14, чтение C ++ 17, мне кажется, что и GCC, и Clang верны:
От: N4659 8.2.2 / 4 Вызов функции [expr.call]
Это определяется реализацией, заканчивается ли время жизни параметра
когда функция, в которой она определена, возвращается или в конце
вложение полного выражения. Инициализация и уничтожение каждого
Параметр встречается в контексте вызывающей функции.