Сколько стека и кучи (в байтах) требуется для функции C в X86 - PullRequest
0 голосов
/ 02 января 2019

Реализация функции sum в «C» выглядит следующим образом:

int sum(int b[], int c)
{
    int s,i;

    if (c<0)
    {
            printf("ERROR\n");
    }
    s = 0;

    for(i=0; i<c; ++i)
    {
        s = s + b[i];
    }
    return s;
}

Я хотел бы знать, сколько стека и кучи в байтах требуется для функции sum в платформе X86 Linux? Как это узнать?

Вероятно ли, что вызов функции из обработчика прерываний будет проблематичным или успешным?

Ответы [ 3 ]

0 голосов
/ 03 января 2019

Чтобы развить то, на что уже указывали другие пользователи, я постараюсь ответить на оба вопроса ОП.

Первый вопрос ОП:

Я хотел бы знать, сколько стека и кучи в байтах требуется для сумма функций в платформе X86 Linux? Как это узнать?

Мы можем разбить этот первый вопрос на 2 части. Один о размере стека, а другой о размере кучи.

Размер стека:

Чтобы узнать, сколько стека использует ваша функция, вы можете использовать одну из диагностических прагм GCC , то есть прагму -Wframe-larger-than=<X>. Вот пример того, как его использовать. Сначала мы добавляем прагму в код и сохраняем файл.

main.cpp

#include <stdio.h>
#pragma GCC diagnostic error "-Wframe-larger-than=1"

int sum(int b[], int c) {
    int s,i;
    if (c<0) {
            printf("ERROR\n");
    }
    s = 0;
    for(i=0; i<c; ++i) {
        s = s + b[i];
    }
    return s;
}

Теперь мы можем попытаться скомпилировать код:

junglefox@ubuntu:~$ gcc -c main.cpp
main.cpp: In function ‘int sum(int*, int)’:
main.cpp:20:1: error: the frame size of 32 bytes is larger than 1 bytes [-Werror=frame-larger-than=]
 }
 ^
cc1plus: some warnings being treated as errors
junglefox@ubuntu:~$

, который сообщает размер 32 байта .

  • Альтернативным методом измерения размера стека является использование флага компилятора stack-usage в GCC . Поэтому мы удаляем или закомментируем строку // #pragma GCC diagnostic error "-Wframe-larger-than=1" и пытаемся снова скомпилировать файл, как показано ниже.

junglefox@ubuntu:~$ gcc -c main.cpp -fstack-usage

Это создаст файл main.su.

junglefox@ubuntu:~$ cat main.su 
main.cpp:5:5:int sum(int*, int) 48  static

, который показывает, что, по-видимому, мы используем 48 байт стека.


Размер кучи

Чтобы узнать, какой размер кучи использует наша программа, мы будем использовать инструмент valgrind Massif. Для этого нам сначала нужно добавить функцию main () в наш код (без которой мы не можем создать двоичный файл. И двоичный файл - это то, что нам нужно запустить с valgrind). Итак, main.cpp, выглядит сейчас так,

#include <stdio.h>
// #pragma GCC diagnostic error "-Wframe-larger-than=1"

int sum(int b[], int c) {
    int s,i;
    if (c<0) {
            printf("ERROR\n");
    }
    s = 0;
    for(i=0; i<c; ++i) {
        s = s + b[i];
    }
    return s;
}

int main() {
    // As Peter pointed, uncomment one of the following lines,
    // for it to be a valid test. Also, compiler optimizations,
    // when turned on, can give different results.
    // sum(NULL,0);
    // sum(NULL,-1);
    return 0;
}

А теперь мы скомпилируем, соберем и запустим бинарный файл с помощью valgrind, как показано здесь:

junglefox@ubuntu:~$ gcc -o main main.cpp
junglefox@ubuntu:~$ valgrind ./main --tool=massif

Это сгенерирует кучу информации, которая выглядит примерно так:

    ==8179== Memcheck, a memory error detector
==8179== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8179== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==8179== Command: ./main --tool=massif
==8179== 
==8179== 
==8179== HEAP SUMMARY:
==8179==     in use at exit: 0 bytes in 0 blocks
==8179==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==8179== 
==8179== All heap blocks were freed -- no leaks are possible
==8179== 
==8179== For counts of detected and suppressed errors, rerun with: -v
==8179== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

, который сообщает об общем использовании кучи в 0 килобайт.

Кроме того, как попытался объяснить @mevets, вы всегда можете заглянуть в базовый код сборки, сгенерированный компилятором. В GCC вы могли бы сделать,

junglefox@ubuntu:~/gcc -S main.cpp
junglefox@ubuntu:~/cat main.s

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

ПРИМЕЧАНИЕ / РЕДАКТИРОВАТЬ: Но для полноты, в C или C ++, без динамического выделения памяти с использованием malloc() или new, вы, как программист, НЕ используете кучу. Кроме того, если вы не объявляете массив внутри своей функции, вы не используете какой-либо значительный объем стека.


Второй вопрос ОП:

Вероятен ли вызов функции из обработчика прерываний быть проблемным или успешным?

Как многие люди любезно указали в комментариях, НЕ используйте printf() в вашем Обработчике прерываний .

Цитировать из этой ссылки :

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

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

Итак, кроме printf(), одна вещь, которая может занять много времени, - это размер массива, который вы передаете этой функции, когда используется как Interrupt Service Routine. Он имеет сложность O(n). Если c слишком велик, ваша программа будет остановлена ​​на относительно долгое время, пока ISR не завершит цикл for().

0 голосов
/ 03 января 2019

C не имеет понятия «стек»; и когда код, написанный на C, скомпилирован, вы должны думать о нем как о том, что он искажен до неузнаваемой формы.

Например, если вызывающая сторона делает это:

    myArray[2] = 99;
    result = sum( myArray, 4);

Затем компилятор может встроить совершенно отдельную копию функции sum(), а затем (используя такие оптимизации, как свертывание констант, удаление мертвого кода и развертывание цикла) преобразовать эту отдельную копию функции в (эквивалент):

    result = myArray[0] + myArray[1] + 99 + myArray[3];

.. и затем аннотируйте его (или преобразуйте в форму «одиночного статического назначения») для обеспечения большего параллелизма, например:

    temp1 = (myArray[0] + myArray[1]); temp2 = (99 + myArray[3]);
    result = temp1 + temp2;

.. а затем преобразовать в нечто вроде:

    mov eax,[myArray]
    mov ebx,[myArray+4]
    lea eax,[eax+ebx]

    mov ecx,99
    mov edx,[myArray+12]
    lea ecx,[ecx+edx]

    lea eax,[eax+ecx]

    mov [result],eax

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

    mov eax,[myArray]
    mov ebx,[myArray+4]
    mov edx,[myArray+12]
    lea eax,[eax+ebx]
    lea eax,[eax+edx+99]
    mov [result],eax

Обратите внимание, что это не похоже на оригинальный код - например, нет printf(), нет петли и нет ветвей.

Конечно (учитывая, что функция не static, и при условии, что не выполняется оптимизация времени соединения или генерация кода времени соединения), компилятор, вероятно, также сгенерирует версию "Я ничего не знаю о вызывающей стороне" функционировать и вставить это в выходной объектный файл на случай, если компоновщик этого захочет; и (если никакие другие объектные файлы не используют функцию), компоновщик может выбросить эту версию функции. В таком случае; Вы можете посмотреть на код, сгенерированный компилятором для версии «Я ничего не знаю о вызывающей программе», а затем сделать бесполезные / неправильные предположения, основанные на количестве стека, используемого кодом, который отбрасывается и никогда не выполняется.

0 голосов
/ 02 января 2019

Сколько стоит стек?После удаления printf требуется 0 байтов стека;функция достаточно проста для работы с 3-мя регистрами:

.globl _sum
_sum: /* (int *b, int c); */
    mov 4(%esp), %edx
    mov 8(%esp), %ecx
    cmp $0, %ecx
    jl   badcount
    leal (%edx,%ecx,4), %ecx
    xor %eax, %eax
nxt:
    cmp %ecx, %edx
    je    done
    add (%edx), %eax
    add $4, %edx
    jmp nxt
done:
    ret
badcount:
    mov $-1, %eax
    ret

Вы должны иметь возможность полагаться на компилятор, чтобы не делать глупостей (независимо от того, что думает стандартный c ##).

Если вы настолько далеко зашли в угол, что вам нужно посчитать байты стека, найдите ошибку на своем пути ранее.Даже Фрэнк и Фюртер поняли, что устранение симптомов отличается от причины.

...