Подтвердить что ты не робот

Позднее разрушение функциональных параметров

Согласно 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, даже если их время жизни расширяется реализацией?

4b9b3361

Ответ 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]

Определяется реализацией, заканчивается ли время жизни параметра когда функция, в которой она определена, возвращает или в конце включая полное выражение. Инициализация и уничтожение каждого параметр встречается в контексте вызывающей функции.