Возвращаемое значение функции обычно хранится на стеке или в регистре. Но для большой структуры, это должно быть на стеке. Сколько копирования должно произойти в реальном компиляторе для этого кода? Или это оптимизировано далеко?
Например:
struct Data {
unsigned values[256];
};
Data createData()
{
Data data;
// initialize data values...
return data;
}
(Принятие функции не может быть встроено..)
Нет; Копии не сделаны.
Адрес возвращаемого значения вызывающего абонента фактически передается в виде скрытого аргумента к функции, и функция REFORTATA просто записывает в кадр стека звонящего.
Это известно как под названием «Оптимизация возврата» . Также см. FAQ C ++ FAQ по этой теме .
Компиляторы коммерческого класса C ++ реализуют возвращаемое значение таким образом, что позволяет им устранить накладные расходы, по крайней мере, в простых случаях
...
, когда ваш youcode () вызывает RBV (), компилятор тайно проходит указатель на место, где должен построить RBV (), чтобы построить «возвращенный» объект.
Вы можете продемонстрировать, что это было сделано путем добавления деструктора с помощью printf на ваш структурой. Деструктор должен быть вызван только после того, как эта оптимизация возвращаемой стоимости находится в эксплуатации, в противном случае дважды.
Также вы можете проверить сборку, чтобы увидеть, что это происходит:
Data createData()
{
Data data;
// initialize data values...
data.values[5] = 6;
return data;
}
Вот сборка:
__Z10createDatav:
LFB2:
pushl %ebp
LCFI0:
movl %esp, %ebp
LCFI1:
subl $1032, %esp
LCFI2:
movl 8(%ebp), %eax
movl $6, 20(%eax)
leave
ret $4
LFE2:
Любопытно, что он выделил достаточно места на стеке для элемента данных SUBL $ 1032,% ESP
, Но обратите внимание, что приходит первый аргумент в стеке 8 (% EBP)
в качестве базового адреса объекта, а затем инициализирует элемент 6 этого элемента. Поскольку мы не указывали какие-либо аргументы для создания создания, это любопытно, пока вы не осознаете, что это секретный скрытый указатель на версию данных родителей.
Есть много приведенных примеров, но в основном
C не указывает, насколько большие структуры возвращаются из функции.
Вот некоторые тесты для одного конкретного компилятора, GCC 4.1.2 на X86 RHEL 5.4
[00:05:21 1 ~] $ gcc -O2 -S -c t.c
[00:05:23 1 ~] $ cat t.s
.file "t.c"
.text
.p2align 4,,15
.globl createData
.type createData, @function
createData:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl $1, 24(%eax)
popl %ebp
ret $4
.size createData, .-createData
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
.section .note.GNU-stack,"",@progbits
#include <stdlib.h>
struct Data {
unsigned values[256];
};
struct Data createData()
{
struct Data data;
int i;
for(i = 0; i < 256 ; i++)
data.values[i] = rand();
return data;
}
[00:06:08 1 ~] $ gcc -O2 -S -c t.c
[00:06:10 1 ~] $ cat t.s
.file "t.c"
.text
.p2align 4,,15
.globl createData
.type createData, @function
createData:
pushl %ebp
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
movl $1, %ebx
subl $1036, %esp
movl 8(%ebp), %edi
leal -1036(%ebp), %esi
.p2align 4,,7
.L2:
call rand
movl %eax, -4(%esi,%ebx,4)
addl $1, %ebx
cmpl $257, %ebx
jne .L2
movl %esi, 4(%esp)
movl %edi, (%esp)
movl $1024, 8(%esp)
call memcpy
addl $1036, %esp
movl %edi, %eax
popl %ebx
popl %esi
popl %edi
popl %ebp
ret $4
.size createData, .-createData
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
.section .note.GNU-stack,"",@progbits
.file "t.c"
.text
.p2align 4,,15
.globl createData
.type createData, @function
createData:
pushl %ebp
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
movl $1, %ebx
subl $1036, %esp
movl 8(%ebp), %edi
leal -1036(%ebp), %esi
.p2align 4,,7
.L2:
call rand
movl %eax, -4(%esi,%ebx,4)
addl $1, %ebx
cmpl $257, %ebx
jne .L2
movl %esi, 4(%esp)
movl %edi, (%esp)
movl $1024, 8(%esp)
call memcpy
addl $1036, %esp
movl %edi, %eax
popl %ebx
popl %esi
popl %edi
popl %ebp
ret $4
.size createData, .-createData
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
.section .note.GNU-stack,"",@progbits
Кроме того, VS2008 (скомпилирован вышеупомянутое как C), будет зарезервировать данные структуры на стопку создателя () и выполнять PREP MOVSD
, чтобы скопировать его обратно в звонящий в режим отладки, в режиме отпуска Режим он будет перемещать возвращаемое значение RAND () (% EAX) непосредственно обратно в звонящий
typedef struct {
unsigned value[256];
} Data;
Data createData(void) {
Data r;
calcualte(&r);
return r;
}
Data d = createData();
msvc(6,8,9) и gcc mingw(3.4.5,4.4.0) сгенерируют код типа следующего псевдокода
void createData(Data* r) {
calculate(&r)
}
Data d;
createData(&d);
Но для большой структуры она должна быть на
куче.
Действительно так! На стеке выделяется большая структура, объявленная как локальная переменная. Рад, что это очищено.
Что касается избежания копирования, как отмечали другие:
Большинство вызывающих соглашений имеют дело с "функцией, возвращающей структуру", передавая дополнительный параметр, указывающий расположение в кадре стека вызывающего абонента, в котором должна быть размещена структура. Это определенно относится к соглашению о вызове, а не к языку.
С помощью этого вызывающего соглашения даже относительно простой компилятор может заметить, что путь к коду определённо собирается вернуть структуру, и исправить присваивания членам этой структуры так, чтобы они попали прямо в кадр вызывающего и не были скопированы. Ключ к этому состоит в том, чтобы компилятор заметил, что все прерывающие кодовые пути через функцию возвращают ту же самую структурную переменную. В этом случае компилятор может безопасно использовать пробел в кадре вызывающего абонента, исключая необходимость копирования в точке возврата.
gcc на linux выдаст memcpy() для копирования структуры обратно на стек вызывающего абонента. Однако, если функция имеет внутреннюю связь, то становится доступно больше оптимизаций.