Безопасно ли возвращать struct в C или C++?



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



typedef struct{
int a,b;
}mystruct;


и тогда вот функция



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


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



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



мысли о в чем дело?

624   11  

11 ответов:

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

обычно, когда (как и в вашем примере) ваша структура не слишком велика, я бы сказал, что этот подход даже лучше, чем возврат структуры 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;
}

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

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

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

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

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

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

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

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

не только это безопасно, чтобы вернуться в struct в C (или a class в C++, где struct - s на самом деле class - es с default public: члены), но много программного обеспечения делает это.

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

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

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

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

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

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");
}

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

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

  1. вызывается конструктор копирования mystruct(const mystruct&) (this - это временная переменная за пределами функции func выделено самим компилятором)
  2. вызывает деструктор ~mystruct на переменную, которая была выделена внутри func
  3. звонки mystruct::operator= если возвращаемое значение присваивается чему-то другому с помощью =
  4. вызывает деструктор ~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 по умолчанию содержит значение для a.

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

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

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

действительно, как я обнаружил, к моей боли: 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

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

Comments

    Ничего не найдено.