Редактировать: Как Майкл указал в комментариях, то, что написано ниже, действительно не будет работать в реальном мире из-за относительной к стеку адресации оптимизирующим компилятором. Поэтому для производственного уровня alloca
нужна помощь компилятора, чтобы фактически "работать". Но, надеюсь, приведенный ниже код может дать некоторые идеи о том, что происходит внутри, и о том, как могла бы работать такая функция, как alloca
, если бы не было оптимизаций адресации относительно стека, о которых можно было бы беспокоиться.
Кстати, на тот случай, если вам все еще будет любопытно, как вы можете сделать для себя простую версию alloca
, поскольку эта функция в основном возвращает указатель на выделенное пространство в стеке, вы можете написать функцию в сборке, которая может правильно манипулировать стеком и возвращать указатель, который вы можете использовать в текущей области действия вызывающей стороны (как только вызывающая сторона возвращает, указатель на пространство стека из этой версии alloca
становится недействительным, поскольку возврат от вызывающей стороны очищает стек).
Предполагая, что вы используете какой-то вариант Linux на платформе x86_64 с 64-битным ABI Unix, поместите следующее в файл с именем "my_alloca.s":
.section .text
.global my_alloca
my_alloca:
movq (%rsp), %r11 # save the return address in temp register
subq %rdi, %rsp # allocate space on stack from first argument
movq $0x10, %rax
negq %rax
andq %rax, %rsp # align the stack to 16-byte boundary
movq %rsp, %rax # save address in return register
pushq %r11 # push return address on stack
ret # return back to caller
Затем внутри вашего модуля кода C / C ++ (т. Е. Ваших файлов ".cpp") вы можете использовать его следующим образом:
extern my_alloca(unsigned int size);
void function()
{
void* stack_allocation = my_alloca(BUFFERSIZE);
//...do something with the allocated space
return; //WARNING: stack_allocation will be invalid after return
}
Вы можете скомпилировать my_alloca.s, используя gcc -c my_alloca.s
. Это даст вам файл с именем «my_alloca.o», который вы затем сможете использовать для связи с другими объектными файлами, используя gcc -o
или ld
.
.
Основная «ошибка», о которой я мог подумать в этой реализации, заключается в том, что вы можете потерпеть крах или получить неопределенное поведение, если компилятор не сработал, выделив место в стеке с помощью записи активации и базового указателя стека ( т. е. указатель RBP
в x86_64), а точнее выделенная память для каждого вызова функции. Затем, так как компилятор не будет знать о памяти, которую мы распределили в стеке, когда он очищает стек при возврате вызывающей стороны и пытается вернуться назад, используя то, что, по его мнению, является возвращаемым адресом вызывающей стороны. в стеке в начале вызова функции он перейдет к указателю инструкции, указывающему на no-wheres-ville, и вы, скорее всего, столкнетесь с ошибкой шины или каким-либо типом ошибки доступа, так как попытаетесь выполнять код в ячейке памяти, к которой вы не имеете права.
На самом деле могут происходить и другие опасные вещи, например, если компилятор использует пространство в стеке для распределения аргументов (это не должно применяться для этой функции в 64-битном ABI Unix, поскольку существует только один аргумент), так как снова вызовет очистку стека сразу после вызова функции, что испортит правильность указателя. Но с такой функцией, как execvp()
, которая не будет возвращаться, если не будет ошибки, это не должно быть такой большой проблемой.
В целом, подобная функция будет зависеть от платформы.