Как компиляторы C реализуют функции, которые возвращают большие структуры?
Возвращаемое значение функции обычно сохраняется в стеке или в регистре. Но для большой структуры она должна быть в стеке. Сколько копий должно произойти в реальном компиляторе для этого кода? Или он оптимизирован?
Например:
struct Data {
unsigned values[256];
};
Data createData()
{
Data data;
// initialize data values...
return data;
}
(Предполагая, что функция не может быть встроена.)
Ответы
Ответ 1
Отсутствует; копии не выполняются.
Адрес вызывающего объекта Возвращаемое значение данных фактически передается как скрытый аргумент функции, а функция createData просто записывает в стек стека вызывающего абонента.
Это называется с именем return value optimization. Также см. С++ faq в этом разделе.
компиляторы коммерческого класса С++ реализуют возврат по значению таким образом, который позволяет им устранять накладные расходы, по крайней мере в простых случаях
...
Когда yourCode() вызывает 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
было выделено достаточно места для элемента данных subl $1032, %esp
, но обратите внимание, что он принимает первый аргумент в стеке 8(%ebp)
в качестве базового адреса объекта и затем инициализирует элемент 6 этого элемента, Поскольку мы не указывали никаких аргументов createData, это любопытно, пока вы не осознаете, что это секретный скрытый указатель на родительскую версию Data.
Ответ 2
Но для большой структуры он должен находиться в стеке heap.
В самом деле! В стеке выделяется большая структура, объявленная как локальная переменная. Рад, что это прояснилось.
Как и во избежание копирования, как отмечали другие:
-
Большинство соглашений о вызовах относятся к "функции, возвращающей структуру", передавая дополнительный параметр, указывающий местоположение в кадре стека вызывающего объекта, в котором должна быть размещена структура. Это определенно вопрос для вызывающего соглашения, а не для языка.
-
С помощью этого соглашения о вызове становится возможным даже, когда относительно простой компилятор замечает, когда путь кода определенно собирается вернуть структуру, и чтобы он исправлял назначения этим членам структуры, чтобы они переходили непосредственно в кадра вызывающего абонента и не нужно копировать. Ключ заключается в том, чтобы компилятор заметил, что все конечные пути кода через функцию возвращают одну и ту же структурную переменную. В этом случае компилятор может безопасно использовать пространство в кадре вызывающего абонента, исключая необходимость в копии в точке возврата.
Ответ 3
Существует много примеров, но в основном
Этот вопрос не имеет определенного ответа. это будет зависеть от компилятора.
C не указывает, как большие функции возвращаются из функции.
Здесь некоторые тесты для одного конкретного компилятора, gcc 4.1.2 на x86 RHEL 5.4
gcc тривиальный случай, без копирования
[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
gcc более реалистичный случай, выделить на стек, memcpy для вызывающего
#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
gcc 4.4.2 ### сильно вырос и не копируется для вышеуказанного нетривиального случая.
.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) зарезервирует struct Data в стеке createData() и выполнит цикл rep movsd
, чтобы скопировать его обратно в вызывающий в режиме Debug, в режиме Release он переместит возвращаемое значение rand() (% eax) непосредственно обратно вызывающему абоненту
Ответ 4
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);
Ответ 5
gcc on linux выдаст memcpy(), чтобы скопировать структуру обратно в стек вызывающего. Если функция имеет внутреннюю связь, тем больше становится доступнее оптимизация.