−1
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
// вообще, есть одна говнистая особенность сишки:
// нельзя вернуть из функции массив хуйпойми какой длины, выделив память чисто на стеке
// Вот например:
char *a_ret (size_t len)
{
char *ret = alloca(len);
memset(ret, 'a', len);
return ret; // так нельзя
}
// т.е. надо делать как-нибудь вот так
char *a_ret (size_t len)
{
char *ret = malloc(len);
if (ret == NULL)
{
exit(ENOMEM);
}
memset(ret, 'a', len);
return ret;
}
Но это ж на самом-то деле говно какое-то. Дергать аллокаторы какие-то, ради каких-то мелких кусков байтиков.
Почему не сделать хуйни, чтоб вызываемая функция как бы приосталавливалась и просила ту функцию, которая ее вызывает, чтоб она вот такой-то alloca() сделала нужного размера в своем стекфрейме, а потом туда вот та вызванная функция байтики уже вхерачивала? Ну т.е. можно сделать отдельный свой стек для локальных переменных тех функций, которые должны уметь такую хуйню делать (т.е. просить вызвавшую их функцию "а сделай ка там себе alloca(123) и дай мне указатель, а я в него насру")
Вообще хуйня какая-то, сишка слишком сильно сковывает всякой своей хуйней, соглашениями вызовов, всякими ABI там. То ли дело ассемблер!
Запостил:
j123123,
15 Мая 2019
puxStackBuffer - Must point to a StackType_t array that has at least ulStackDepth indexes (see the ulStackDepth parameter above) - the array will be used as the task's stack, so must be persistent (not declared on the stack of a function).
Во всяких POSIX Threads и вендоговняном CreateThread нихуя такого нет - вы ему не можете ткнуть адрес куда он будет стеком своим срать, оно там за тебя его как-то аллоцирует. Не по-царски.
В ANS стандарт не включены слова для работы с указателями стека, но во многих системах они есть: rp0 rp@ sp0 sp@ и пр.:
http://gforth.org/manual/Stack-pointer-manipulation.html#Stack-pointer-manipulation
А если хочешь, в алоцированую на стеке память записывать извне -- передай укозатель на то, что хочешь скопировать. Если у тебя генератор -- укозатель на функцию.
Дык в винапи давным-давно сделали подобную хуйню...
- зовёшь функцию с null'ом, она "приостанавливается" (возвращает управление) и говорит сколько надо байтов
- делаешь alloca/malloc/что там тебе удобнее
- зовёшь функцию ещё раз с этим буфером для "продолжения"
Есть в крестоговне лямбды, вот такая хуйня:
это UB или не UB? На самом-то деле вообще хуй знает, учитывая что alloca не часть стандарта C++ и C
Но зато есть гнутое расширение https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html в котором хуйни с UB быть не должно
Теоретически, можно было б сделать особый вид alloca, который был бы справедлив только для тех функций, которые всегда инлайнятся, назвать этот alloca можно как-нибудь типа alloca_in_caller т.е. типа выдели на стеке в той функции, которая меня вызывала, и дай мне указатель на ту вот хуйню
Вызывающий запоминает позицию в этом стеке. Вызываемый размещает в нём кучу строк и структур и возвращает их адреса. Вызывающий юзает эти структуры и откатывает позицию стека когда они ему больше не нужны.
Тогда мне надо на каждый такой вызов функции делать какой-то говнобуфер и еще думать над тем, какой у него там будет размер максимальный. Ну и еще я могу передавать указатель на подобного рода функцию в некую другую функцию (callback) и тогда мне надо еще вместе с функцией передавать отдельным аргументом то, сколько максимум байтиков та функция может высрать. В общем хуйня какая-то.
Правильней было б придумать особое соглашение вызова. Вот например в GCC если функция возвращает через return некую структурку известного размера с кучей байтиков, то вызывающая функция резервирует у себя на стеке нужное количество байтов под эту структуру, а потом делает call. Ну т.е. стек выглядит так
Но можно свое соглашение вызова придумать, т.е. сначала будет все как обычно, вызываемая функция где-то там в стек срет своими локальными переменными, адрес возврата где надо т.е.:
Но в самом конце, когда надо хуйпоймикакой массивчик высрать в стек для другой функции, мы просто переписываем все локальные переменные в стеке этой хуйней, предварительно забэкапив адрес возврата и хуйнув куда-нибудь то, сколько байтиков мы насрали (можно в регистр)
Ну вот как-то так это можно было бы решить
А как? Если вызывающая функция сделает у себя alloca() — она затрёт стекфрейм вызванной функции. Даже если мы волшебным образом сделаем отдельный стек для локальных переменных (джвух rsp у нас нету, придётся ебаться со скрытыми глобальными thread_local переменными, м-м-м…), адрес возврата всё равно будет лежать в «стандартном» стеке.
Так что во всём виноваты прыщи «ассемблер x86» с его кривыми стекфреймами.
Вверху можно будет хранить большие переменные. А внизу мелкие, с точностью до бита.
Когда доходит дело до использования, то выясняется, что перед вызовом функции вызывающий код должен забекапить содержимое своих регистров, чтобы использовать эти регистры для передачи параметров в функцию. Для резервного копирования обычно используют... всё тот же стек. Итого получается, что вызывающий код вместо того, чтобы пушить передаваемые данные (как было при стековых соглашениях вроде pascal, cdecl), пушит собственные промежуточные данные.
А вот дебажить «fastcall» сложнее — в середине функции уже вряд ли получится узнать, какие были переданы аргументы, приходится ставить бряки на начало. То ли дело «cdecl»: промотал стек до конца стекфрейма, и все аргументы прекрасно видны.
Поставь мне бряку в дампе :(
В худшем случае, если аргумент нужен после вызова, ты бекапишь его.
Но если аргумент уже поюзан и не нужен, а часто это так, то бекапить ничего не надо. Профит.
Довольно частая ситуация –— нужно вызвать функцию от кокококонстант. При «cdecl» я могу запушить непосредственную константу из кода, не трогая никаких регистров (кроме rsp и rip, разумеется). При «fastcall» мне придётся эту константу класть в регистр, а если этот регистр оказался подо что-то занят, мне придётся его бекапить.
Да, довольно часто регистры можно переназначить, чтобы бекапить ничего не требовалось, но удаётся переназначить не всегда. RCX всё-таки довольно часто используется как счётчик.
Именно поэтому я за thumb-2. Да, ARM таки скатился в команды переменной длины.
И что делать если пришло прерывание?
Блочить их пока всё не сохранится? Сохранять стейт во флаги (ARM так делает для ldmia)? Рестартить всю push'a с нуля после выхода из прерывания? Все варианты - полная жопа.
https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
Можно насрать в область, примыкающую к области локальных переменных вызывающей функции, не выделяя память, и вернуть размер этой области, чтобы вызывающая функция после возврата сама сделала alloca.
Минус: выделять придётся чуть больше, чем нужно, ибо между локальными переменными и свободным куском ещё лежит адрес разврата, параметры, не поместившиеся в регистрах, резервные копии регистров и локальные переменные вызываемой функции. Эта область (адрес разврата + параметры) после возврата не будет нужна, но останется лежать мёртвым грузом.
Если лишняя память представляет проблему, то вызывающая функция может дефрагментировать память с помощью memmove и скорректировать адрес массива.
С контекстом была другая фигня, которую отменили: «Специальное свойство __caller__, возвращающее объект активации вызывающей функции и, таким образом, позволяющее восстанавливать стек вызовов, было удалено по соображениям безопасности».
Я без тебе всi дни y полонi печалi.
А на ppc вместо дедика софтварная stack machine.
Ой...
Где сишкопосты, обсёры крестов, а главное ГОМОИКОНЫ?