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

Безопасно ли возвращать структуру на 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?

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

//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'ed

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

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

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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

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

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

Ответ 9

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

Ниже приведен простой пример подтверждения (взятый из сборки компилятора 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 по умолчанию содержит значение для.

Ответ 10

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

У меня был более сложный ответ, но модератору это не понравилось. Итак, по вашему расходованию, мой совет короткий.

Ответ 11

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

Действительно, это происходит, когда я обнаружил свою боль: 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 делает это.