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

    Комментарии (93) 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 rp@ sp0 sp@ и пр.:
      http://gforth.org/manual/Stack-pointer-manipulation.html#Stack-pointer-manipulation
      Ответить
      • В сишке можно просто выделить один большой кусок памяти и сделать аналог ALLOT.
        Ответить
    • Штука аллокации на стене такова, что априори ясно, кто будет освобождать. Если тебе нужен другой стек -- то в /bdsm/

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

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

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

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

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

                    Да, довольно часто регистры можно переназначить, чтобы бекапить ничего не требовалось, но удаётся переназначить не всегда. RCX всё-таки довольно часто используется как счётчик.
                    Ответить
                    • Лолшто. Даже в cdecl ecx относится к caller saved регистрам и не переживёт вызов. Т.е. тебе и там пришлось бы его бекапить.
                      Ответить
                      • Как всё сложно. Именно поэтому я за «PHP».
                        Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • Кстати, пуша и попа убрали из длинного режима x86_64. Мол, теперь перечисляйте явно, что хотите запушить, чтобы не запушить ничего лишнего.
                    Ответить
                    • показать все, что скрытоvanished
                      Ответить
                      • Подтверждаю. Да и пушить ~128 байт регистров всё же довольно накладно.
                        Ответить
                      • Ничтожества, ваша работа со стеком лишь забааляет меня! В армах у stm и ldm можно указать список регистров (будет битовый вектор в инструкции), и стек может расти в любую сторону. В Thumb режиме всё ограниченнее, там только ldmia, stmia, push и pop и регистров меньше.
                        Ответить
                    • Это вообще плохие инструкции, имхо. Они выполняют кучу микроопераций с неявным стейтом (в отличие от тех же rep movsb где стейт явный).

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

                      Блочить их пока всё не сохранится? Сохранять стейт во флаги (ARM так делает для ldmia)? Рестартить всю push'a с нуля после выхода из прерывания? Все варианты - полная жопа.
                      Ответить
                      • Кто сказал, что они обязаны сохранять регистры последовательно? Вдруг они сохраняли их параллельно? А вдруг?
                        Ответить
          • Там ещё и стек для локалок отдельный.
            Ответить
    • показать все, что скрытоvanished
      Ответить
    • В «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
        Ответить
    • показать все, что скрытоvanished
      Ответить
    • Xynta. Вся эта трескотня исходит из того, что локалы реализованы в стеке (благодаря дешевому стеку модели flat), хотя в спецификации сказано только, что они в волатильной памяти.
      Ответить
    • А куда запроподевался j123123?

      Где сишкопосты, обсёры крестов, а главное ГОМОИКОНЫ?
      Ответить
    • я иду обратно. опять. на те же грабли
      Ответить

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