Нашли или выдавили из себя код, который нельзя назвать нормальным,
на который без улыбки не взглянешь?
Не торопитесь его удалять или рефакторить, — запостите его на
говнокод.ру, посмеёмся вместе!
// вообще, есть одна говнистая особенность сишки:
// нельзя вернуть из функции массив хуйпойми какой длины, выделив память чисто на стеке
// Вот например:
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 там. То ли дело ассемблер!
Кстати, в FreeRTOS https://www.freertos.org/xTaskCreateStatic.html есть такая хрень, что для создаваемых тредов (точнее они там тасками называются) можно даже ткнуть указатель для их стека
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 нихуя такого нет - вы ему не можете ткнуть адрес куда он будет стеком своим срать, оно там за тебя его как-то аллоцирует. Не по-царски.
В S" Forth" есть PAD —– это такая область памяти в которой стандартные слова не срут и её можно использовать в своих целях, например как временный буфер. Ещё можно выделить память в словаре, вот так: HERE 100500 ALLOT, это просто инкремент указателя на свободную область памяти, освободить можно так: -100500 ALLOT. Если блок один, то можно без ALLOT, просто HERE, но тогда некоторые слова могут туды поднасрать, эта область часто используется как временный буфер.
Штука аллокации на стене такова, что априори ясно, кто будет освобождать. Если тебе нужен другой стек -- то в /bdsm/
А если хочешь, в алоцированую на стеке память записывать извне -- передай укозатель на то, что хочешь скопировать. Если у тебя генератор -- укозатель на функцию.
> почему бы не сделать хуйни
Дык в винапи давным-давно сделали подобную хуйню...
- зовёшь функцию с null'ом, она "приостанавливается" (возвращает управление) и говорит сколько надо байтов
- делаешь alloca/malloc/что там тебе удобнее
- зовёшь функцию ещё раз с этим буфером для "продолжения"
Это ж надо два раза функцию вызывать, один раз чтоб узнать сколько байтов, а второй чтоб их записать. Да и может оказаться, что для узнавания сколько байт надо, придется провести какие-то вычисления, а потом эти же вычисления придется проводить повторно, когда функцию вызовут во второй раз, чтоб уже записать те байты. Неоптимально.
Теоретически, можно было б сделать особый вид alloca, который был бы справедлив только для тех функций, которые всегда инлайнятся, назвать этот alloca можно как-нибудь типа alloca_in_caller т.е. типа выдели на стеке в той функции, которая меня вызывала, и дай мне указатель на ту вот хуйню
Да хуйня это всё. alloca() ты можешь адекватно заюзать только если знаешь верхнюю границу размера и она небольшая. А раз ты её знаешь и она небольшая - почему просто не сделать буфер на этот размер?
Ну заведи себе под эту хуйню второй стек на пару гиг.
Вызывающий запоминает позицию в этом стеке. Вызываемый размещает в нём кучу строк и структур и возвращает их адреса. Вызывающий юзает эти структуры и откатывает позицию стека когда они ему больше не нужны.
> А раз ты её знаешь и она небольшая - почему просто не сделать буфер на этот размер?
Тогда мне надо на каждый такой вызов функции делать какой-то говнобуфер и еще думать над тем, какой у него там будет размер максимальный. Ну и еще я могу передавать указатель на подобного рода функцию в некую другую функцию (callback) и тогда мне надо еще вместе с функцией передавать отдельным аргументом то, сколько максимум байтиков та функция может высрать. В общем хуйня какая-то.
Правильней было б придумать особое соглашение вызова. Вот например в GCC если функция возвращает через return некую структурку известного размера с кучей байтиков, то вызывающая функция резервирует у себя на стеке нужное количество байтов под эту структуру, а потом делает call. Ну т.е. стек выглядит так
...
... локальные переменные вызываемой функции
...
адрес возврата
... <-
... <-
... <- место, в которую вызываемая функция структурку хуйнет
... <-
... <-
...
... локальные переменные вызывающей функции
...
Но можно свое соглашение вызова придумать, т.е. сначала будет все как обычно, вызываемая функция где-то там в стек срет своими локальными переменными, адрес возврата где надо т.е.:
...
... локальные переменные вызываемой функции
...
адрес возврата
...
... локальные переменные вызывающей функции
...
Но в самом конце, когда надо хуйпоймикакой массивчик высрать в стек для другой функции, мы просто переписываем все локальные переменные в стеке этой хуйней, предварительно забэкапив адрес возврата и хуйнув куда-нибудь то, сколько байтиков мы насрали (можно в регистр)
адрес возврата (тут делаем ret)
... <-
... <-
... <- место, в которую вызываемая функция хуйнет хуйзнаетсколько байтиков
... <-
... <-
...
... локальные переменные вызывающей функции
...
Надо сделать особый экстеншн, чтоб можно было декларативно для любой функции описывать ее соглашение вызовов, и вообще чтоб свое ABI можно было изобретать, не переписывая компилятор
Только там нельзя описать такое abi, как я описал выше (с высиранием в стек массива заранее неизвестного размера). А на ассемблере такое можно намутить. Именно поэтому я за ассемблер!
> чтоб вызываемая функция как бы приосталавливалась и просила ту функцию, которая ее вызывает, чтоб она вот такой-то alloca() сделала нужного размера в своем стекфрейме
А как? Если вызывающая функция сделает у себя alloca() — она затрёт стекфрейм вызванной функции. Даже если мы волшебным образом сделаем отдельный стек для локальных переменных (джвух rsp у нас нету, придётся ебаться со скрытыми глобальными thread_local переменными, м-м-м…), адрес возврата всё равно будет лежать в «стандартном» стеке.
Так что во всём виноваты прыщи «ассемблер x86» с его кривыми стекфреймами.
Кстати, подозреваю, что решение хранить адрес возврата в общем стеке вместе с данными — самая дорогая и долгоиграющая ошибка в компьютерной истории. Её последствия аукаются даже теперь, через сорок лет после создания x86!
Я вспомнил проблему Эскобара о соглашениях вызова. Сейчас пропагандируют фасткал, когда аргументы передаются не через стек, а через регистры (по возможности). На платформе x86_64 фасткал вообще стал единственным стандартным соглашением (если быть точным, то их два: MS и не-MS). Считается, что это позволяет экономить стек и такты.
Когда доходит дело до использования, то выясняется, что перед вызовом функции вызывающий код должен забекапить содержимое своих регистров, чтобы использовать эти регистры для передачи параметров в функцию. Для резервного копирования обычно используют... всё тот же стек. Итого получается, что вызывающий код вместо того, чтобы пушить передаваемые данные (как было при стековых соглашениях вроде pascal, cdecl), пушит собственные промежуточные данные.
Но в «cdecl» нам нужно пушить всегда, а в «быстрыйзвонок» часто встречаются ситуации, когда rcx/rdx/r8/r9 после вызова нам не нужны.
А вот дебажить «fastcall» сложнее — в середине функции уже вряд ли получится узнать, какие были переданы аргументы, приходится ставить бряки на начало. То ли дело «cdecl»: промотал стек до конца стекфрейма, и все аргументы прекрасно видны.
Бекапить нужно не только тогда, когда аргумент нужен после вызова.
Довольно частая ситуация –— нужно вызвать функцию от кокококонстант. При «cdecl» я могу запушить непосредственную константу из кода, не трогая никаких регистров (кроме rsp и rip, разумеется). При «fastcall» мне придётся эту константу класть в регистр, а если этот регистр оказался подо что-то занят, мне придётся его бекапить.
Да, довольно часто регистры можно переназначить, чтобы бекапить ничего не требовалось, но удаётся переназначить не всегда. RCX всё-таки довольно часто используется как счётчик.
Забавно: я так делал когда писал под биос и дос: там тоже все параметры через регистры пердавались и нужно было делать пуша и попа.
А через регистры один хуй ничего толстого не влезет: все струхтуры передаются через посредство создания оной на стеке коли и копирования этого говна туда.
С другой стороны, дергать (R)SP ради пердачи инта четырехбайтового и правда глупо
Ничтожества, ваша работа со стеком лишь забааляет меня! В армах у stm и ldm можно указать список регистров (будет битовый вектор в инструкции), и стек может расти в любую сторону. В Thumb режиме всё ограниченнее, там только ldmia, stmia, push и pop и регистров меньше.
Это вообще плохие инструкции, имхо. Они выполняют кучу микроопераций с неявным стейтом (в отличие от тех же rep movsb где стейт явный).
И что делать если пришло прерывание?
Блочить их пока всё не сохранится? Сохранять стейт во флаги (ARM так делает для ldmia)? Рестартить всю push'a с нуля после выхода из прерывания? Все варианты - полная жопа.
Можно насрать в область, примыкающую к области локальных переменных вызывающей функции, не выделяя память, и вернуть размер этой области, чтобы вызывающая функция после возврата сама сделала alloca.
Минус: выделять придётся чуть больше, чем нужно, ибо между локальными переменными и свободным куском ещё лежит адрес разврата, параметры, не поместившиеся в регистрах, резервные копии регистров и локальные переменные вызываемой функции. Эта область (адрес разврата + параметры) после возврата не будет нужна, но останется лежать мёртвым грузом.
Если лишняя память представляет проблему, то вызывающая функция может дефрагментировать память с помощью memmove и скорректировать адрес массива.
И зачем она нужна? Это же просто указатель на функцию без контекста, если я правильно понял.
С контекстом была другая фигня, которую отменили: «Специальное свойство __caller__, возвращающее объект активации вызывающей функции и, таким образом, позволяющее восстанавливать стек вызовов, было удалено по соображениям безопасности».
Xynta. Вся эта трескотня исходит из того, что локалы реализованы в стеке (благодаря дешевому стеку модели flat), хотя в спецификации сказано только, что они в волатильной памяти.
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
А если хочешь, в алоцированую на стеке память записывать извне -- передай укозатель на то, что хочешь скопировать. Если у тебя генератор -- укозатель на функцию.
Оберни в струхтуру да верни. Копулятор тогда твои портянки скопирует во внешний стек
Если размер питушка мне не известен, то я делаю
Pitushok* CreatePitushok()
и
FreePitushok(Pitushok*)
Хотя я и отдаю себе отчет в том что malloc в охулион раз более тяжелая вжопурация чем выделить три байта в стеке
Да, от этого действительно можно подгореть
Ай фил ёр пэйн, чувак
Дык в винапи давным-давно сделали подобную хуйню...
- зовёшь функцию с 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 всё-таки довольно часто используется как счётчик.
А через регистры один хуй ничего толстого не влезет: все струхтуры передаются через посредство создания оной на стеке коли и копирования этого говна туда.
С другой стороны, дергать (R)SP ради пердачи инта четырехбайтового и правда глупо
Именно поэтому я за thumb-2. Да, ARM таки скатился в команды переменной длины.
Кстати, мамазоновые виртуалки есть на арме
Прикинь?
И что делать если пришло прерывание?
Блочить их пока всё не сохранится? Сохранять стейт во флаги (ARM так делает для ldmia)? Рестартить всю push'a с нуля после выхода из прерывания? Все варианты - полная жопа.
Literally "penis-to-understand", "fuckin undefined"
For example: "массив хуйпойми какой длины" -- variable length array
https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
Можно насрать в область, примыкающую к области локальных переменных вызывающей функции, не выделяя память, и вернуть размер этой области, чтобы вызывающая функция после возврата сама сделала alloca.
Минус: выделять придётся чуть больше, чем нужно, ибо между локальными переменными и свободным куском ещё лежит адрес разврата, параметры, не поместившиеся в регистрах, резервные копии регистров и локальные переменные вызываемой функции. Эта область (адрес разврата + параметры) после возврата не будет нужна, но останется лежать мёртвым грузом.
Если лишняя память представляет проблему, то вызывающая функция может дефрагментировать память с помощью memmove и скорректировать адрес массива.
С контекстом была другая фигня, которую отменили: «Специальное свойство __caller__, возвращающее объект активации вызывающей функции и, таким образом, позволяющее восстанавливать стек вызовов, было удалено по соображениям безопасности».
Я без тебе всi дни y полонi печалi.
А на ppc вместо дедика софтварная stack machine.
Ой...
Где сишкопосты, обсёры крестов, а главное ГОМОИКОНЫ?