Безопасно ли возвращать структуру в C или C ++? - PullRequest
77 голосов
/ 06 марта 2012

Что я понимаю, так это то, что этого не следует делать, но я думаю, что я видел примеры, которые делают что-то подобное (код примечания не обязательно синтаксически правильный, но идея есть)

typedef struct{
    int a,b;
}mystruct;

А потом вот функция

mystruct func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

Я понял, что мы всегда должны возвращать указатель на структуру malloc, если мы хотим сделать что-то подобное, но я уверен, что видел примеры, которыесделать что-то подобноеЭто правильно?Лично я всегда либо возвращаю указатель на структуру malloc, либо просто передаю ссылку на функцию и изменяю там значения.(Поскольку я понимаю, что, как только область действия функции закончится, любой стек, использованный для размещения структуры, может быть перезаписан).

Давайте добавим вторую часть к вопросу: зависит ли это от компилятора?Если да, то каково поведение последних версий компиляторов для рабочих столов: gcc, g ++ и Visual Studio?

Мысли по этому поводу?

Ответы [ 11 ]

71 голосов
/ 06 марта 2012

Это совершенно безопасно, и это не так.Кроме того: он не зависит от компилятора.

Обычно, когда (как ваш пример) ваша структура не слишком велика, я бы сказал, что этот подход даже лучше, чем возвращение структуры с malloc'ом (mallocдорогая операция).

66 голосов
/ 06 марта 2012

Это совершенно безопасно.

Вы возвращаетесь по значению. Что приведет к неопределенному поведению, если вы вернетесь по ссылке.

//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;
}

Поведение вашего фрагмента совершенно корректно и определено. Это не зависит от компилятора. Все нормально!

Лично я всегда либо возвращаю указатель на структуру malloc'а

Ты не должен. Вам следует избегать динамически выделяемой памяти, когда это возможно.

или просто сделайте передачу по ссылке на функцию и измените значения есть.

Эта опция совершенно действительна. Это вопрос выбора. В общем, вы делаете это, если хотите вернуть что-то еще из функции, изменяя исходную структуру.

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

Это неправильно. Я имею в виду, это вроде правильно, но вы возвращаете копию структуры, которую вы создаете внутри функции. Теоретически . На практике RVO может и, вероятно, произойдет. Читайте об оптимизации возвращаемого значения. Это означает, что хотя retval выходит из области видимости после завершения функции, она может быть встроена в вызывающий контекст, чтобы предотвратить дополнительное копирование. Это оптимизация, которую компилятор может реализовать бесплатно.

9 голосов
/ 06 марта 2012

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

7 голосов
/ 16 мая 2015

Не только безопасно возвращать struct в C (или class в C ++, где struct -s на самом деле class -e с элементами по умолчанию public:), но и многопрограммное обеспечение делает это.

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

Кроме того, в Linux x86-64 ABI указывается, что возвращаются struct с двумя скалярами (например, указатели или * 1018).*) значения делаются через регистры (%rax & %rdx), поэтому это очень быстро и эффективно.Так что для этого конкретного случая, вероятно, быстрее вернуть такие двухскалярные поля struct, чем сделать что-либо еще (например, сохранить их в указателе, переданном в качестве аргумента).

Возвращение такого двухскалярного поляstruct намного быстрее, чем malloc, и возвращает указатель.

5 голосов
/ 06 марта 2012

Это совершенно законно, но при больших структурах необходимо учитывать два фактора: скорость и размер стека.

4 голосов
/ 31 мая 2013

Безопасность зависит от того, как была реализована сама структура. Я только что наткнулся на этот вопрос при реализации чего-то подобного, и вот потенциальная проблема.

Компилятор при возврате значения выполняет несколько операций (среди прочего):

  1. Вызывает конструктор копирования mystruct(const mystruct&) (this - это временная переменная вне функция func, назначенная самим компилятором)
  2. вызывает деструктор ~mystruct для переменной, которая была выделена внутри func
  3. вызывает mystruct::operator=, если возвращаемое значение присваивается чему-либо еще с =
  4. вызывает деструктор ~mystruct для временной переменной, используемой компилятором

Теперь, если mystruct так же просто, как описано здесь, все хорошо, но если у него есть указатель (например, char*) или более сложное управление памятью, то все зависит от того, как mystruct::operator=, mystruct(const mystruct&) и ~mystruct реализованы. Поэтому я предлагаю предостеречь при возврате сложных структур данных в качестве значения.

4 голосов
/ 07 марта 2012

Совершенно безопасно возвращать структуру, как вы сделали.

Однако, основываясь на этом утверждении: Поскольку я понимаю, что, как только область действия функции закончится, любой стек будет использоваться длявыделить структуру можно перезаписать, Я мог бы представить себе только сценарий, в котором любой из членов структуры был бы динамически выделен (malloc'ed или new'ed), и в этом случае, без RVO, динамически распределенные члены будутуничтожен, и возвращенная копия будет иметь член, указывающий на мусор.

4 голосов
/ 07 марта 2012

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

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");
}
3 голосов
/ 07 марта 2012

Я также согласен с 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 содержит значение для.

2 голосов
/ 24 декабря 2014

Давайте добавим вторую часть к вопросу: зависит ли это от компилятора?

На самом деле, как я и обнаружил, это действительно так: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/

Я использовал gcc на win32 (MinGW) для вызова COM-интерфейсов, которые возвращали структуры.Оказывается, что MS делает это иначе, чем GNU, и поэтому моя (gcc) программа потерпела крах с разбитым стеком.

Возможно, что MS здесь имеет более высокий уровень - но все, что меня волнует, это совместимость ABI между MS.и GNU для сборки на Windows.

Если да, то каково поведение последних версий компиляторов для рабочих столов: gcc, g ++ и Visual Studio

Youможет найти некоторые сообщения в списке рассылки Wine о том, как MS, кажется, делает это.

...