Распределение памяти для глобальных и локальных переменных

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

Случай 1:
Я объявила глобальный целочисленный массив размером 63500000, а используемая память - 256 МБ
Идеальная ссылка

include <stdio.h>
int a[63500000];
int main()
{
    printf ("This code requires about 250 MB memory\n");
    return 0;
}

Случай 2:
Я объявил локальный целочисленный массив одинакового размера в main(), а используемая память - 1,6 МБ
Идеальная ссылка

#include <stdio.h>
int main()
{
    int a[63500000]= {1,5,0};
    printf ("This code requires only 1.6 MB \n");
    //printf ("%d\n", a[0]);
    return 0;
}

Случай 3:
Я объявил локальный целочисленный массив одинакового размера в другой функции, а используемая память - 1,6 МБ
Идеальная ссылка

#include <stdio.h>
void f()
{
    int a[63500000];
}

int main()
{
    f();
    return 0;
}

Пожалуйста, объясните, почему разница в используемой памяти или неправильная концепция распределения памяти?

Ответ 1

Прежде всего: компилятор ideone - это GCC.

Итак, что делает GCC при компиляции этого?:

void foo ()
{
  int a[63500000];
}

gcc -S -O2 foo.c генерирует:

foo:
    pushl   %ebp
    movl    %esp, %ebp
    popl    %ebp
    ret

то есть. ничего не выделяется в стеке.

Массив просто оптимизирован GCC, потому что он никогда не используется.

GCC не будет делать это с глобальным, потому что возможно, что глобальный используется в другой единице компиляции, и поэтому он не уверен, что он никогда не используется. Кроме того: глобальный не находится в стеке (поскольку он является глобальным).

Теперь давайте посмотрим, что произойдет, когда вы на самом деле используете локальный массив:

int bar (int a, int b, int c)
{
  int f[63500000];
  f[a] = 9;
  f[b] = 7;
  return f[c];
}

Все очень разные:

bar:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $254000000, %esp
    movl    8(%ebp), %eax
    movl    $9, -254000000(%ebp,%eax,4)
    movl    12(%ebp), %eax
    movl    $7, -254000000(%ebp,%eax,4)
    movl    16(%ebp), %eax
    movl    -254000000(%ebp,%eax,4), %eax
    leave
    ret

Эта строка: subl $254000000, %esp соответствует размеру массива. то есть память выделяется в стеке.

Теперь, если я попытался использовать функцию bar в программе:

int bar (int a, int b, int c)
{
  int f[63500000];
  f[a] = 9;
  f[b] = 7;
  return f[c];
}

int main (void)
{
  return bar (0, 0, 0);
}

Мы уже видели, что функция bar выделяет 250 мегабайт в стеке. В моей установке GNU/Linux по умолчанию размер стека ограничен 8 МБ. Поэтому, когда программа запускается, она вызывает "Ошибка сегментации". Я могу увеличить его, если захочу, выполнив в оболочке следующее:

ulimit -s 1000000 #i.e. allow stack size to grow close to 1GB

Затем я могу запустить программу, и она действительно запустится.

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

Ответ 2

Случаи 2, 3

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

Случай 1

Переменные, определенные в глобальной области, выделяются в сегменте данных (или, как правило, в пространстве памяти, запрошенном из операционной системы), который существует для времени жизни процесса.

Дополнительно

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

Обратите внимание, что современная ОС может обеспечить адресное пространство, запрошенное программой, но физически не вернуть это адресное пространство с ОЗУ, пока физический доступ к памяти (или части памяти, часто называемой страницей) физически недоступен.

Ответ 3

case 2 и case 3 приведет к переполнению стека, поскольку вы запрашиваете 64 МБ стековой памяти, в которой ваш стек равен обычно 8 МБ в Linux. это приведет к случайным плохим вещам и/или основным дампам и сбоям.

этот ответ в значительной степени объясняет различные разделы адресного пространства процесса (.text,.bss,.data) и способы выполнения различных распределений переменных.