проблема инициализации большого двойного массива - PullRequest
2 голосов
/ 26 августа 2010

Глупый вопрос от нового программиста на C ... Я получаю ошибку сегментации в следующем коде:

#include <stdio.h>
int main(void)
{
double YRaw[4000000]={0}; 
return 0;
}

Используя GDB, я получаю следующий комментарий:

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00007fff5dd7b148
0x0000000100000f24 in main () at talk2me.c:18
18 double YRaw[4000000]={0}; // set YRaw[memdepth] so index is 0 to memdepth-1

Все работает, если я уменьшу размер массива YRaw в 10 раз. У меня в системе 6 ГБ ОЗУ, так почему я получаю ошибку?Спасибо, Гкк

Ответы [ 2 ]

5 голосов
/ 26 августа 2010

4000000 * sizeof(double) может быть порядка 32 МБ. Это слишком много для стека, поэтому вы получаете исключение. (Короче говоря, стек переполнен.)

Либо используйте malloc() для выделения его из кучи, либо сделайте его static, или сделайте его глобальным.

Как правило, автоматическое распределение должно использоваться только для объектов малого и среднего размера. Порог трудно охарактеризовать, но 32 МБ в большинстве случаев намного выше его.

Обновление:

В процессе есть несколько областей памяти. (Обратите внимание, что я собираюсь упростить это для ясности, если вы хотите узнать подробности, прочитайте документацию по ld и найдите файл управления компоновщиком, фактически используемый для размещения памяти исполняемых файлов вашей платформы.)

Во-первых, существует сегмент text (иногда называемый code) . Он содержит фактический код, который выполняется, и обычно любые постоянные данные. Как правило, текстовый сегмент защищен от случайных изменений, и в некоторых системах физическая память фактически может быть разделена между процессами, которые выполняют одну и ту же программу или используют одну и ту же общую библиотеку.

Далее, есть сегменты data и bss . Вместе эти сегменты содержат все статически распределенные переменные. Сегмент данных содержит инициализированные переменные и bss неинициализированные переменные. Они различны, потому что неинициализированные переменные известны в исполняемом файле только по их индивидуальным адресам и общему размеру. Эта информация используется для запроса пустых страниц в операционной системе, что является одним из объяснений того, почему неинициализированные глобальные переменные имеют значение 0.

Тогда есть stack и heap. Оба эти сегмента создаются во время выполнения из памяти, выделенной процессу при его загрузке, и часто расширяются во время выполнения. Стек логически хранит записи о вложенности вызовов, параметрах и локальных переменных, хотя детали могут удивительно отличаться на разных платформах. Куча - это пул памяти, управляемый malloc() и его друзьями (и обычно operator new() в C ++). На многих платформах карта памяти процесса организована так, что стек и куча не взаимодействуют, но это может означать, что стек имеет верхнюю границу общего размера, тогда как куча обычно ограничена системой виртуальной памяти.

Имея это в качестве фона, давайте выясним, где хранится каждое объявление.

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

Переменные, объявленные static, распределяются так же, как глобальная переменная, но без записи в таблице открытых символов. Это означает, что неинициализированный буфер static находится в сегменте bss, а инициализированный static - в сегменте данных, но его имя известно только для области, в которой можно увидеть его определение.

Распределения из кучи не гарантированно будут успешными во время выполнения. Существует верхняя граница общего размера процесса, поддерживаемая системной политикой и аппаратной архитектурой. Например, в типичной 32-разрядной системе архитектура не может разрешать более 4 ГБ адресов для какого-либо одного процесса.

Вот несколько конкретных примеров:

int foo(void)
{
    double YRaw[4000000]={0}; 
    // ... do something with huge buffer
}

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

static double YRaw2[4000000]={0}; 
int foo(void)
{
    static double YRaw3[4000000]={0}; 
    // ... do something with huge buffer
}

Здесь YRaw2 и YRaw3 инициализируются, оба попадают в сегмент данных, и на многих платформах фактический исполняемый файл будет содержать 4 миллиона значений 0.0, которые вы указали в качестве их начальных значений.Единственная разница между этими двумя буферами - это область видимости.YRaw2 может использоваться любой функцией в том же модуле, в то время как YRaw3 видна только внутри функции.

static double YRaw4[4000000]; 
int foo(void)
{
    static double YRaw5[4000000]; 
    // ... do something with huge buffer
}

Здесь YRaw4 и YRaw5 оба заканчиваются в сегменте bssи обычно не увеличивает сам исполняемый файл.Опять же, буферы отличаются только областью их имен.Они будут неявно инициализированы тем же значением 0, которое было указано для YRaw2 и YRaw3 при запуске программы.

double YRaw6[4000000]; 
int foo(void)
{
    // ... do something with huge buffer
}

Здесь YRaw6 аналогично YRaw4 выше, за исключением того, чтоимя имеет глобальную область видимости, и буфер может использоваться совместно с другими модулями, а также с каждой функцией в этом модуле.Он хранится в сегменте bss, поэтому, как и YRaw4, он не влияет на размер файла.

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

int foo(void)
{
    static double *YRaw7 = NULL;

    if (!YRaw7) {
        // allocate the buffer on the first use
        YRaw7 = calloc(4000000, sizeof(double));
    }
    // ... do something with huge buffer
}

Здесь YRaw7 хранится в куче, выделенной при первом использованиии никогда не освобождается, пока процесс не завершится.На большинстве «разумных» платформ этот шаблон использования является разумным и допустимым.

int foo(void)
{
    double *YRaw8 = calloc(4000000, sizeof(double));
    assert(YRaw8 != NULL);
    // do something with huge buffer
    // ...
    // but be careful that all code paths that return also
    // free the buffer if it was allocated.
    free(YRaw8);
}

Здесь YRaw8 имеет то же время жизни, что и автоматическая переменная, как вы предполагали в исходном примере, но физически сохраняетсяв кучу.Вероятно, разумно убедиться, что выделение памяти прошло успешно, как я сделал с вызовом assert(), но, возможно, нет лучшего ответа на нехватку памяти, чем допустить сбой утверждения.

Еще одна тонкость: я использовал calloc() для размещения буферов.Это имеет приятное свойство: память гарантированно инициализируется всеми нулевыми битами, если выделение выполнено успешно.Однако, это имеет побочный эффект, который (обычно) приходится записывать в каждый байт распределения, чтобы иметь такой эффект.Это означает, что все эти страницы виртуальной памяти не только выделяются для процесса, но каждая страница должна быть выгружена и каждый байт записан.Использование malloc() вместо этого обычно будет работать лучше, но за счет того, что память не гарантируется, что она будет очищена.

Наконец, очевидный вопрос: «Где я должен выделить этот буфер в любом случае?»

Я не могу дать вам жесткое и быстрое правило, кроме того, что большие выделения никогда не принадлежат стеку.Если буфер должен существовать в течение времени жизни процесса, то неинициализированный static (в области модуля или функции) обычно является правильным ответом.

Если для буфера требуется другое время жизни, чтобы иметьразмер известен только во время выполнения или для того, чтобы жить и умирать в ответ на внешние события во время выполнения, тогда он должен быть выделен в куче с malloc() и друзьями и выпущен в конечном итоге с free() или, возможно, при завершении процесса.

3 голосов
/ 26 августа 2010

Вы пытались объявить весь массив в стеке.Даже если у вас есть терабайт оперативной памяти, только небольшая фиксированная часть будет выделена для стекового пространства.Большие объемы данных должны быть выделены в куче, используя malloc:

#include <stdio.h>
int main(void)
{
  double* YRaw = malloc(4000000 * sizeof(double));
  memset(YRaw, 0, 4000000 * sizeof(double));

  /* ... use it ... */

  free(YRaw); /* Give the memory back to the system when you're done */

  return 0;
}

См. Также: «Что и где находится стек и куча?»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...