Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Стэк в scmRTOS
Форум разработчиков электроники ELECTRONIX.ru > Cистемный уровень проектирования > Операционные системы > scmRTOS
spongebob
Всем привет!

Поясните, пожалуйста, есть ли какая-нибудь взаимосвязь между стеком программы и стеками процессов в ОС?
И между их размерами...
Что конкретно хранится в стеках процессов?

Допустим, правильно ли следующее.

В системе два процесса (П1 и П2).
П1 завершает работу.
Планировщик копирует содержимое стека программы в стек П1, а содержимое стека П2 в стек программы и передает управление П2.
П2 работает, потом завершает работу.
Планировщик копирует содержимое стека программы в стек П2, а содержимое стека П1 в стек программы и передает управление П1.
Далее все повторяется по кругу...
Стеки процессов не должны превышать размеры стека программы. Допустим, если в mega1280 во внутренней памяти размещается только стек программы, то стеки каждого из процессов не должны превышать 8192 байт.

Бьюсь который день со спонтанной перезагрузкой, никак баг поймать не могу sad.gif
_Артём_
Цитата(spongebob @ Apr 24 2012, 21:19) *
Допустим, если в mega1280 во внутренней памяти размещается только стек программы, то стеки каждого из процессов не должны превышать 8192 байт.

Это чтож за ПРОЦЕСС с таким размером стека?

Цитата(spongebob @ Apr 24 2012, 21:19) *
Бьюсь который день со спонтанной перезагрузкой, никак баг поймать не могу sad.gif

Может стек здесь и ни причём?
Был у меня такой случай: задача копировала данные из кругового буфера в линейней для дальнейшей обработки. И в функции копирования была ошибка: функция начинала копировать из буфера ~64 кБ данных в линейный буфер и при этом переписывала всё ОЗУ. Через какое-то время происходил сброс (по WDT или как-то ещё).
spongebob
Цитата(_Артём_ @ Apr 24 2012, 22:40) *
Это чтож за ПРОЦЕСС с таким размером стека?


В процессе вызываются функции обработки данных, в которых используются локальные крупные массивы.
На самом деле памяти меньше съедается, я просто хочу понять принцип sm.gif
Например, что произойдет, если я объявлю размер стека в процессе 10 кБ, а стек программы, как писалось выше, будет иметь размер не более 8 кБ?

Цитата
Может стек здесь и ни причём?
Был у меня такой случай: задача копировала данные из кругового буфера в линейней для дальнейшей обработки. И в функции копирования была ошибка: функция начинала копировать из буфера ~64 кБ данных в линейный буфер и при этом переписывала всё ОЗУ. Через какое-то время происходил сброс (по WDT или как-то ещё).


Может и ни при чем...
Сторожевого таймера нет.
_Артём_
Цитата(spongebob @ Apr 24 2012, 21:52) *
Например, что произойдет, если я объявлю размер стека в процессе 10 кБ, а стек программы, как писалось выше, будет иметь размер не более 8 кБ?


У вас внешняя память?
Стек программы - стек на котором работают прерывания и main до вызова OS::Run.
Стеки для процессов задаются индивидуально и их размеры никак не связаны друг с другом (лишбы памяти хватило и при этом всё работало).
Какой у вас компилятор IAR или GCC?

Цитата(spongebob @ Apr 24 2012, 21:52) *
Сторожевого таймера нет.


Сейчас не помню точно был ли у меня включен WDT. Может и не был, но как оно тогда на reset попадало?
spongebob
Цитата(_Артём_ @ Apr 24 2012, 23:05) *
У вас внешняя память?


Ага, к 8 кБ на кристалле еще 32 кБ снаружи.

Цитата
Стек программы - стек на котором работают прерывания и main до вызова OS::Run.


Вы хотите сказать, что стек программы после запуска ОС не используется? sm.gif
Кстати, мы ведь ради интереса можем взять адрес какой-нибудь локальной переменной, принадлежащей процессу. По адресу будет видно где она лежит...

Цитата
Стеки для процессов задаются индивидуально и их размеры никак не связаны друг с другом (лишбы памяти хватило и при этом всё работало).


Об этом я знаю... я спрашивал как связан стек программы со стеками процессов... стеки процессов конечно не зависят друг от друга...

Цитата
Какой у вас компилятор IAR или GCC?


GCC.

Цитата
Сейчас не помню точно был ли у меня включен WDT. Может и не был, но как оно тогда на reset попадало?


Очень интересный вопрос, но для того, чтобы на резет попадало вотчдога не всегда нужно, нужен просто баг хороший wink.gif
_Артём_
Цитата(spongebob @ Apr 24 2012, 22:28) *
Ага, к 8 кБ на кристалле еще 32 кБ снаружи.

Интересно как скажется на производительности то, что стек расположен по внешней ОЗУ?

Цитата(spongebob @ Apr 24 2012, 22:28) *
Вы хотите сказать, что стек программы после запуска ОС не используется? sm.gif

Если не ошибаюся, то возможны варианты:
1. В прерывании используется TISRW - прерывание работает на стеке прерванного процесса.
2. В прерывании используется TISRW_SS - прерывание использует стек выделенный для функции main.

Цитата(spongebob @ Apr 24 2012, 22:28) *
я спрашивал как связан стек программы со стеками процессов... стеки процессов конечно не зависят друг от друга...

Да вроде никак не связан.
Какая между ними связь может вообще быть?

Цитата(spongebob @ Apr 24 2012, 22:28) *
Очень интересный вопрос, но для того, чтобы на резет попадало вотчдога не всегда нужно, нужен просто баг хороший wink.gif

Возможный вариант: моя функция копирования портила часть (или всё озу), но в конечном итоге делала ret. Но стек уже был заполнен непонятно чем. Програма была небольшая (10-15 кБ). И в результате возврат происходил в область заполненную FF. И программа могла дойти до адреса 0.
ReAl
Цитата(_Артём_ @ Apr 24 2012, 23:04) *
Интересно как скажется на производительности то, что стек расположен по внешней ОЗУ?
Каждый push будет занимать не два такта, а минимум три, зависимо от настроек внешнего интерфейса. Ну и вся работа с локальными переменными на стеке (LDD Rx, Y+d).
Стоило бы подумать о том, чтобі большие массивы не делать локальными, стеки разместить во внутреннем ОЗУ, а большие массивы — во внешнем.

Цитата(_Артём_ @ Apr 24 2012, 23:04) *
Если не ошибаюся, то возможны варианты:
1. В прерывании используется TISRW - прерывание работает на стеке прерванного процесса.
2. В прерывании используется TISRW_SS - прерывание использует стек выделенный для функции main.
Да. Второй вариант несколько медлительнее, но экономичнее по суммарному объёму стеков при разрешении вложенных прерываний.
При входе в первое прерывание регистры сохраняются на стеке прерванной задачи, а вложенные прерывания уже сохраняют всё на выделенном стеке общем для всех процессов.
Если вложенные прерывания не разрешаются, то для AVR особого смысла использовать TISRW_SS нет.
spongebob
Цитата(ReAl @ Apr 25 2012, 08:32) *
Стоило бы подумать о том, чтобі большие массивы не делать локальными, стеки разместить во внутреннем ОЗУ, а большие массивы — во внешнем.


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

По поводу размещения... тут я совсем слаб...
Делаем большие массивы глобальными и с помощью ключей компилятора (линкера) перемещаем секции data, bss во внешнюю память (-Wl,--section-start,.data=0x802200)?
А как тогда разместить стеки процессов во внутреннем ОЗУ? Если процессов много, то хватит ли им 8 кБ внутреннего ОЗУ? sm.gif
IgorKossak
Если массивы большие и нужда в них возникает редко, то может очень помочь использование кучи. Писал в своё время менеджер кучи для AVR, ничего сложного в этом нет. На производительность не слишком влияет ибо событие, как я уже говорил, редкое. Зато память экономится радикально.
ViKo
Если массивы временные, то почему бы не использовать один и тот же, глобальный, для разных целей? Только уже без ОС.
AHTOXA
Цитата(ViKo @ Apr 25 2012, 13:25) *
Если массивы временные, то почему бы не использовать один и тот же, глобальный, для разных целей? Только уже без ОС.
А почему это вы думаете, что ОС запрещает использовать глобальные объекты? sm.gif

Цитата(spongebob @ Apr 25 2012, 11:57) *
Фактически, большая часть из этих массивов - временные, нужны иногда, поэтому было принято решение делать их локальными, чтобы они создавались на стеке (типа, память сэкономить).

В scmRTOS 4.0 появился механизм для вычисления используемого каждым процессом стека (см. пример 4-Debug). Включите его, погоняйте. Узнаете, сколько чего и где. Может быть, всё спокойно влезет во внутреннее ОЗУ.
Цитата(spongebob @ Apr 25 2012, 11:57) *
По поводу размещения... тут я совсем слаб...

Почитайте про линкерные скрипты. Примеры используемых avr-gcc скриптов находятся в lib\ldscripts.
ViKo
Цитата(AHTOXA @ Apr 25 2012, 10:44) *
А почему это вы думаете, что ОС запрещает использовать глобальные объекты? sm.gif

Думаю, что в ОС будет сложно контролировать, кто пользуется в данный момент универсальным массивом.

upd. Разве что с кооперативной многозадачностью...
AHTOXA
А вот как раз для такого контроля в ОС и предусмотрены средства синхронизации sm.gif
ReAl
Цитата(spongebob @ Apr 25 2012, 08:57) *
Делаем большие массивы глобальными и с помощью ключей компилятора (линкера) перемещаем секции data, bss во внешнюю память (-Wl,--section-start,.data=0x802200)?
Не надо так. Не только стеки, все внутренние структуры scmRTOS полетят во внешнюю память. Вообще всё. С соответствующим замедлением. Во внутренней останется только «стек main()», который и использовать-то толком тяжело.

Перенести только нужное во внешнюю память можно так http://electronix.ru/forum/lofiversion/index.php/t47714.html

ViKo
Цитата(AHTOXA @ Apr 25 2012, 10:56) *
А вот как раз для такого контроля в ОС и предусмотрены средства синхронизации sm.gif

Что это? sm.gif Мьютексы?
Работа с массивами - дело долгое. От многозадачности мало что останется, пмсм.
AHTOXA
Цитата(ViKo @ Apr 25 2012, 13:57) *
Что это? sm.gif Мьютексы?
Работа с массивами - дело долгое. От многозадачности мало что останется, пмсм.

Да, мутексы. Тут ведь дело не в ОС/не ОС. А в логике работы. Если две разные задачи должны одновременно работать с одним массивом, то это проблемы в логике. Вероятнее, нужно так: одна задача готовит данные в массиве, вторая - обрабатывает. При таком подходе мутекс вполне оправдан - он позволяет второй задаче подождать, пока первая задача подготовит данные.
А в случае топикстартера всё похоже ещё проще - с массивом работает одна задача. Просто массив надо вынести из стека задачи. В этом случае можно и без мутекса.
spongebob
Цитата(ViKo @ Apr 25 2012, 11:57) *
Работа с массивами - дело долгое. От многозадачности мало что останется, пмсм.


Фактически дело обстоит так.
Имеется очередь (OS::channel) из элементов структур (в структуре массив 250 байт + еще несколько переменных).
Один процесс кладет в очередь данные (команды) когда нужно, другой процесс - извлекает их и обрабатывает.
Очередь нужна, чтобы "копить" команды, т. к., команды могут выполняться достаточно долго, а терять их очень нежелательно.
Ну и чтобы поместить элемент в очередь (push) и извлечь его оттуда (pop) приходится заводить временный элемент (тип и размер - выше), который и отжирает стек.
Сама очередь - глобальная.

Ну, может, архитектура не совсем нормальная... посоветуйте, пожалуйста, как лучше sm.gif

Цитата(AHTOXA @ Apr 25 2012, 12:24) *
А в случае топикстартера всё похоже ещё проще - с массивом работает одна задача. Просто массив надо вынести из стека задачи. В этом случае можно и без мутекса.


Да, с массивом работает одна задача. Но их как минимум два sm.gif
Принцип действия следующий.

Процесс 1 (главный, дает команды): команда -> функция подготовки команды и помещения ее в очередь (помещает данные в свой локальный буфер 1, а из него в очередь) -> очередь.
Процесс 2 (подчиненный выполняет команды): функция извлечения команды из очереди в буфер 2 и обработки команды.

Команды занимают приличный объем и имеют разный размер. Буферы 1 и 2 - одного типа, под все команды (универсальные).
Проблема в том как поместить в очередь команду, не используя для этого промежуточный буфер 1.

Цитата(ReAl @ Apr 25 2012, 11:56) *
Не надо так. Не только стеки, все внутренние структуры scmRTOS полетят во внешнюю память. Вообще всё. С соответствующим замедлением. Во внутренней останется только «стек main()», который и использовать-то толком тяжело.


Вот так вот изначально было сделано... фактически во внутренних 8 кБ остался только стек main().
Кстати, возвращаясь к вопросу об устройстве стеков при использовании ОС. После старта ОС стек main() не используется (планировщик при передаче управления заносит в указатель стека адрес стека требуемого процесса, который может быть расположен где угодно)?

А почему стек main() использовать тяжело?

Цитата
Перенести только нужное во внешнюю память можно так http://electronix.ru/forum/lofiversion/index.php/t47714.html


Спасибо, почитаем.
Как я понял, надо осваивать скриптописание для линкера? sm.gif
В двух словах, создаем специальные секции, в которые по необходимости переносим некоторые переменные, а эти секции линкуются куда надо (в данном случае во внешнюю память)?

Цитата(AHTOXA @ Apr 25 2012, 11:44) *
В scmRTOS 4.0 появился механизм для вычисления используемого каждым процессом стека (см. пример 4-Debug). Включите его, погоняйте. Узнаете, сколько чего и где. Может быть, всё спокойно влезет во внутреннее ОЗУ.


Очень интересно sm.gif

Цитата
Почитайте про линкерные скрипты. Примеры используемых avr-gcc скриптов находятся в lib\ldscripts.


Спасибо, почитаем, главное - понять что там написано sm.gif Пока линкерные скрипты для меня страшны sad.gif
_Артём_
Цитата(ReAl @ Apr 25 2012, 10:56) *
Во внутренней останется только «стек main()», который и использовать-то толком тяжело.

Можно наверное в нём что-то расположить, используя указатели: адрес ведь известен.

ViKo
Может, ляпну глупость, но, разве в очередь (message) обязательно пихать сам массив (структуру), а не указатель на него? В RTOS вроде, так делается?
AHTOXA
Цитата(spongebob @ Apr 25 2012, 16:37) *
Процесс 1 (главный, дает команды): команда -> функция подготовки команды и помещения ее в очередь (помещает данные в свой локальный буфер 1, а из него в очередь) -> очередь.
Процесс 2 (подчиненный выполняет команды): функция извлечения команды из очереди в буфер 2 и обработки команды.

Дык, помещайте в очередь не сам буфер, а его номерsm.gif (Ну или как правильно сказал ViKo, указатель на него).
spongebob
Цитата(_Артём_ @ Apr 25 2012, 15:52) *
Можно наверное в нём что-то расположить, используя указатели: адрес ведь известен.


А, ну это да, можно...

Цитата(AHTOXA @ Apr 25 2012, 16:59) *
Дык, помещайте в очередь не сам буфер, а его номерsm.gif (Ну или как правильно сказал ViKo, указатель на него).


Т. е., заводим большой массив (глобальный статический), который, к примеру, может вместить в себя 10 команд (размер данных каждой команды = размеру данных максимальной команды). Доступ к массиву между процессами разделяем с помощью мьютекса.
Процесс 1 помещает команду в массив, указатель на команду в массиве помещает в очередь команд (она теперь маленькая по объему).
Процесс 2 извлекает из очереди указатель на команду и по нему получает доступ к данным команды.
Так?
Но тогда придется как-то учитывать процессу 1, что в массиве по определенным адресам уже есть команды, чтобы не затереть ничего.
Сергей Борщ
QUOTE (spongebob @ Apr 25 2012, 17:56) *
Но тогда придется как-то учитывать процессу 1, что в массиве по определенным адресам уже есть команды, чтобы не затереть ничего.
Вот тут на помощь и приходит куча. Процесс 1 запрашивает у менеджера кучи область памяти нужного размера (а не максимального), получает указатель, заполняет память по этому указателю, после чего помещает указатель в очередь и забывает про него. Процесс 2 достает указетель из очереди, обрабатывает данные и дает менеджеру кучи команду освободить память. Поскольку команды у вас обрабатываются строго по порядку - проблема фрагментации кучи не возникнет. Забота "о том, чтобы ничего не затереть" ложится на менеджер кучи. Выкладывал тут на форуме переделанный под scmRTOS (завернутый в мутекс и переложенный на C++) легковесный менеджер кучи имени zltigo.

Кстати, при словах "Команды занимают приличный объем и имеют разный размер" приходят на ум умные слова "полиморфизм" и "кормление слона в примере 3-channel". Т.е. все команды можно сделать потомками одного абстрактного класса с виртуальной функцией execute(), которую и будет вызывать по извлеченному из канала указателю Процесс 2. И тогда команды очень легко и красиво создаются при помощи оператора new() и забирают из кучи ровно столько памяти, сколько необходимо. И заботу о необходимом каждой конкретной команде объеме памяти полностью берет на себя компилятор. А освобождается память после выполнения execute() оператором delete(). И при таком подходе компилятор же будет следить за тем, чтобы вы не забыли написать обработку для какой-либо команды.
spongebob
Цитата(Сергей Борщ @ Apr 25 2012, 20:38) *
Вот тут на помощь и приходит куча. Процесс 1 запрашивает у менеджера кучи область памяти нужного размера (а не максимального), получает указатель, заполняет память по этому указателю, после чего помещает указатель в очередь и забывает про него. Процесс 2 достает указетель из очереди, обрабатывает данные и дает менеджеру кучи команду освободить память. Поскольку команды у вас обрабатываются строго по порядку - проблема фрагментации кучи не возникнет. Забота "о том, чтобы ничего не затереть" ложится на менеджер кучи. Выкладывал тут на форуме переделанный под scmRTOS (завернутый в мутекс и переложенный на C++) легковесный менеджер кучи имени zltigo.

Кстати, при словах "Команды занимают приличный объем и имеют разный размер" приходят на ум умные слова "полиморфизм" и "кормление слона в примере 3-channel". Т.е. все команды можно сделать потомками одного абстрактного класса с виртуальной функцией execute(), которую и будет вызывать по извлеченному из канала указателю Процесс 2. И тогда команды очень легко и красиво создаются при помощи оператора new() и забирают из кучи ровно столько памяти, сколько необходимо. И заботу о необходимом каждой конкретной команде объеме памяти полностью берет на себя компилятор. А освобождается память после выполнения execute() оператором delete(). И при таком подходе компилятор же будет следить за тем, чтобы вы не забыли написать обработку для какой-либо команды.


Принцип понятен, спасибо большое за рекомендации.
Но как же мнение о том, что динамическая память для AVR - это слишком ресурсоемко и не выгодно?
Кстати, в WinAVR уже есть менеджер кучи и чем он отличается от выше Вами указанного?
IgorKossak
Цитата(spongebob @ Apr 26 2012, 08:12) *
Но как же мнение о том, что динамическая память для AVR - это слишком ресурсоемко и не выгодно?

Опять мифы!!!
Аналогичные мифы (применительно к МК) о C++, printf/scanf, RTOS и прочая циркулируют в эмбеддерской среде с пугающей живучестью.
Подумайте над следующим. Чьи ресурсы занимаются? Контроллера или лично Ваши (рабочее время, деньги)? Ваши затраты будут единоразовыми и неизвестно в каком случае больше - применив кучу или изголяясь окольными путями. В случае контроллера, если он решает поставленную задачу и решает успешно, то какая Вам разница? Попробуйте лично и определитесь.

PS Когда не устраивает библиотечный вариант какой-либо фичи, напишите или используйте кем-нибудь написанный готовый - облегчённый. Что касается варианта кучи, то Вам уже предложили или возьмите из второй версии scmRTOS.
Сергей Борщ
QUOTE (spongebob @ Apr 26 2012, 08:12) *
Но как же мнение о том, что динамическая память для AVR - это слишком ресурсоемко и не выгодно?
А вы попробуйте. На первый взгляд ресурсоемким и невыгодным выглядит решение без менеджера кучи. Менеджеры бывают разные...
В вашем случае менеджер можно упростить до предела, сделав его на базе кольцевого буфера. Выделение памяти двигает указатель головы, освобождение - указатель хвоста. Вы обещаете освобождать память строго в том же порядке, в котором она была выделена (а в случае вашей очереди это так и получается) и за счет этого менеджеру не нужны поиск свободного блока нужного размера, "склеивание" освобожденных блоков и т.д. А чтобы оставить возможность в дальнейшем для каких-то других целей использовать "настоящий" менеджер - можно обращаться к этому через переопределенный оператор new.

QUOTE (spongebob @ Apr 26 2012, 08:12) *
Кстати, в WinAVR уже есть менеджер кучи и чем он отличается от выше Вами указанного?
Не знаю. Я этот менеджер использовал на ARM, где родной библиотечный тянет за собой мама дорогая сколько лишнего. Менеджер из avr-libc я не использовал. Сравните, будет интересно узнать результаты.
ViKo
Цитата(Сергей Борщ @ Apr 26 2012, 10:28) *
В вашем случае менеджер можно упростить до предела, сделав его на базе кольцевого буфера. Выделение памяти двигает указатель головы, освобождение - указатель хвоста. Вы обещаете освобождать память строго в том же порядке, в котором она была выделена (а в случае вашей очереди это так и получается)...

Вряд ли такую строго упорядоченную конструкцию можно назвать кучей. Скорее уж, действительно, кольцевой буфер. Тогда к чему лишние процедуры выделения, освобождения...?
Я понимаю, работа с этими массивами идет постоянно. Вот и сделать их глобальными. Иметь некий индекс текущего рабочего буфера, им и манипулировать.
spongebob
Кстати, насколько прожорлив sprintf? Сколько он потребляет стека? Использует ли он кучу?
Сергей Борщ
Кучу - нет. По стеку можно посмотреть его исходники, порядка 20-30 байт плюс на передачу параметров (это относится только к sprintf из avr-libc, естественно).
spongebob
Цитата(Сергей Борщ @ Apr 27 2012, 01:06) *
Кучу - нет. По стеку можно посмотреть его исходники, порядка 20-30 байт плюс на передачу параметров (это относится только к sprintf из avr-libc, естественно).


Ну, получается и не прожорливый совсем... мельком погуглил - жалуются на прожорливость.
Да, вопрос про avr-libc sm.gif
Кстати, в WinAVR есть эти исходники (сейчас нет под рукой посмотреть, к сожалению)?
Наверное, в avr-libc sprintf жутко оптимизированный под AVRы? sm.gif
Сергей Борщ
QUOTE (spongebob @ Apr 27 2012, 09:25) *
Кстати, в WinAVR есть эти исходники (сейчас нет под рукой посмотреть, к сожалению)?
В WinAVR нет. Лежат отдельно в репозитории avr-libc.
QUOTE (spongebob @ Apr 27 2012, 09:25) *
Наверное, в avr-libc sprintf жутко оптимизированный под AVRы? sm.gif
Да.
ReAl
Цитата(Сергей Борщ @ Apr 27 2012, 00:06) *
Кучу - нет. По стеку можно посмотреть его исходники, порядка 20-30 байт
Полный вариант — немного больше сорока, добавляется буфер под максимально возможную длину для преобразования float. В очень старых версиях (~10-летней давности) полный вариант при наличии float-форматов выделял этот буфер через malloc. Это притянулось от какого-то распространённого варината форматтера, но быстро было исправлено.
Сергей Борщ
QUOTE (ReAl @ Apr 27 2012, 21:46) *
добавляется буфер под максимально возможную длину для преобразования float.
Что-то я не нашел его в исходниках. 11-байтовый плюс еще несколько локальных переменных видел, но они используются во всех вариантах. преобразование float скидывает 6 регистров на стек, плюс сами параметры printf на стек кладутся.
ReAl
Точно. Давненько я туда не заглядывал. Работает, да и ладно :-)
Когда-то давно-давно, до avr-libc 1.6, как я сейчас выяснил, было
Код
#define FLTBUFLEN 40
...
#if PRINTF_LEVEL >= PRINTF_FLT
    int8_t    decpt;
    char    fb[FLTBUFLEN];    /* floating point buffer */
#endif
остатки от форматтера, который и double умел.
Сейчас они 1) жёстко урезали до float с максимум 7 значащими цифрами и написанная на ассемблере __ftoa_engine в тот 11-байтовый буфер складывает флаги результата преобразования (в т.ч. NAN) и преобраованную мантиссу, а порядок возвращает в vfprintf как число, которое там уже выводится отдельно. Итого к стеку в самом vfprintf добавляется адрес возврата при вызове __ftoa_engine и шесть байтов для сохраняемых в ней регистров. Очень хорошо (то-то я как пример 4-debug написал, так удивился, что отладочній процесс с printf-ом ест меньше стека, чем яожидал :-) ).
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.