Как измерить использование стека функций в C?

Есть ли способ измерить, сколько памяти стека использует функция?

Этот вопрос не относится к рекурсивным функциям; однако мне было интересно узнать, сколько памяти стека займет функция, вызываемая рекурсивно.

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

Для ясности, это не вопрос о том, как оптимизировать для лучшего использования стека

Итак, есть ли какой-нибудь надежный способ узнать, сколько памяти стека использует функция в C?


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


person ideasman42    schedule 12.02.2015    source источник
comment
Ты сможешь. Вам необходимо найти документ, описывающий ABI для используемой вами платформы и языковые сопоставления для данного типа языка. После этого вам нужно покопаться в документации вашего компилятора и найти детали реализации по организации фреймов стека и оптимизации автоматических переменных. После прочтения всего этого вы просто сгенерируете вывод сборки и посмотрите, как на самом деле используется указатель стека, потому что в противном случае это утомительно и неточно ...   -  person Valeri Atamaniouk    schedule 13.02.2015
comment
Я не пробовал этого, но одна идея, которая приходит в голову, если вы хотите обнаружить это динамически, скажем, для рекурсивной иерархии вызовов, заключается в том, чтобы вызвать функцию перед той, которая вас интересует, которая выделяет очень большой буфер стека и инициализирует его известным шаблоном, например [0,1,2,3,4,5,6 ..., 0,1,2,3,4,5 ...], а затем вызывает сопутствующую функцию, которая проверяет, какая часть известного шаблона осталась нетронутой. Это, конечно, не будет точным с точностью до байта, но может дать приблизительное представление об использовании стека.   -  person 500 - Internal Server Error    schedule 13.02.2015
comment
просто сгенерируйте вывод сборки и посмотрите, как на самом деле используется указатель стека Если вы компилируете с помощью gcc, вы можете использовать параметр -S для создания файла сборки из вашего .c файла, который вы можете проверить с любым Текстовый редактор. Другой вариант - использовать отладчик, который показывает код сборки. Таким образом, вы можете пошагово просмотреть код и увидеть, как используются указатель стека и базовый указатель.   -  person user3386109    schedule 13.02.2015
comment
Обратите внимание, это специфика GCC, но я думал выборочно использовать -Wframe-larger-than=###, чтобы найти предел стека ... Проблема в том, что я хочу применить его к одной функции. И похоже, #pragma GCC diagnostic не поддерживает -Wframe-larger-than   -  person ideasman42    schedule 13.02.2015
comment
Как насчет использования встроенной сборки для получения значения %ebp внутри вашей функции и внутри функции, которая вызывается вашей функцией?   -  person Aasmund Eldhuset    schedule 13.02.2015
comment
почему вы хотите оптимизировать использование стека? это странно, поскольку нет необходимости иметь реализацию стека в соответствии со стандартом c. даже если он есть, то, как используется стек, полностью зависит от компилятора и ОС,   -  person Jason Hu    schedule 13.02.2015
comment
Вы можете вызвать свою функцию, зарегистрировав указатель на две локальные переменные в стеке вызывающего и вызываемого соответственно, а затем вычтя их значение (после преобразования в целое число, чтобы избежать UB).   -  person The Paramagnetic Croissant    schedule 13.02.2015
comment
@HuStmpHrrr - хотя использование стека может зависеть от многих факторов, я бы хотя бы хотел знать, вызывает ли изменение моего кода существенные различия в использовании стека. Конечно, возможная другая конфигурация по-разному отреагирует на любое изменение.   -  person ideasman42    schedule 13.02.2015
comment
@ ideasman42, если вы можете получить результат с -Wframe-larger-than, переместите вашу функцию в отдельный модуль.   -  person Valeri Atamaniouk    schedule 13.02.2015
comment
@Valeri Atamaniouk, хотя это может работать, на практике это довольно хлопотно и может быть вызвано встроенными функциями в заголовках.   -  person ideasman42    schedule 13.02.2015
comment
@ ideasman42 так почему ваша цель минимизировать использование стека? Предполагая, что вы работаете в Linux, отметьте ulimit -s, чтобы показать максимальный размер стека, и обычно он будет ‹= 10 МБ, что совсем не велико. однако ulimit -d показывает максимальный размер кучи, который может быть бесконечным. Мне действительно не совсем понятно, что вы пытаетесь оптимизировать использование стека, поскольку даже если вы потратите на это время, отдача слишком мала, чтобы иметь значение.   -  person Jason Hu    schedule 13.02.2015
comment
@ ideasman42 Я неправильно понял вопрос и подумал, что он вам понадобится во время сборки. во время выполнения вам нужно заполнить стек шаблоном перед вызовом функции и проверить шаблон после.   -  person Valeri Atamaniouk    schedule 13.02.2015
comment
@HuStmpHrrr, система, в которой я работаю, помимо прочего, пишет код C для встроенных систем, например. это одна из причин, по которой вы можете захотеть экономно использовать стек.   -  person ideasman42    schedule 13.02.2015
comment
@ ideasman42 о, понял. Другое дело, когда вы говорите о встроенной системе. анализировать это действительно сложно.   -  person Jason Hu    schedule 13.02.2015
comment
Ну, можно примерно посчитать размер. если вы знаете, как устроен стек, вы можете вычислить размер ваших локальных переменных, параметров, указателя фрейма и указателя возврата. Теперь у вас есть такой размер, и вы умножаете его на звонки. Это только приблизительная оценка, поскольку могут быть дополнения и сохраненные регистры, которые вы не можете учесть.   -  person rfreytag    schedule 13.02.2015
comment
@pfannkuchen_gesicht - верно, вы можете сделать довольно точное предположение, если просто сложите все размеры (и учесть выравнивание), но не так просто узнать, какие переменные могут быть оптимизированы.   -  person ideasman42    schedule 13.02.2015
comment
@ ideasman42 Вы можете просмотреть руководство по компилятору, чтобы узнать о его особенностях. например если вы используете gcc, вы можете указать использование стека каждой из ваших функций с помощью флага -fstack-usage - хотя вам придется вычислить использование графа вызовов самостоятельно (например, если функция рекурсивная, умножьте ее с количеством рекурсий.)   -  person nos    schedule 13.02.2015
comment
@nos, в то время как для GCC это лучший ответ. Его немного неудобно использовать для одного файла / функции, поскольку это означает создание с разными CFLAGS. но я могу сделать некоторую вспомогательную утилиту для запуска этого в любом файле с правильными включениями, определениями.   -  person ideasman42    schedule 13.02.2015


Ответы (3)


Использование предупреждений

Это специфично для GCC (проверено с gcc 4.9):

Добавьте это над функцией:

#pragma GCC diagnostic error "-Wframe-larger-than="

Что сообщает об ошибках, таких как:

error: the frame size of 272 bytes is larger than 1 bytes [-Werror=frame-larger-than=]

Хотя это немного странный метод, вы можете, по крайней мере, сделать это быстро при редактировании файла.

Использование CFLAGS

Вы можете добавить -fstack-usage в свой CFLAGS, который затем записывает текстовые файлы вместе с объектными файлами. См. https://gcc.gnu.org/onlinedocs/gnat_ugn/Static-Stack-Usage-Analysis.html Хотя это работает очень хорошо, это может быть немного неудобно в зависимости от вашей системы сборки / конфигурации - создать один файл с другим CFLAG, хотя это, конечно, можно автоматизировать . - (спасибо комментарию @ nos)


Примечание,

Похоже, что большинство / все естественные методы компилятора полагаются на угадывание, что не на 100% точно останется точным после оптимизации, так что это, по крайней мере, дает окончательный ответ с использованием бесплатного компилятора.

person ideasman42    schedule 12.02.2015
comment
Я пытался использовать флаг -fstack-usage, но получаю ошибку компилятора. можете ли вы привести пример использования этого флага? - person Prabhakaran; 09.09.2020
comment
@ Karan2020 выложите пожалуйста ссылку на вашу ссылку - person vlad_tepesch; 16.09.2020
comment
@vlad_tepesch Ссылка на ссылку gcc.gnu.org/onlinedocs/gnat_ugn/ уже размещен в ответе. Я передал опцию компилятору GCC. Например: gcc -c имя_файла.c -fstack-usage. - person Prabhakaran; 18.09.2020

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

static byte* p1;
static byte* p2;
void f1()
{
    byte b;
    p1 = &b;
    f2();
}
void f2()
{
    byte b;
    p2 = &b;
}
void calculate()
{
    f1();
    int stack_space_used = (int)(p2 - p1);
}

(Примечание: функция объявляет локальную переменную, которая является только байтом, но компилятор обычно выделяет для нее целое машинное слово в стеке.)

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

person Mike Nakis    schedule 12.02.2015
comment
Я собирался сделать что-то подобное, но ваш пример немного упрощен. В том, что функция может иметь циклы, несколько переменных, определенных в разных ветвях, вызывать встроенные функции ... это не всегда так просто, как добавление одной переменной в конец блока и получение ее адреса. Кроме того, возможно, что компилятор повторно переменные заказов - stackoverflow.com/questions/238441 / - person ideasman42; 13.02.2015
comment
@ ideasman42 Я изменил свой ответ. Я настаиваю на том, что этот механизм - очень хороший подход, в основном из-за его простоты. - person Mike Nakis; 13.02.2015
comment
если у вас есть 3+ ветки кода, каждая со своими вложенными переменными, я не понимаю, как это может хорошо работать. Другими словами, AFAICS это хорошо работает только для функций, которые определяют все переменные в одном блоке. - person ideasman42; 13.02.2015
comment
Нет, повторяю, большинству компиляторов все равно, определяете ли вы их все в одном блоке или каждый в своем собственном блоке. Попробуй. - person Mike Nakis; 13.02.2015
comment
Ветви @ddriver совершенно неактуальны. Большинство компиляторов выделяют пространство стека для локальных переменных, как если бы все они были объявлены в корневой области видимости функции. Не верите мне? Попробуй. Я выложил код. Это так просто. Попробуй. - person Mike Nakis; 13.02.2015
comment
@ ideasman42 Да, если у вас есть вложенные ветки и переменные и т. д., для всего этого дерьма нужно создать пространство в преамбуле, есть вещи, которые могут сделать это беспорядочным, но встроенные функции не являются одними из них, которые место также будет включено ... - person Grady Player; 13.02.2015
comment
Это не будет динамическое число, включая весь стек вызовов, просто использование этой функции ... - person Grady Player; 13.02.2015
comment
@GradyPlayer Если вам нужно динамическое число, вы должны использовать принцип, который я показал в своем ответе, чтобы построить что-то более сложное, которое будет вычислять динамическое число. - person Mike Nakis; 13.02.2015
comment
Это действительно просто: думайте о f1() как о вашем main(), а о f2() как о функции, которую вы вызываете всякий раз, когда вам нужен ответ на вопрос, сколько места в стеке мы уже израсходовали? - person Mike Nakis; 13.02.2015
comment
Помимо других проблем с этим методом, о которых уже упоминалось, у этого метода могут быть проблемы, если включена оптимизация компилятора, что характерно для сборок выпуска и повлияет на использование стека. Ключевое слово volatile следует использовать при объявлении byte b, чтобы компилятор не оптимизировал эти переменные, что могло бы привести к получению адреса регистра или другого непригодного для использования значения. - person Jim Fell; 13.02.2015
comment
@JimFell Нет, не так. Компилятор не сможет оптимизировать b, если вы когда-нибудь возьмете адрес b. Он может по-прежнему оптимизировать значение b, но не сам b. И нас не волнует значение b. - person Mike Nakis; 13.02.2015
comment
@MikeNakis Не все компиляторы оптимизируют одинаково. - person Jim Fell; 13.02.2015
comment
@JimFell прав, но они стараются не ломать наши программы своими оптимизациями. Большую часть времени. - person Mike Nakis; 13.02.2015
comment
@MikeNakis В большинстве случаев это не все время. - person Jim Fell; 13.02.2015
comment
@JimFell не делайте этого, потому что у компилятора может быть ошибка, это недопустимый аргумент. - person Mike Nakis; 13.02.2015
comment
@MikeNakis Валидность в глазах смотрящего ... как и элегантность. - person Jim Fell; 13.02.2015
comment
@Mike Nakis, но почему вы думаете, что f1 содержит p1=&b инструкцию по назначению? Предположим, f1 - произвольная функция, которую нам не разрешено изменять. Как измерить количество стека, которое f1 будет использовать при вызове? - person mercury0114; 18.11.2020
comment
@ mercury0114 Это совсем другой вопрос. ОП говорит, что он хочет измерить использование стека своей функцией, чтобы оптимизировать ее, поэтому он явно контролирует свою функцию. - person Mike Nakis; 18.11.2020

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

void MyFunc( void );

void *pFnBottom = (void *)MyFunc;
void *pFnTop;
unsigned int uiStackUsage;

void MyFunc( void )
{
    __asm__ ( mov pFnTop, esp );
    uiStackUsage = (unsigned int)(pFnTop - pFnBottom);
}
person Jim Fell    schedule 12.02.2015
comment
Можете ли вы также определить pFnBottom и pFnTop внутри myFunc? - person étale-cohomology; 21.07.2021
comment
@ étale-cohomology Возможно, но это может повлиять на использование стека вашей функции. Даже использование ключевого слова register не гарантирует, что ваши переменные будут храниться в регистрах. Самый надежный способ - использовать реализацию, показанную с глобальными переменными. Вы можете объявить их статическими, чтобы ограничить их область действия. - person Jim Fell; 21.07.2021
comment
Спасибо! Понятно. - person étale-cohomology; 21.07.2021