Разрешено ли исключение копирования в форме оптимизации именованного возвращаемого значения в C?

Гарантируется ли завершение следующей программы на языке C с помощью 0 или компилятору разрешено идентифицировать объекты s и t друг с другом, как это разрешено в C++, как так называемая форма оптимизации возвращаемого значения (NRVO) при копировании?

typedef struct {
    int i, j;
    double a, b;
} S;

int result;

S test(S *q) {
    S s = {0, 0, 0, 0};
    result = &s == q;
    return s;
}

int main(void)
{
  S t = test(&t);
  return result;
}

Кланг выходит с 1 вопреки моим ожиданиям, см. https://godbolt.org/z/ME8sPGn3n.

(В C++ оба 0 и 1 являются допустимыми результатами из-за явного разрешения на выполнение NRVO.)

Ваша программа вызывает неопределенное поведение, поэтому вам не следует ничего ожидать.

0___________ 27.07.2024 23:33

@0___________ Не могли бы вы указать мне, какая часть UB? Предполагая, что я не упустил из виду что-то очевидное по глупости, я, по крайней мере, почти уверен, что в C++ нет UB, и я не вижу, чем он будет отличаться в C.

user17732522 27.07.2024 23:35

В С++ тоже. godbolt.org/z/nEos6ojch

0___________ 27.07.2024 23:38

Он также возвращает 1 в C++.

Brad Lanam 27.07.2024 23:38

@0___________ Да, в C++ разрешены оба кода выхода 0 и 1, и неизвестно, какой из них будет выбран. Хотя это не УБ. Вопрос в том, отличается ли это в C.

user17732522 27.07.2024 23:38

@BradLanam См. комментарий выше.

user17732522 27.07.2024 23:39

Учитывая, что s является локальным для test(), а q = &s;, доступ к значению q после возврата test() осуществляется через UB.

Andrew Henle 27.07.2024 23:55

@AndrewHenle Хорошо, это определяется реализацией в C++. Но я снова удалил этот вариант. Наверное, это просто отвлекло от моей мысли.

user17732522 27.07.2024 23:57

@user17732522 user17732522 Мне не известен какой-либо эквивалент NRVO на языке C, поэтому я бы сказал, что возвращаемое значение C должно быть равно 0, поскольку s является локальным для test(), а t является локальным для main(). Это два разных объекта, адреса каждого из которых взяты, поэтому у них должны быть разные адреса. Кажется, вы нашли ошибку компилятора.

Andrew Henle 28.07.2024 00:08

@user17732522 user17732522 Я подозреваю, что если бы вы не взяли адреса объектов, то по правилу «как если бы» [N]RVO было бы вполне приемлемо в C, поскольку не было бы ни одного из возможных побочных эффектов. в C++ при копировании объектов. Конечно, без получения адреса(ов) вы никогда не сможете наблюдать такое поведение изнутри своего кода.

Andrew Henle 28.07.2024 00:17

@AndrewHenle Конечно. Независимо от того, оптимизирует ли компилятор копию, само по себе обычно невозможно наблюдать, и тогда это можно сделать как на C, так и на C++ в соответствии с общими правилами «как если бы», применимыми ко всем оптимизациям. Правило NRVO в C++ существует потому, что оно явно разрешает такую ​​оптимизацию, даже если она имеет наблюдаемые побочные эффекты. Гораздо проще генерировать наблюдаемые побочные эффекты в C++, но, как показывает этот пример, это также возможно и в C при получении адресов.

user17732522 28.07.2024 00:22

@user17732522 user17732522 Я добавил это: NRVO используется в режиме C

Ted Lyngmo 28.07.2024 00:28

@TedLyngmo Вы использовали мой вариант, который я тем временем удалил, который, как мне было указано в предыдущем комментарии, имеет UB на C. Вместо этого вам следовало опубликовать исходный вариант, который все еще находится в вопросе.

user17732522 28.07.2024 00:29

@TedLyngmo В C используется указатель, значение которого указывает на объект за пределами его жизни, равно UB, как это происходит с ptr при сравнении в main. (В C++ такое использование значения указателя имеет поведение, определяемое реализацией, при этом обычно значение указателя по-прежнему ведет себя так, как если бы оно представляло исходный адрес объекта для цели ==.)

user17732522 28.07.2024 00:32

@user17732522 user17732522 Я обновил билет, указав ваш текущий код.

Ted Lyngmo 28.07.2024 00:33
Are there two or three S objects? Два. Объект должен быть «определен». Возвращаемое значение не является объектом. Это ценность.
KamilCuk 28.07.2024 00:46

@KamilCuk Я заметил, что это в любом случае не имеет значения, потому что время жизни s в C существует уже тогда, когда его блок введен, поэтому в любом случае он должен будет иметь уникальный адрес во время всей соответствующей обработки. Но мне интересно, действительно ли временных объектов нет. Черновик N3220 по крайней мере описывает объекты с временным временем жизни для выражений без lvalue типа структуры/объединения с элементами массива в §6.2.4. Я полагаю, что это необходимо, потому что на элемент массива можно сформировать указатель.

user17732522 28.07.2024 00:52

@KamilCuk Кроме того, что интересно, в §6.2.4 также говорится об объектах с временным временем жизни: «Такой объект не может иметь уникальный адрес». Фактически это означает, что в C разрешен эквивалент исключения копирования в форме оптимизации возвращаемого значения (RVO вместо NRVO). Адрес временного объекта будет единственным наблюдаемым отличием при применении этой оптимизации в C.

user17732522 28.07.2024 00:59

Как правило, если есть оптимизация, которая явно разрешена в C++ и явно не разрешена или явно не разрешена в C, и вы обнаружите, что clang выполняет эту оптимизацию для C, вы должны предположить, что clang содержит ошибки, особенно если другой компилятор не делает то же самое.

zwol 28.07.2024 02:17

@ user17732522, ты читаешь об этом больше, чем есть на самом деле. Единственная цель временного существования — гармонизировать семантику доступа к элементам массива, содержащегося в качестве подобъекта выражения, отличного от lvalue. Даже если бы ваш тип S предоставлял объекты с временным сроком жизни - это не так - это условие не позволило бы (наблюдаемому) использовать NVRO. Временный объект предположительно можно идентифицировать с одним из именованных объектов, участвующих в возврате структуры по значению, но это не идентифицирует источник и место назначения друг с другом.

John Bollinger 29.07.2024 02:34

@JohnBollinger Да, именно поэтому я указал, что это эквивалентно RVO, а не NRVO, в C++ (до C++17, который исключил временные объекты как объекты результатов функции). Я не говорил о примере в вопросе. RVO будет идентификацией временного объекта результата вызова функции с переменной, которая инициализируется из него.

user17732522 29.07.2024 02:39

Хорошо, @user17732522, но для этого вам не нужна временная жизнь. В C нет способа различить, выполняется ли (анонимный) RVO или нет, поэтому это разрешено в соответствии с правилом «как если бы», и так было всегда. Насколько я понимаю, многие компиляторы делают это.

John Bollinger 29.07.2024 02:47

@JohnBollinger Хм, для типов без членов массива это явно ненаблюдаемо, но я подумал, возможно, неявное преобразование массива в указатель позволит наблюдать адрес временного объекта. Но единственный способ, которым мне это удастся сделать, приведет к тому, что временный объект больше не будет инициализировать переменную.

user17732522 29.07.2024 03:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
14
23
807
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Гарантированно завершается с 0. t и s — разные объекты, указатели на них не могут сравниваться равными.


О коде, который вы удалили:

А как насчет следующего варианта, в котором соответствующие соображения по поводу срока службы могут быть другими?

Из https://port70.net/~nsz/c/c11/n1570.html#6.2.4p2:

Значение указателя становится неопределенным, когда объект, на который он указывает (или только что прошедший), достигает конца своего существования.

Он может вернуть 1 или 0 или выполнить ловушку.

Теперь также подтверждено, что это ошибка в clang: github.com/llvm/llvm-project/issues/…

Ted Lyngmo 29.07.2024 19:00

Стоит отметить, что оптимизация была бы разрешена, если бы программа не пыталась ее соблюдать!

M.M 31.07.2024 00:30

Исключение копирования/перемещения — это концепция C++, определенная в разделе 15.8.3 C++17. В некоторых случаях это позволяет оптимизировать вызовы конструктора и деструктора, и в этом случае объекты источника и назначения являются одним и тем же:

Когда определенные критерии соблюдены, реализация может быть опущена. конструкция копирования/перемещения объекта класса, даже если конструктор выбранный для операции копирования/перемещения и/или деструктор для объект имеет побочные эффекты. В таких случаях реализация обрабатывает источник и цель пропущенной операции копирования/перемещения как просто два разные способы обращения к одному и тому же объекту.

В C нет такого положения, хотя, начиная с C17, он позволяет использовать объект с временным сроком жизни, например. массив, содержащийся в структуре в выражении, отличном от lvalue, чтобы не иметь уникального адреса. Однако это неприменимо к данному случаю.

Тот факт, что эта оптимизация была выполнена для программы на языке C, меняет наблюдаемое поведение программы, что нарушает стандарт C. В частности, t и s — это разные объекты, время жизни которых перекрывается, и поэтому они должны иметь разные адреса.

Итак, это ошибка в clang, заключающаяся в том, что исключение копирования применяется к программе на C.

Что касается «C не имеет такого положения»: за исключением того, как заметил user17732522, возвращаемый объект с временным сроком жизни может иметь тот же адрес, что и другой объект.

Eric Postpischil 28.07.2024 16:29

@EricPostpischil Интересно, кажется, в C17 была добавлена ​​часть об отсутствии уникального адреса.

dbush 28.07.2024 16:46

Существует множество причин, по которым программы выполняют сравнения указателей на равенство, и во многих случаях такие сравнения можно наиболее эффективно рассматривать как получение либо 0, либо 1 неопределенным способом без побочных эффектов. В этом примере последний раз обращение к s будет предшествовать первому обращению к t, и, таким образом, использование одного и того же хранилища для обоих позволит сделать код более эффективным. Несмотря на то, что объекты технически имеют перекрывающееся время жизни, код, который сравнивает их адреса, редко заботится о результате сравнения, тем более, что большинство вещей, которые код обычно делает на основе результата сравнения, делают это необходимым. присваивать объектам отдельные адреса.

Хотя такое поведение в режиме, который должен соответствовать требованиям, можно считать «ошибкой», режим компиляции, который позволяет объектам использовать общие адреса в некоторых случаях, не разрешенных Стандартом, может для некоторых целей быть более полезным, чем тот, который точно соответствует Стандарту. Например, хотя в стандарте указано, что все статические константные структуры имеют уникальные адреса, компиляторам часто бывает полезнее консолидировать их адреса. К сожалению, документация компилятора часто не в состоянии адекватно документировать крайние случаи, когда поведение может отклоняться от языка Денниса Ритчи или Стандарта.

Другие вопросы по теме