Я понимаю, что этого делать не следует, но я считаю, что видел примеры, которые делают что-то подобное (код заметки не обязательно синтаксически правильный, но идея есть)
typedef struct{
int a,b;
}mystruct;
А вот функция
mystruct func(int c, int d){
mystruct retval;
retval.a = c;
retval.b = d;
return retval;
}
Я понял, что мы всегда должны возвращать указатель на malloc'ed struct, если мы хотим сделать что-то подобное, но я уверен, что видел примеры, которые делают что-то подобное. Это верно? Лично я всегда либо возвращаю указатель на malloc'ed структуру, либо просто передаю ссылку на функцию и изменяю там значения. (Поскольку я понимаю, что как только область действия функции завершена, любой стек, который использовался для выделения структуры, может быть перезаписан).
Добавим вторую часть к вопросу: это зависит от компилятора? Если да, то каково поведение последних версий компиляторов для настольных компьютеров: gcc, g ++ и Visual Studio?
Мысли по этому поводу?
Ответы:
Это совершенно безопасно, и в этом нет ничего плохого. Также: это не зависит от компилятора.
Обычно, когда (например, ваш пример) ваша структура не слишком велика, я бы сказал, что этот подход даже лучше, чем возврат malloc'ed структуры (
malloc
это дорогостоящая операция).источник
Это совершенно безопасно.
Вы возвращаетесь по стоимости. Что привело бы к неопределенному поведению, если бы вы возвращались по ссылке.
//safe mystruct func(int c, int d){ mystruct retval; retval.a = c; retval.b = d; return retval; } //undefined behavior mystruct& func(int c, int d){ mystruct retval; retval.a = c; retval.b = d; return retval; }
Поведение вашего сниппета совершенно корректно и определено. Это не зависит от компилятора. Все нормально!
Вы не должны. По возможности следует избегать динамически выделяемой памяти.
Этот вариант вполне допустим. Это вопрос выбора. В общем, вы делаете это, если хотите вернуть что-то еще из функции, изменяя исходную структуру.
Это не правильно. Я имел в виду, что это вроде как правильно, но вы возвращаете копию структуры, которую создаете внутри функции. Теоретически . На практике RVO может и, вероятно, произойдет. Прочтите об оптимизации возвращаемого значения. Это означает, что хотя
retval
кажется, что после завершения функции она выходит за пределы области видимости, на самом деле она может быть построена в контексте вызова, чтобы предотвратить дополнительную копию. Это оптимизация, которую компилятор может реализовать бесплатно.источник
Время жизни
mystruct
объекта в вашей функции действительно заканчивается, когда вы покидаете функцию. Однако вы передаете объект по значению в операторе возврата. Это означает, что объект копируется из функции в вызывающую функцию. Исходный объект исчез, но копия продолжает жить.источник
Не только безопасно возвращать a
struct
в C (или aclass
в C ++, гдеstruct
-s фактически являютсяclass
-es сpublic:
членами по умолчанию ), но и многие программы делают это.Конечно, при возврате a
class
в C ++ язык указывает, что будет вызываться некоторый деструктор или движущийся конструктор, но есть много случаев, когда это может быть оптимизировано компилятором.Кроме того, Linux x86-64 ABI указывает, что возврат a
struct
с двумя скалярными (например, указателями илиlong
) значениями осуществляется через регистры (%rax
&%rdx
), поэтому это очень быстро и эффективно. Так что в этом конкретном случае, вероятно, быстрее вернуть такие двухскалярные поля,struct
чем делать что-либо еще (например, сохранять их в указателе, переданном в качестве аргумента).Таким образом, возврат такого двухскалярного поля
struct
происходит намного быстрее, чемmalloc
-ing его и возврат указателя.источник
Это совершенно законно, но при работе с большими структурами необходимо учитывать два фактора: скорость и размер стека.
источник
Тип структуры может быть типом значения, возвращаемого функцией. Это безопасно, потому что компилятор собирается создать копию структуры и вернуть копию, а не локальную структуру в функции.
typedef struct{ int a,b; }mystruct; mystruct func(int c, int d){ mystruct retval; cout << "func:" <<&retval<< endl; retval.a = c; retval.b = d; return retval; } int main() { cout << "main:" <<&(func(1,2))<< endl; system("pause"); }
источник
Безопасность зависит от того, как была реализована сама структура. Я просто наткнулся на этот вопрос, когда реализовал нечто подобное, и вот потенциальная проблема.
Компилятор при возврате значения выполняет несколько операций (среди возможных других):
mystruct(const mystruct&)
(this
это временная переменная вне функции,func
выделенной самим компилятором)~mystruct
переменной, которая была размещена внутриfunc
mystruct::operator=
если возвращаемое значение присвоено чему-то еще с=
~mystruct
временной переменной, используемой компиляторомТеперь, если
mystruct
это так просто, как описано здесь, все в порядке, но если у него есть указатель (напримерchar*
) или более сложное управление памятью, тогда все зависит от тогоmystruct::operator=
, какmystruct(const mystruct&)
, и~mystruct
реализованы. Поэтому я предлагаю соблюдать осторожность при возврате сложных структур данных в качестве значения.источник
Совершенно безопасно вернуть структуру, как вы это сделали.
Однако, основываясь на этом утверждении: поскольку я понимаю, что после того, как область действия функции завершена, любой стек, использованный для выделения структуры, может быть перезаписан, я могу представить только сценарий, в котором любой из членов структуры был динамически выделен ( malloc'ed или new'ed), и в этом случае без RVO динамически выделяемые члены будут уничтожены, а возвращенная копия будет иметь член, указывающий на мусор.
источник
Я также согласен с sftrabbit, жизнь действительно заканчивается и область стека очищается, но компилятор достаточно умен, чтобы гарантировать, что все данные должны быть получены в регистрах или каким-либо другим способом.
Ниже приведен простой пример подтверждения (взято из сборки компилятора Mingw).
_func: push ebp mov ebp, esp sub esp, 16 mov eax, DWORD PTR [ebp+8] mov DWORD PTR [ebp-8], eax mov eax, DWORD PTR [ebp+12] mov DWORD PTR [ebp-4], eax mov eax, DWORD PTR [ebp-8] mov edx, DWORD PTR [ebp-4] leave ret
Вы можете видеть, что значение b было передано через edx. в то время как eax по умолчанию содержит значение для.
источник
Возвращать конструкцию небезопасно. Я люблю делать это сам, но если кто-то позже добавит конструктор копирования к возвращаемой структуре, конструктор копирования будет вызван. Это может быть неожиданным и может нарушить код. Эту ошибку очень сложно найти.
У меня был более развернутый ответ, но модератору он не понравился. Итак, за ваш счет, мой совет короткий.
источник
Действительно, как я обнаружил, к своей боли: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/
Я использовал gcc на win32 (MinGW) для вызова COM-интерфейсов, возвращающих структуры. Оказывается, MS делает это иначе, чем GNU, и поэтому моя программа (gcc) вылетела из-за разбитого стека.
Может быть, здесь MS может иметь более высокое положение, но все, что меня волнует, - это совместимость ABI между MS и GNU для построения в Windows.
В списке рассылки Wine вы можете найти несколько сообщений о том, как MS это делает.
источник
Примечание: этот ответ относится только к C ++ 11 и далее. Нет такого понятия, как "C / C ++", это разные языки.
Нет, возвращать локальный объект по значению не опасно, и это рекомендуется делать. Однако я думаю, что во всех ответах здесь отсутствует важный момент. Многие другие сказали, что структура либо копируется, либо размещается напрямую с помощью RVO. Однако это не совсем так. Я постараюсь объяснить, что именно может произойти при возврате локального объекта.
Семантика перемещения
Начиная с C ++ 11, у нас были ссылки на rvalue, которые являются ссылками на временные объекты, которые можно безопасно украсть. Например, std :: vector имеет конструктор перемещения, а также оператор присваивания перемещения. Оба они имеют постоянную сложность и просто копируют указатель на данные перемещаемого вектора. Я не буду здесь вдаваться в подробности семантики перемещения.
Поскольку объект, созданный локально внутри функции, является временным и выходит за пределы области видимости при возврате из функции, возвращенный объект никогда не копируется в C ++ 11 и далее. Конструктор перемещения вызывается для возвращаемого объекта (или нет, как будет объяснено позже). Это означает, что если вы должны вернуть объект с помощью дорогостоящего конструктора копирования, но недорогого конструктора перемещения, например большого вектора, от локального объекта к возвращаемому объекту передается только право собственности на данные, что обходится дешево.
Обратите внимание, что в вашем конкретном примере нет разницы между копированием и перемещением объекта. Конструкторы перемещения и копирования по умолчанию для вашей структуры приводят к одним и тем же операциям; копирование двух целых чисел. Однако это по крайней мере так же быстро, как любое другое решение, потому что вся структура помещается в 64-битный регистр ЦП (поправьте меня, если я ошибаюсь, я не знаю много регистров ЦП).
RVO и NRVO
RVO означает оптимизацию возвращаемого значения и является одной из немногих оптимизаций, которые делают компиляторы и которые могут иметь побочные эффекты. Начиная с c ++ 17 требуется RVO. При возврате безымянного объекта он создается непосредственно на месте, где вызывающий объект присваивает возвращаемое значение. Ни конструктор копирования, ни конструктор перемещения не вызываются. Без RVO безымянный объект сначала был бы построен локально, затем был бы создан перемещением по возвращенному адресу, а затем локальный безымянный объект был бы разрушен.
Пример, когда требуется RVO (c ++ 17) или вероятно (до c ++ 17):
auto function(int a, int b) -> MyStruct { // ... return MyStruct{a, b}; }
NRVO означает оптимизацию именованного возвращаемого значения и то же самое, что и RVO, за исключением того, что это делается для именованного объекта, локального для вызываемой функции. Это все еще не гарантируется стандартом (c ++ 20), но многие компиляторы все еще это делают. Обратите внимание, что даже с именованными локальными объектами они в худшем случае перемещаются при возврате.
Заключение
Единственный случай, когда вам следует рассмотреть возможность отказа от возврата по значению, - это когда у вас есть именованный, очень большой (как по размеру стека) объект. Это связано с тем, что NRVO еще не гарантирован (начиная с C ++ 20), и даже перемещение объекта будет медленным. Моя рекомендация и рекомендация в Cpp Core Guidelines - всегда отдавать предпочтение возврату объектов по значению (если возвращаются несколько значений, используйте структуру (или кортеж)), где единственное исключение - когда перемещение объекта дорого. В этом случае используйте неконстантный ссылочный параметр.
НИКОГДА не рекомендуется возвращать ресурс, который нужно вручную освободить из функции в C ++. Никогда не делай этого. По крайней мере, используйте std :: unique_ptr или создайте свою собственную нелокальную или локальную структуру с деструктором, который освобождает свой ресурс ( RAII ) и возвращает экземпляр этого. Тогда также было бы неплохо определить конструктор перемещения и оператор присваивания перемещения, если ресурс не имеет собственной семантики перемещения (и удалить конструктор / присваивание копии).
источник