1. Си / Говнокод #25602

    −1

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 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

    Комментарии (72) RSS

    • Кстати, в 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, но тогда некоторые слова могут туды поднасрать, эта область часто используется как временный буфер.

      В ANS стандарт не включены слова для работы с указателями стека, но во многих системах они есть: rp0 [email protected] sp0 [email protected] и пр.:
      http://gforth.org/manual/Stack-pointer-manipulation.html#Stack-pointer-manipulation
      Ответить
      • В сишке можно просто выделить один большой кусок памяти и сделать аналог ALLOT.
        Ответить
    • Штука аллокации на стене такова, что априори ясно, кто будет освобождать. Если тебе нужен другой стек -- то в /bdsm/

      А если хочешь, в алоцированую на стеке память записывать извне -- передай укозатель на то, что хочешь скопировать. Если у тебя генератор -- укозатель на функцию.
      Ответить
    • Ебать ты додик.
      Оберни в струхтуру да верни. Копулятор тогда твои портянки скопирует во внешний стек
      Ответить
      • Ему переменной длины надо.
        Ответить
        • и типа длинуможно узнать только внутри функции, не снаружи?
          Ответить
          • Ну тип того. Довольно частая ситуация, на самом деле.
            Ответить
            • Я ф таких случаях не парюсь и малочу.

              Если размер питушка мне не известен, то я делаю

              Pitushok* CreatePitushok()
              и
              FreePitushok(Pitushok*)

              Хотя я и отдаю себе отчет в том что malloc в охулион раз более тяжелая вжопурация чем выделить три байта в стеке
              Ответить
              • Ну то-есть я напрягаю железо просто потому что язык говно.
                Да, от этого действительно можно подгореть

                Ай фил ёр пэйн, чувак
                Ответить
        • Нужно добавить в язык VLS —– Variable Length Strucrures.
          Ответить
    • > почему бы не сделать хуйни
      Дык в винапи давным-давно сделали подобную хуйню...
      - зовёшь функцию с null'ом, она "приостанавливается" (возвращает управление) и говорит сколько надо байтов
      - делаешь alloca/malloc/что там тебе удобнее
      - зовёшь функцию ещё раз с этим буфером для "продолжения"
      Ответить
      • Это ж надо два раза функцию вызывать, один раз чтоб узнать сколько байтов, а второй чтоб их записать. Да и может оказаться, что для узнавания сколько байт надо, придется провести какие-то вычисления, а потом эти же вычисления придется проводить повторно, когда функцию вызовут во второй раз, чтоб уже записать те байты. Неоптимально.

        Есть в крестоговне лямбды, вот такая хуйня:
        char *x1 = [](size_t i){ return (char *)alloca(i); }(10);
        x1[9] = 5;

        это UB или не UB? На самом-то деле вообще хуй знает, учитывая что alloca не часть стандарта C++ и C

        Но зато есть гнутое расширение https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html в котором хуйни с UB быть не должно

        Теоретически, можно было б сделать особый вид alloca, который был бы справедлив только для тех функций, которые всегда инлайнятся, назвать этот alloca можно как-нибудь типа alloca_in_caller т.е. типа выдели на стеке в той функции, которая меня вызывала, и дай мне указатель на ту вот хуйню
        Ответить
        • Да хуйня это всё. alloca() ты можешь адекватно заюзать только если знаешь верхнюю границу размера и она небольшая. А раз ты её знаешь и она небольшая - почему просто не сделать буфер на этот размер?
          Ответить
          • Ну если создать процесс с очень жирным стеком, скажем в пару гиг, то верхняя граница может быть и достаточно большой.
            Ответить
            • Ну заведи себе под эту хуйню второй стек на пару гиг.

              Вызывающий запоминает позицию в этом стеке. Вызываемый размещает в нём кучу строк и структур и возвращает их адреса. Вызывающий юзает эти структуры и откатывает позицию стека когда они ему больше не нужны.
              Ответить
            • Это уже какой-то совсем неадекватный сценарий.
              Ответить
          • > А раз ты её знаешь и она небольшая - почему просто не сделать буфер на этот размер?

            Тогда мне надо на каждый такой вызов функции делать какой-то говнобуфер и еще думать над тем, какой у него там будет размер максимальный. Ну и еще я могу передавать указатель на подобного рода функцию в некую другую функцию (callback) и тогда мне надо еще вместе с функцией передавать отдельным аргументом то, сколько максимум байтиков та функция может высрать. В общем хуйня какая-то.

            Правильней было б придумать особое соглашение вызова. Вот например в GCC если функция возвращает через return некую структурку известного размера с кучей байтиков, то вызывающая функция резервирует у себя на стеке нужное количество байтов под эту структуру, а потом делает call. Ну т.е. стек выглядит так
            ... 
            ... локальные переменные вызываемой функции
            ... 
            адрес возврата
            ... <-
            ... <-
            ... <- место, в которую вызываемая функция структурку хуйнет
            ... <-
            ... <-
            ...
            ...  локальные переменные вызывающей функции
            ...


            Но можно свое соглашение вызова придумать, т.е. сначала будет все как обычно, вызываемая функция где-то там в стек срет своими локальными переменными, адрес возврата где надо т.е.:
            ... 
            ... локальные переменные вызываемой функции
            ... 
            адрес возврата
            ... 
            ... локальные переменные вызывающей функции 
            ...

            Но в самом конце, когда надо хуйпоймикакой массивчик высрать в стек для другой функции, мы просто переписываем все локальные переменные в стеке этой хуйней, предварительно забэкапив адрес возврата и хуйнув куда-нибудь то, сколько байтиков мы насрали (можно в регистр)
            адрес возврата (тут делаем ret)
            ... <-
            ... <-
            ... <- место, в которую вызываемая функция хуйнет хуйзнаетсколько байтиков
            ... <-
            ... <-
            ...
            ... локальные переменные вызывающей функции
            ...

            Ну вот как-то так это можно было бы решить
            Ответить
            • Надо сделать особый экстеншн, чтоб можно было декларативно для любой функции описывать ее соглашение вызовов, и вообще чтоб свое ABI можно было изобретать, не переписывая компилятор
              Ответить
              • В «Watcom C» можно описывать свой ABI с помощью #pragma. Именно поэтому...
                Ответить
                • Примеры:
                  #pragma aux __cdecl "_*" \
                      parm    caller [] \
                      value    struct float struct routine [eax] \
                      modify [eax ecx edx]
                  
                  #pragma aux __pascal "^" \
                      parm    reverse routine [] \
                      value    struct float struct caller [] \
                      modify [eax ebx ecx edx]
                  
                  #pragma aux __stdcall "_*@nnn" \
                      parm    routine [] \
                      value    struct struct caller [] \
                      modify [eax ecx edx]
                  
                  #pragma aux __syscall "*" \
                      parm    caller [] \
                      value    struct struct caller [] \
                      modify [eax ecx edx]
                  
                  #pragma aux __watcall "*_" \
                      parm    routine [eax ebx ecx edx] \
                      value    struct caller
                  Ответить
                  • Только там нельзя описать такое abi, как я описал выше (с высиранием в стек массива заранее неизвестного размера). А на ассемблере такое можно намутить. Именно поэтому я за ассемблер!
                    Ответить
    • > чтоб вызываемая функция как бы приосталавливалась и просила ту функцию, которая ее вызывает, чтоб она вот такой-то alloca() сделала нужного размера в своем стекфрейме
      А как? Если вызывающая функция сделает у себя alloca() — она затрёт стекфрейм вызванной функции. Даже если мы волшебным образом сделаем отдельный стек для локальных переменных (джвух rsp у нас нету, придётся ебаться со скрытыми глобальными thread_local переменными, м-м-м…), адрес возврата всё равно будет лежать в «стандартном» стеке.

      Так что во всём виноваты прыщи «ассемблер x86» с его кривыми стекфреймами.
      Ответить
      • Кстати, подозреваю, что решение хранить адрес возврата в общем стеке вместе с данными — самая дорогая и долгоиграющая ошибка в компьютерной истории. Её последствия аукаются даже теперь, через сорок лет после создания x86!
        Ответить
        • Зато его там удобно затереть
          Ответить
        • Что-то мне намекает, что общий стек для переменных и возвратов юзался задолго до х86. Как минимум на 8080 уже так было.
          Ответить
          • И то верно. А вот в 8008 стека для данных не было, и именно поэтому я за «8008» а был только кольцевой стек адресов развратов на семь ячеек:
            http://bitsavers.informatik.uni-stuttgart.de/components/intel/MCS8/MCS_8_Assembly_Language_Programming_Manual_Preliminary_Edition_Nov73.pdf
            .
            Ответить
            • Круто! Оказывается, у 8008 переполнения стека не бывает, просто «лишние» данные затираются.
              Ответить
              • кольцо же
                Ответить
                • Уж больно маленькое кокококольцо. Теперь понятно, почему старые программисты боялись рекурсивных функций.
                  Ответить
                  • да даже и без кольца рекукурсия страшна потому что стек масенький и ты его не контролируешь по сути
                    Ответить
          • Можно под адреса возврата плавучепитуховый стек использовать
            Ответить
            • Засранчик, у нас нет плавающего петуха.
              Ответить
            • Целые числа слишком скучные и ограниченные. Лучше повысить уровень абстракции и перевести всю адресацию на плавучих питухов.
              Ответить
              • Вообще у 80x87 есть инструкции для целых чисел (FIST, FILD). Но вариант с плавучими числами забавнее: чем выше адрес, тем грубее будет адресация.
                Ответить
                • > тем грубее будет адресация
                  Вверху можно будет хранить большие переменные. А внизу мелкие, с точностью до бита.
                  Ответить
              • пол бита
                Ответить
              • for (int *i = &a; i < a+1; i += 1.0 / 8) {
                    *c++ = "01" [*i & INT_MIN != 0];
                }
                
                Ответить
        • З.Ы. В форте стек данных отделён от стека развратов. Именно поэтому я за "форт".
          Ответить
          • Идея общего стека хорошо ложица на идею локальных (автоаатических) переменных няшной

            Выпездовал со стека все локальные переменные и потом адрес возврата.

            Если стека два, то надо как-то это синкать иначе можно случайно утечь локальными переменными
            Ответить
            • А какая разница? Один хрен в обоих вореантах надо чистить все локальные переменные, а адрес разврата сам почистится в RET.
              Ответить
              • Я вспомнил проблему Эскобара о соглашениях вызова. Сейчас пропагандируют фасткал, когда аргументы передаются не через стек, а через регистры (по возможности). На платформе x86_64 фасткал вообще стал единственным стандартным соглашением (если быть точным, то их два: MS и не-MS). Считается, что это позволяет экономить стек и такты.

                Когда доходит дело до использования, то выясняется, что перед вызовом функции вызывающий код должен забекапить содержимое своих регистров, чтобы использовать эти регистры для передачи параметров в функцию. Для резервного копирования обычно используют... всё тот же стек. Итого получается, что вызывающий код вместо того, чтобы пушить передаваемые данные (как было при стековых соглашениях вроде pascal, cdecl), пушит собственные промежуточные данные.
                Ответить
                • Но в «cdecl» нам нужно пушить всегда, а в «быстрыйзвонок» часто встречаются ситуации, когда rcx/rdx/r8/r9 после вызова нам не нужны.
                  А вот дебажить «fastcall» сложнее — в середине функции уже вряд ли получится узнать, какие были переданы аргументы, приходится ставить бряки на начало. То ли дело «cdecl»: промотал стек до конца стекфрейма, и все аргументы прекрасно видны.
                  Ответить
                  • > ставить бряки
                    Поставь мне бряку в дампе :(
                    Ответить
                    • Поставил бряку в твой дамп, проверь. Да, это вообще печально.
                      Ответить
                • Да не, не всё так печально. Оно реально такты экономит. И даёт больше шансов на оптимизацию.

                  В худшем случае, если аргумент нужен после вызова, ты бекапишь его.

                  Но если аргумент уже поюзан и не нужен, а часто это так, то бекапить ничего не надо. Профит.
                  Ответить
                  • Бекапить нужно не только тогда, когда аргумент нужен после вызова.

                    Довольно частая ситуация –— нужно вызвать функцию от кокококонстант. При «cdecl» я могу запушить непосредственную константу из кода, не трогая никаких регистров (кроме rsp и rip, разумеется). При «fastcall» мне придётся эту константу класть в регистр, а если этот регистр оказался подо что-то занят, мне придётся его бекапить.

                    Да, довольно часто регистры можно переназначить, чтобы бекапить ничего не требовалось, но удаётся переназначить не всегда. RCX всё-таки довольно часто используется как счётчик.
                    Ответить
                    • Лолшто. Даже в cdecl ecx относится к caller saved регистрам и не переживёт вызов. Т.е. тебе и там пришлось бы его бекапить.
                      Ответить
                      • Как всё сложно. Именно поэтому я за «PHP».
                        Ответить
                • Забавно: я так делал когда писал под биос и дос: там тоже все параметры через регистры пердавались и нужно было делать пуша и попа.

                  А через регистры один хуй ничего толстого не влезет: все струхтуры передаются через посредство создания оной на стеке коли и копирования этого говна туда.

                  С другой стороны, дергать (R)SP ради пердачи инта четырехбайтового и правда глупо
                  Ответить
                  • Кстати, пуша и попа убрали из длинного режима x86_64. Мол, теперь перечисляйте явно, что хотите запушить, чтобы не запушить ничего лишнего.
                    Ответить
                    • будем чесны: пуша и попа были нужны когда их вызывали руками. Копулятору пофиг что там перечислять. Будет чуть больше размер кода, но всем похуй
                      Ответить
                      • Подтверждаю. Да и пушить ~128 байт регистров всё же довольно накладно.
                        Ответить
                      • Ничтожества, ваша работа со стеком лишь забааляет меня! В армах у stm и ldm можно указать список регистров (будет битовый вектор в инструкции), и стек может расти в любую сторону. В Thumb режиме всё ограниченнее, там только ldmia, stmia, push и pop и регистров меньше.
                        Ответить
                        • > все ограниченнее
                          Именно поэтому я за thumb-2. Да, ARM таки скатился в команды переменной длины.
                          Ответить
                        • x86 говно
                          Ответить
                          • Ну, кстати, в прошлом году ARM'яне собирались потеснить интелов на лэпжопах, но что-то пока не заметно.
                            Ответить
                            • слишком много хуиты скопулировано для интела

                              Кстати, мамазоновые виртуалки есть на арме
                              Прикинь?
                              Ответить
                    • Это вообще плохие инструкции, имхо. Они выполняют кучу микроопераций с неявным стейтом (в отличие от тех же rep movsb где стейт явный).

                      И что делать если пришло прерывание?

                      Блочить их пока всё не сохранится? Сохранять стейт во флаги (ARM так делает для ldmia)? Рестартить всю push'a с нуля после выхода из прерывания? Все варианты - полная жопа.
                      Ответить
                      • Кто сказал, что они обязаны сохранять регистры последовательно? Вдруг они сохраняли их параллельно? А вдруг?
                        Ответить
          • Там ещё и стек для локалок отдельный.
            Ответить
    • "хуйпойми" (rus. variable, something without of predefined value).
      Literally "penis-to-understand", "fuckin undefined"

      For example: "массив хуйпойми какой длины" -- variable length array
      Ответить
    • В «gcc» есть встроенные функции для получения стека вызывающей функции:
      https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html

      Можно насрать в область, примыкающую к области локальных переменных вызывающей функции, не выделяя память, и вернуть размер этой области, чтобы вызывающая функция после возврата сама сделала alloca.

      Минус: выделять придётся чуть больше, чем нужно, ибо между локальными переменными и свободным куском ещё лежит адрес разврата, параметры, не поместившиеся в регистрах, резервные копии регистров и локальные переменные вызываемой функции. Эта область (адрес разврата + параметры) после возврата не будет нужна, но останется лежать мёртвым грузом.

      Если лишняя память представляет проблему, то вызывающая функция может дефрагментировать память с помощью memmove и скорректировать адрес массива.
      Ответить
      • а в жопаскрипте есть нестандартизированная функция, которая возвращает родительскую функцию https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/caller
        Ответить
        • И зачем она нужна? Это же просто указатель на функцию без контекста, если я правильно понял.

          С контекстом была другая фигня, которую отменили: «Специальное свойство __caller__, возвращающее объект активации вызывающей функции и, таким образом, позволяющее восстанавливать стек вызовов, было удалено по соображениям безопасности».
          Ответить
    • Нахуй ты вообще с этим ебёшся? Меньше выёбываешься - меньше шансов отстрелить ногу.
      Ответить

    Добавить комментарий