LINUX.ORG.RU

C++: Выделение массива памяти 1Гб в куче или в стеке?

 


3

8

Вот когда то давным давно, было не правильным выделять много памяти в стеке. Сначала даже не было динамических массивов.

Потом они появились, появились 64 битные системы и оперативка гигами, и виртуальные страницы памяти.

Т.е. по сути память сейчас можно выделять в стеке в равной степени как и в куче.

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

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

10Мб..100Мб..1Гб..10Гб?

Операционка не подразумевается какая-то конкретная, все современные десктоповые вроде так умеют.

Т.е. по сути память сейчас можно выделять в стеке в равной степени как и в куче.

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

intelfx ★★★★★
()
Ответ на: комментарий от intelfx

Оно не ругалось, пока я не полез в эту память:

    qDebug() << "start";

    uint c = 1000*1000*1000;
    char dummy[c];
    for (uint ii = 0; ii < c; ++ii)
        dummy[ii] = char(ii);

    qDebug() << "check";

    for (uint ii = 0; ii < c; ++ii)
        assert( dummy[ii] == char(ii) );

    qDebug() << "ready";

Максимум проходит на 1 мегабайте.

victor79
() автор топика
Ответ на: комментарий от victor79

Нет. Это опция запуска. ulimit, setrlimit, …

При компиляции можно делать так:

    // Call function f with a 256MB stack.
    static int bigstack(void *(*f)(void *), void* userdata) {

      pthread_t thread;
      pthread_attr_t attr;

      // allocate a 256MB region for the stack.
      size_t stacksize = 256*1024*1024;
      pthread_attr_init(&attr);
      pthread_attr_setstacksize(&attr, stacksize);

      int rc = pthread_create(&thread, &attr, f, userdata);
      if (rc){
        printf("ERROR: return code from pthread_create() is %d\n", rc);
        return 0;
      }
      pthread_join(thread, NULL);
      return 1;

    }
monk ★★★★★
()
Ответ на: комментарий от monk

Спасибо, будут использовать для критических обработок.

victor79
() автор топика

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

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

просто, раз по умолчанию у любой программы есть стек (так как сами процы заточены под его использование), то в некоторых случаях гораздо «дешевле» и быстрее временно «выделить» под переменную некоторый небольшой объём на стеке, чем в куче, что современные компиляторы и делают. то есть это такой, своего рода, очень удобный хак.

anonymous
()
Ответ на: комментарий от anonymous

очень удобный хак

Видимо меня опять назовут еретиком и заклеймят, но таки имею желание вставить, что есть такой замечательный .data, который ниоткуда выделять не надо, регистрами ёрзать не заставляет. Он тупо есть и всегда доступен. Всегда думал, что это максимально удобный хак - выделить в .data некоторое количество байт и пользовать их по своему усмотрению в зависимости от ситуации. Такой простой и понятный «карман», который всегда под рукой и там лежит то ключ, то отвертка, то сигареты. И я помню, что именно сейчас там.
Ещё и компилируется хоть в «камаз» по размеру (чего делать не надо, но технически - можно).

Можете пояснить, если не сложно, почему это плохо?

Toxo2 ★★★
()

В стеке только самую мелочь. Все большие куски только в куче.

anonymous
()
Ответ на: комментарий от Toxo2

зависит от того как использовать твое приложение с карманами. Исходя из общего случая, это не сильно хорошо, но не смертельно.

anonymous
()

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

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

Сначала даже не было динамических массивов

Если ты про alloca и выделние массивов foo[не-константа] то их и сейчас нет - это кривые непереносимые костыли чреватые падениями и проблемами с безопасностью.

slovazap ★★★★★
()

По умолчанию, 64-битным программах доступен 1 мегабайт стековой памяти (по крайней мере в проектах Visual C++), как и 32-битным. Что кстати не очень хорошо и иногда являлось сюрпризом для тех, кто портировал свой код с 32-битной на 64-битную систему. 64-битная программа потребуется больше стековой памяти, и эта память неожиданно может кончиться, хотя 32-битная версия до этого стабильно работала годами. Подробнее это я рассматривал в заметке 2010 года «Причины, по которым 64-битные программы требуют больше стековой памяти» - https://www.viva64.com/ru/b/0069/

Размер стека можно увеличить. Однако, выделять на стеке 1 Гб, это явно что-то не то. :)

Andrey_Karpov_2020
()
Ответ на: комментарий от intelfx

Удивительно, правда?

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

X512 ★★★★★
()

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

и сейчас не будет правильно

Harald ★★★★★
()
Ответ на: комментарий от Andrey_Karpov_2020

По умолчанию, 64-битным программах доступен 1 мегабайт стековой памяти

Привет, виндузятник! Видимо, ты случайно забрёл на linux.org.ru. Но не беда. Спешу тебе сообщить, что на LOR по умолчанию всё-таки подразумевается линукс. Ну так вот, в настольных дистрибутивах линукса стек обычно 8 мегабайт. Дефолтный размер в 1 мегабайт это специфика Windows.

i-rinat ★★★★★
()
Ответ на: комментарий от X512

Программа должна падать при выделении стековой памяти.

От C/C++ обычно не ожидаются дополнительные проверки, потому что если они программисту не нужны, это впустую потраченные ресурсы.

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

Вот если цена у такой проверки нулевая, тогда было бы неплохо её производить. Но как понимаешь, цена нулевой быть не может. К тому же и смысла у этой проверки нет. При попытке доступа к памяти за пределами выделенных адресов программа всё равно упадёт.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

При попытке доступа к памяти за пределами выделенных адресов программа всё равно упадёт.

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

X512 ★★★★★
()
Ответ на: комментарий от Toxo2

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

почему это плохо?

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

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

anonymous
()
Ответ на: комментарий от anonymous

но обычно в .data пишут какие-то начальные значения переменных и поэтому обычно секция сохраняется в бинаре целиком, то есть, размер секции напрямую отражается на размере бинаря.

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

X512 ★★★★★
()
Ответ на: комментарий от X512

кажется это напрямую влияет сколько памяти будет потрачено компилятором при компиляции. то есть если написать где-инбудь static int[1024*1024*1024] то компиляция будет дофига прожорливой, но это не точно, не проверял.

zerhud
()
Ответ на: комментарий от X512

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

zerhud
()
Ответ на: комментарий от zerhud

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

Легко:

void Do()
{
	int buf[1000000];
	buf[0] = 1;
}

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

Напомню что стек растёт в сторону уменьшения адресов и заканчивается защитной страницей для отлова переполнения стека.

X512 ★★★★★
()
Последнее исправление: X512 (всего исправлений: 1)
Ответ на: комментарий от X512

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

i-rinat ★★★★★
()
Ответ на: комментарий от intelfx

лимит поднимаешь и норм выделяется

anonymous
()
Ответ на: комментарий от i-rinat

То есть даже если компилятор вставит код проверки, всё равно можно вылезти за границы.

Не вызывая undefined behavior нельзя. А без проверок любое выделение буфера на стеке больше защитных страниц получается undefined behavior что нарушает спецификацию языка. В GCC есть опция -fstack-check для проверки переполнения стека: с проверкой, без проверки.

X512 ★★★★★
()

Плохая идея.

Эти ваши «манипуляции» со стеком. На самом деле, как было плохой идеей лазить в стек таким образом, так и осталось. Стек в программе вообще-то не для того предназначен чтобы уходить от проблем с дефрагментацией. Если угодно, то это своего рода неизбежное зло и хранилище некой специализированной информации – передачи и возврата значений при вызовах, временного хранения данных в случае, если нет нужды в более-менее длительном их хранении (см. alloca()), общей организации вызовов, прерываний и возвратов.

Если говорить об alloca() и динамических массивах, то проблема ровно одна и та же – после возврата этих областей памяти существовать не будет. Вернуть их в точку вызова таким образом, не получится. Так что лучше бы создавать/удалять массивы при помощи стандартных malloc/realloc/free в куче. Единственное что хорошо с alloca() и динамическими массивами, так это то, что не запутаетесь и память за собой чистить самостоятельно ненужно. Вроде, утешение но какое-то слабенькое, если честно. То, чо alloca() не внесли в стандарт, это меньше всего всех волнует. Она реализована практически во всех современных системах, да и ну её эту кроссплатфрменность к козе в трещину, если честно. Под Linux работает и то что надо.

Выделить память в стеке? Да сколько осилите.

$ ulimit -all
...
stack size                  (kbytes, -s) 8192
...

# ulimit -s 64000
# ulimit -all
...
stack size                  (kbytes, -s) 64000
...

Ну либо своим кодом (от рута!):

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
#include <stdio.h>
#include <sys/resource.h>

int main (int argc, char **argv) {
	struct rlimit rl;
	int rc;

	rc = getrlimit(RLIMIT_STACK, &rl);
	if(rc == 0) {
		printf("lim: %d / %d\n", (int)rl.rlim_cur,
					 (int)rl.rlim_max);
	rl.rlim_cur = 64000000;
	rl.rlim_max = 64000000;
        rc = setrlimit(RLIMIT_STACK, &rl);
	if(rc != 0) {
		fprintf(stderr, "setrlimit failed = %d\n", rc);
		}
	}
	rc = getrlimit(RLIMIT_STACK, &rl);
	if(rc == 0) {
		printf("lim again: %d / %d\n", (int)rl.rlim_cur,
					       (int)rl.rlim_max);
	}
    return 0;
}

Так-то ничего хитрого в принципе, но в общем и целом так лучше не делать чтобы уйти от фрагментации. Вдобавок, гимор может начатся в многопоточном приложении – одному потоку подай стек такой, другому – сякой. В общем, придётся делать что-то типа:

      /* Запрашиваем те же 64MB что и в примерах выше. */
      size_t new_stacksize = 64*1024*1024;
      pthread_attr_init(&attr);
      pthread_attr_setstacksize(&attr, new_stacksize);

      int rc = pthread_create(&thread, &attr, f, userdata);

Т.е., гимор тут преодолим, но возможен. Смысл на него нарываться?

Для ухода от фрагментации, лучше бы наверное что-то типа своего «менеджера памяти» уровня приложения написать. Т.е., к примеру, предполагаем что нам понадобится гиг памяти, просто запрашиваем его при старте у системы и работаем далее с этим куском памяти, не вылезая за его пределы. Т.е., раз захватили память через malloc() и дальше просто работаем в его пределах. Разовый malloc() это не так чтобы и дорого, чем делать malloc()/free(), да ещё и реальное освобождение памяти под вопросом.

Либо используем что-то типа jemalloc/tcmalloc, ибо для того и писались (одна из задач, решаемых этими библиотеками как раз преодоление фрагментации памяти). tcmalloc весьма хороша для многопоточных приложений, правда, по дефолту сразу около 6М отжирает под свой пул памяти. jemalloc это что-то типа malloc() общего назначения. Я их обе использую.

Ну вот, как-то вот так.

Moisha_Liberman ★★
()
Ответ на: комментарий от X512

А без проверок любое выделение буфера на стеке больше защитных страниц получается undefined behavior

Ты осознаёшь, что мешаешь в одну кучу UB, который является понятием из стандарта и описывает некий абстрактный компилятор, и канарейки, которые генерируются деталями реализации конкретных компиляторов? Как это всё у тебя в голове стыкуется?

Реализация доступа к массивам в GCC не включает в себя проверку границ. Это сложение адреса и смещения. В процессоре (подразумеваем x86) тоже нет обязательной проверки границ массива. Так что доступ вполне получить можно. Что ты вообще пытаешься доказать?

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Что ты вообще пытаешься доказать?

Что полностью корректная программа без обращений за границы массива может портить память. Это нарушение стандарта языка, по стандарту тут нет undefined behavior. В стандарте не написано что нельзя выделять на стеке память больше 4096 байт. MSVC кстати по умолчанию всё правильно делает.

X512 ★★★★★
()
Последнее исправление: X512 (всего исправлений: 2)
Ответ на: комментарий от Andrey_Karpov_2020

Размер стека можно увеличить. Однако, выделять на стеке 1 Гб, это явно что-то не то. :)

Некоторых дефолтные размеры не устраивают, и нужно увеличивать размер стека. Особенно подгорает у пользователей современного C++, ведь API std::thread такой возможности не предоставляет.

seiken ★★★★★
()
Ответ на: Плохая идея. от Moisha_Liberman

Ну либо своим кодом (от рута!):

А без рута нельзя задать размер стека для начального потока? В Windows можно, в заголовке исполняемого файла PE есть размер стека.

X512 ★★★★★
()
Ответ на: комментарий от X512

При попытке доступа к памяти за пределами выделенных адресов программа всё равно упадёт.

Нет

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

Легко: int buf[1000000]; buf[0] = 1;

ulimit -s в студию!

$ ulimit -s
8192

$ cat stack.c
int main() {
    char buf[8*1024*1024];
    buf[0] = 1;
}

$ gcc stack.c && ./a.out
segmentation fault (core dumped)  ./a.out
anonymous
()

Выделение массива памяти 1Гб в куче или в стеке?

Асинхронный код на раст не требует выделения памяти.

cloun1901
()

в C++ - выделять память ручками - это вообще не правильно. Для этого Страус придумал RAII.

никто не выделяет, ни в стэке, ни в куче.

то о чём вы говорите - это допотопный си, на котором пишет Торвальдс..

ХАЛИВАР!

anonymous
()
Ответ на: комментарий от X512

Это нарушение стандарта языка, по стандарту тут нет undefined behavior. В стандарте не написано что нельзя выделять на стеке память больше 4096 байт.

Стандарт допускает существование implementation-defined ограничений, в которые нужно уложиться чтобы программа работала.

[implimits]/1:

Because computers are finite, C++ implementations are inevitably limited in the size of the programs they can successfully process. Every implementation shall document those limitations where known. This documentation may cite fixed limits where they exist, say how to compute variable limits as a function of available resources, or say that fixed limits do not exist or are unknown.

anonymous
()
Ответ на: комментарий от X512

Это нарушение стандарта языка, по стандарту тут нет undefined behavior. В стандарте не написано что нельзя выделять на стеке память больше 4096 байт.

Ну или вот более лутший пункт, [intro.compliance.general]/(2.1):

If a program contains no violations of the rules in [lex] through [thread] and [depr], a conforming implementation shall, within its resource limits as described in [implimits], accept and correctly execute that program.

Стандарт не обязывает исполнять программу с любыми размерами объектов, используемой памяти и т.д. Тут нет нарушения стандарта.

anonymous
()
Ответ на: комментарий от i-rinat

Чувак, что ты тут вообще делаешь?

anonymous
()

Конечно стек можно сделать большим с помощью ulimit, но зачем? Я понимаю аспект удобства автоматической «сборки мусора» на стеке по сравнению с new/delete; но с использованием умных указателей в C++ эта проблема исчезает. В общем пока непонятно, нафига козе баян

Crocodoom ★★★★★
()
Ответ на: комментарий от Toxo2

Видимо меня опять назовут еретиком и заклеймят, но таки имею желание вставить, что есть такой замечательный .data, который ниоткуда выделять не надо, регистрами ёрзать не заставляет. Он тупо есть и всегда доступен. Всегда думал, что это максимально удобный хак - выделить в .data некоторое количество байт и пользовать их по своему усмотрению в зависимости от ситуации. Такой простой и понятный «карман», который всегда под рукой и там лежит то ключ, то отвертка, то сигареты. И я помню, что именно сейчас там.

Для подобных прихотей уже давно сделали кастомные аллокаторы в C++, хватит учить детей плохому =)

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

кастомные аллокаторы в C++

Никакой аллокатор не берёт память из ниоткуда, тебе всё равно от системы её получать придётся. Вот он один из путей получения и описал. Аллокаторы и C++ здесь ни при чём.

cloun1901
()
Ответ на: комментарий от X512

Если без root, то...

… надо выставить CAP_SYS_RESOURCE capability. Тогда в принципе, возможно и без root. Но, если честно, то программы, имеющие возможность переопределять системные ресурсы лучше не бросать совсем уж «без присмотра».

В Windows можно, в заголовке исполняемого файла PE есть размер стека.

В Linux смысла нет особого размер стека в elf прописывать. Хотя, в принципе, линкер посредством ld-скриптов может сгенерировать весьма изысканные и причудливые elf файлы и системный загрузчик их загрузит в память и сегменты даже раскидает по местам, но в Linux для основного потока сохраняется ещё море всякой всячины – переменные окружения, аргументы командной строки (насколько я помню, хотя, и могу ошибаться), т.е. достаточно принять некое разумное общесистемное решение – типа 8К или 10К и дальше его использовать. Системный загрузчик тогда, при старте программы на исполнение, при размещении сегментов в памяти, на лету создаст для данного процесса сегмент стека так, как это принято в данной системе (где-то 8К, где-то 10К). Он же задаст адреса, откуда считать смещение, т.к. в системах без ASLR это делается одним образом, в системах с ASLR – другим, дальше просто пнёт проц на начало сегмента кода, да и всё.

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

Вот что может быть в заголовке elf-файла, так это указание на то, является ли сегмент стека исполняемым. Это тоже определяется конкретной системой (версией Linux и её конфигурацией). Например, у меня в генточке везде, что на десктопе, что на ARM, что на серверах стек неисполняемый по-дефолту.

P.S. Если глянуть любой elf в x86_64 через readelf -a prog_name, то там будет сегмент GNU_STACK, но у него размер будет нулевой. Он будет изменён при старте приложения согласно того, как в ulimit указан размер стека.

Moisha_Liberman ★★
()
Последнее исправление: Moisha_Liberman (всего исправлений: 2)
Ответ на: комментарий от anonymous

Конечно. Нафиг эти кучи, стэк. Профи ходят в ммапнутый файл через boost::interprocess::offset_ptr!

fluorite ★★★★★
()
Ответ на: комментарий от cloun1901

Ага и работает он в сферическом вакууме.

anonymous
()
Ответ на: комментарий от LamerOk

ничего вечного не существует

anonymous
()
Ответ на: комментарий от pavlick

Вообще-то, сильно отличается.

А память из .data сильно отличается от памяти из хипа?

.data это статические переменные, которые существуют на момент старта программы и сформированы в compile time. Куча это пул памяти, который определяется в run-time.

Moisha_Liberman ★★
()
Ответ на: Если без root, то... от Moisha_Liberman

Но, если честно, то программы, имеющие возможность переопределять системные ресурсы лучше не бросать совсем уж «без присмотра».

Стек — это не системный ресурс, а локальный ресурс процесса. Программа всегда может сама выделить стек через malloc или mmap и установить SP на него и система ничего с этим сделать не сможет.

X512 ★★★★★
()
Последнее исправление: X512 (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.