LINUX.ORG.RU

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

 


0

6

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

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

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

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

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

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

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

Ответ на: комментарий от Harald

скорее потока, а не процесса

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

X512 ()
Ответ на: Вообще-то, сильно отличается. от Moisha_Liberman

То и другое будет спроецированы на вирт адреса, под которые будут выделяться страницы. Если нужен «карман», то достаточно сделать new char[very_big], и размещать там объекты через placement new (также можно юзать и память в .data). Не, как вариант годится, конечно, но человек расписал так, что rdata это прям какой-то хитрый хак, дающий много плюшек, а я их не вижу, в целом одно и то же.

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

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

#include <stdio.h>
int main() {
    char oneGB[1024*1024*1024];
    for (int i=0; i<sizeof(oneGB); i++)
        oneGB[i] = "изейше"[i%13];
    puts(oneGB);
}

$ gcc -fsplit-stack 1GB_on_stack.c && ./a.out && echo $?
изейше
0

но официально только для x86.

anonymous ()

Эпилог

В форуме говорил ХАМАМ, что они ХАМЫ, Россию не позволял зарубежным товарищам хаить, критиковал глупые треды, …

Оно мне нужно?  
Форум МЕШАЛ разработке, потому что на него отвлекался ...

Владимир 123

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

Не совсем так.

Стек — это не системный ресурс, а локальный ресурс процесса.

Если смотреть со стороны процесса, то да, владельцем стека как ресурса, является процесс. Он может его увеличивать. Нет, уменьшать процесс ни стек, ни кучу не может. Если выделение памяти произошло, то либо куча, либо стек выросли, но даже если Вы освободите память в случае с кучей через free(), или она сама освободится как в случае со стеком, уменьшения отведённого программе адресного пространства не произойдёт. Так сделано во предположении что если программе раз понадобилась область памяти (не важно – в куче или в стеке), то чтоб системе потом не париться, в случае, если память освободилась, но потом понадобилась опять, у программы уже будет выделен кусок нужного размера.

Если смотреть со стороны системы, то нет. Именно на уровне системы (ulimit) задаются дефолтные и общесистемные настройки, которые потом по мере необходимости корректируются процессом, если у него есть на это права. Посмотреть и откорректировать свои дефолтные настройки Вы можете в файле /etc/security/limits.conf. Т.е., именно система определяет как будет жить ваш процесс на старте. Но именно процесс потом корректирует своё поведение, если может.

Программа всегда может сама выделить стек через malloc

Нет. Система через malloc() новый размер стека установить не может. Он устанавливается либо напрямую, через setrlimit(), либо косвенно, в процессе работы программы – через alloca(), либо при использовании динамических массивов. В случае через setrlimit() я сразу говорю какой стек мне по идее нужен, а система уже пытается выделить мне нужное пространство (и ещё о своих задачах не забывает, т.е., на деле будет стек для приложения, нужный приложению для работы + стек приложения нужный мне лично). В случае с alloca() и динамическими массивами система по запросу будет выделять нужные участки стека, точно так же увеличивая размер стека приложения, но происходит это в менее явном для меня виде. Т.е., в первом случае я хотя бы прикинуть могу какой размер стека у меня в приложении будет, во втором-третьем случаях меня это вообще не парит.

или mmap и

Нет. Области памяти, закреплённые за виртуальным адресным пространством процесса, выделенные через mmap() никакого отношения не имеют ни к стеку, ни к куче. Это отдельные регионы памяти, которые ни в стек, ни в кучу не входят. Это data segment приложения. С ним можно делать что угодно.

Если в куче работает malloc(), точнее, внутри там работают sbrk()/brk() (хотя, если уж быть совсем точным, то всё сводится к одному сисколлу brk()), то работа с mmap() строится на том, что mmap() создаёт некий разделяемый сегмент, который доступен процессу, который его создал. Более того, я сразу уточню что если нужны небольшие объёмы памяти, то они будут выделяться через malloc()/brk(), а вот значительные объёмы будут выделяться через mmap() скорее всего. Так сделано чтобы не отдавать процессу запрашиваемые им пару гигабайт «навечно», пока он не закончит свою работу (выше я написал что ни куча ни стек не уменьшаются, а data segment может и расти и убывать).

установить SP на него и система ничего с этим сделать не сможет.

Стек адресуется как адрес_стека+смещение через регистры SS:ESP для х86_64. Подходя формально, Вам конечно запретить сделать что-то типа mov esp, new_value нельзя. Но вот компилятор скорее всего может неоценить такого подхода и, в случае включённых опций компиляции типа -fstack-protector-strong{all||explicit} не одобрить Ваших действий.

Система тут и делать ничего не должна, если по большому счёту. Должны же «злобные хаккИры» как-то свои атаки типа stack smashing проводить… =)))

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

Ну так-то в принципе, да.

То и другое будет спроецированы на вирт адреса, под которые будут выделяться страницы. Если нужен «карман», то достаточно сделать new char[very_big], и размещать там объекты через placement new (также можно юзать и память в .data). Не, как вариант годится, конечно, но человек расписал так, что rdata это прям какой-то хитрый хак, дающий много плюшек, а я их не вижу, в целом одно и то же.

Динамические массивы вполне себе возможны, это один из вполне валидных вариантов. Только тут надо бы посмотреть – насколько это very_big и не лучше ли использовать mmap(). Почему так, я чуть выше написал.

Moisha_Liberman ()
Ответ на: Не совсем так. от Moisha_Liberman

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

Вы говорите про какие-то древние Юниксы в которых куча работает через sbrk. В современных системах куча работает через mmap и адресное пространство освобождается после вызова free, правда не сразу. Есть полно альтернативных библиотек реализации кучи, использовать стандартную кучу никто не заставляет. Языки со сборкой мусора почти всегда делают свою кучу на основе mmap.

Подходя формально, Вам конечно запретить сделать что-то типа mov esp, new_value нельзя.

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

Но вот компилятор скорее всего может неоценить такого подхода и, в случае включённых опций компиляции типа -fstack-protector-strong{all||explicit} не одобрить Ваших действий.

Все эти проблемы решаются. Есть библиотеки короутин которые выделяют стековую память через malloc/mmap и напрямую задают регистр SP. И всё это без проблем работает. Защитную страницу тоже можно сделать через mmap/mprotect.

И не забывайте что языки программирования не ограничиваются C/C++.

X512 ()
Последнее исправление: X512 (всего исправлений: 2)

Эмм ирония в том, что выделение большого стека будет означать всё равно 1) долгое выделение места для процесса во время запуска программы, равное по затратам на создание процесса и выделение в куче 2) жёсткий минимум необходимой памяти 3) Слабую надёжность программы. Поэтому в такой постановке вопроса вы не получите никакой экономии и разумным решением будет выделение памяти на куче и последующие игры уже с ней.

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

Почему?

Поток не может владеть ресурсами, только процесс.

В Linux поток сам по себе является *легковесным процессом. Т.е., у потока могут быть совершенно свои ресурсы, которые на самом верхнем уровне принадлежат родительскому (основному) процессу.

Напрмер, выше и уважаемый @monk и я написали как выставляются атрибуты для потока. В случае одного потока определение атрибута и создание потока будут выглядеть так:

      /* Запрашиваем те же 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);

Но мне ни кто не запрещает создать ещё несколько потоков со своими стеками, другого размера. И принадлежать эти ресурсы будут каждый своему потоку.

Адресное пространство едино для всех потоков процесса.

И «да» и «нет». Извратиться, конечно, можно. Но смысла осбого нет. Когда система создаёт новый поток, она по сути «копирует» некоторые вещи для каждого из потоков исполнения в начальном состоянии. Дальше уже каждый поток сам их для себя переопределяет и поддерживает. Просто, поток, как и процесс может быть в одном из состоянии – запущен, завершён, заблокирован или готов. Учитывая эти различные состояния как для потоков, так и для процессов, каждый поток должен иметь свой стек. Иначе, например, поток завершился, а как менять состояние общего с другими потоками стека в Вашем примере?

Собственно, что процессы (через fork()), что потоки создаются через один системный вызов clone(), но опции создания там разные. Система же рулит примерно одинаково что потоками, что процессами.

Moisha_Liberman ()

Где то читал про 8 метров стека в Linux, и его действительно можно расширять через ulimits, и он до лимитов вроде как расширяется сам, занимая всё большее число страниц

Однако, не понятно нохуа так делать, это спецолимпиада?

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

Нет.

Вы говорите про какие-то древние Юниксы в которых куча работает через sbrk.

Пример:

#include <stdlib.h>

int main() {
	int *ptr = (int *)malloc(1024 * sizeof(int));
	return 0;
}

Далее очевидно что делаем gcc test.c; strace ./a.out. Смотрим вывод:

strace ./a.out 
execve("./a.out", ["./a.out"], 0x7ffebb592060 /* 53 vars */) = 0
brk(NULL)                               = 0x55d8c24eb000

А вот остальное (загрузка и выгрузка разделяемых библиотек) да, через mmap()/munmap(). Ну просто потому, что размеры библиотек такие, что ну его на фиг их по-другому грузить.

В современных системах куча работает через mmap и адресное пространство освобождается после вызова free, правда не сразу.

Оно будет освобождено только после завершения программы. Либо программа завершится сама и по-хорошему, либо к программе система вышлет ООМ-киллера (если программа вообще «берега попутала» и сожрала недопустимо много памяти на недопустимо долгий срок, это всё настраивается). Альтернатив нет.

И нет. mmap() и malloc()/free() это разные вещи (я об этом и написал) и mmap на расширение кучи ни как не влияет.

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

Да, поэтому я сразу написал TC про jemalloc и tcmalloc.

Языки со сборкой мусора почти всегда делают свою кучу на основе mmap.

А вот это вот сразу так далеко, что отсюда не видно даже. =))) Обычно С-программисты всё-таки, умнее железа и софта и сами знают как грамотно использовать распределение памяти. Вообще, на мой взгляд, все попытки изобрести новые, безопасные языки программирования, да ещё и со сборкой мусора, которая работает сама по себе, вне контроля программиста, это попытка защитить идиотов от самих себя. Нет, не защитите. Любые языки со сборкой мусора это АдЪ, треш, угар и немного, буквально для придания чуточки пикантности, содомiя. В одном флаконе.

Программа может использовать любую память как стек.

Фсмысле??? Нет, не может. Сегмент стека программы это сегмент стека программы. И вот не надо его путать с ADT (abstract data type) под названием stack. Такой абстрактный тип данных Вы можете реализовать в куче или в сегменте стека. Но к stack segment приложения этот ADT не имеет ровным счётом никакого отношения.

Для работы со stack segment есть getrlimit(), setrlimit(), alloca(), либо динамические массивы. Больше никаких средств в Linux не предусмотрено.

Есть библиотеки короутин которые выделяют стековую память через malloc/mmap и напрямую задают регистр SP.

Как корутины (про них ещё Дональд Кнут в незапамятно-лохматом году писал в своей книге «The Art of Computer Programming», если что), так и фидеры и green threads это потоки исполнения в userspace. Т.е., ядро об этих потоках вообще ничего не знает и знать не желает. Рекомендую к прочтению и к ознакомлению парочку из вариантов реализации – GNU Pth и protothreads.

Но всё это не отменяет того факта, что ни зелёные нитки, ни корутины, ни фидеры нельзя и близко сравнивать с native process/pthreads просто потому, что система про эти ухищрения не знает ничего и все эти решения ни как не обрабатываются тем же системным планировщиком. Т.е., если программист сам использует такие решения, то он и сам должен отвечать за работу такого решения внутри своего приложения (потока или процесса, не важно) – отслеживать состояния и переключать контексты например. И да, стек у них тоже один. Но извините это не отдельные потоки. Для системы.

Защитную страницу тоже можно сделать через mmap/mprotect.

Можно. Но не факт то нужно. =)

И не забывайте что языки программирования не ограничиваются C/C++.

Я не думаю что в новых, модных и более безопасных языках чем С, придумали новый API системы. Просто, от программиста этот самый API, лежащий на самом нижнем уровне абстракций, все эти сегменты виртуального адресного пространства процесса, все эти сисколлы, надёжно скрыты за слоями абстракций более высокого уровня. И тут вопрос только – что и от кого мы защищаем. Идиота простите, «программиста» от системы или систему от… программиста? =)))

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

Сразу говорю -- теперь никак. =)))

Как мне это развидеть?

Для минимальной демки вполне пойдёт и splint в vim не орёт. Для эстетов я ещё и дополню тем, что я там ещё и возвращаемое значение не проверяю. Вааще харам, согласен со splint. =)))

Так что, теперь Вам с этим как-то жить… Сорян, так получилось. =)

Moisha_Liberman ()
Ответ на: Нет. от Moisha_Liberman

Фсмысле??? Нет, не может. Сегмент стека программы это сегмент стека программы.

Держите код для создания своего стека, даже ассемблер не понадобился:

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>

void SwitchStack(void *stack, size_t stackSize, void (*Entry)(jmp_buf, void*), void *arg)
{
	jmp_buf oldCtx, ctx;
	if (setjmp(oldCtx) == 0) {
		size_t **fp = NULL;
		size_t *sp = (size_t*)((char*)stack + stackSize);
		printf("stack end: %p\n", sp);
		sp--; *sp = (size_t)arg;
		sp--; *sp = (size_t)oldCtx;
		sp--; *sp = 0; // return address
		memset(ctx, 0, sizeof(ctx));
		ctx[0].regs[3] = (size_t)fp;
		ctx[0].regs[4] = (size_t)sp;
		ctx[0].regs[5] = (size_t)Entry;
		longjmp(ctx, 1);
	}
}

void StackEntry(jmp_buf oldCtx, void *arg)
{
	printf("+StackEntry: %s\n", (char*)arg);
	jmp_buf probeCtx;
	setjmp(probeCtx);
	printf("SP: %p, ", probeCtx[0].regs[4]);
	printf("FP: %p\n", probeCtx[0].regs[3]);
	printf("-StackEntry\n");
	longjmp(oldCtx, 1);
}

int main()
{
	printf("+main\n");
	jmp_buf probeCtx;
	setjmp(probeCtx);
	printf("SP: %p, ", probeCtx[0].regs[4]);
	printf("FP: %p\n", probeCtx[0].regs[3]);
	size_t stackSize = 1024*1024;
	void *stack = malloc(stackSize);
	SwitchStack(stack, stackSize, StackEntry, (void*)"This is a test.");
	free(stack); stack = NULL;
	printf("-main\n");
	return 0;
}

Вывод:

+main
SP: 0x71d54520, FP: 0x71d54588
stack end: 0x18245130
+StackEntry: This is a test.
SP: 0x182450d8, FP: 0x18245120
-StackEntry
-main

И никакого больше системного стека. Код для 32 бит потому что лень возиться с регистрами соглашения вызовов x86-64.

Для системы.

У вас какой-то культ поклонения системе. Система — это просто инструмент. Можно пользоваться частью её функционала, а можно нет.

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

Бггг... =)))

Взоржахом аки конь стоялый! Занесть в летописи! Кто там под лавкою, подвиньтесь, падаю! =)))

И никакого больше системного стека.

Батенька, это Вы в преддверии праздника 23-го февраля так жжоте-то? А не рано ли ещё начинать? =)))

А теперь следите за руками. Два разА повторять трюк не буду. =)))

Вы используете setjmp() и longjmp()? Ну так откройте на них маны, да вкурите истину. =))) Используя setjmp() Вы просто создаёте фрейм программного окружения (туда попадут состояние регистров, в т.ч. и регистра стека, адреса возвратов, локальные переменные каждой запущеной функции, но нет, туда не попадут дескрипторы открытых файлов например).

Т.е., фрейм окружения это некое состояние программы на момент его сохранения через setjmp() в struct jmp_buf. К сегменту стека это ровным счётом никакого отношения не имеет, т.к. сегмент стека не изменяется, а просто сохраняется как есть. Как программа работала, так и работает.

При восстановлении состояния программы через longjmp() просто восстанавливаются значения регистров на некий момент времени, к которому мы хотим вернуться. Сегмент стека опять не меняется, просто восстанавливается значения ss и esp, остальное программа забывает (сколько бы там сохранений ни было, это всем пофиг).

Вот и всё что происходит в Вашем «стеке». Это как было валидным состоянием процесса или потока, так и есть. Никакой фантастики и никаких стеков. Соболезную. =)))

У вас какой-то культ поклонения системе. Система — это просто инструмент. Можно пользоваться частью её функционала, а можно нет.

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

P.S. Соббстна, про то и речь – как ни извращайтесь, а сегмент стека в приложении как жил своей жизнью, так и живёт, если только его специально не озадачивать. Как именно – уже написал, дважды повторять не буду.

Что касается меня лично, то я добавлю что как С-программисту мне и в голову не пришло бы городить огород с таким, как у Вас подходом. =))) Сознавайтесь – на чём сидите, ээээ… я хотел сказать «пишете»? =)))

Moisha_Liberman ()
Последнее исправление: Moisha_Liberman (всего исправлений: 2)
Ответ на: Бггг... =))) от Moisha_Liberman

Вы используете setjmp() и longjmp()? Ну так откройте на них маны, да вкурите истину. =)))

Лучше исходник.

К сегменту стека это ровным счётом никакого отношения не имеет, т.к. сегмент стека не изменяется, а просто сохраняется как есть.

Какая разница что там ядро думает про сегмент стека? Этим сегментом можно просто не пользоваться что и было продемонстрировано. Его даже удалить можно.

X512 ()
Последнее исправление: X512 (всего исправлений: 1)
Ответ на: Бггг... =))) от Moisha_Liberman

Взоржахом аки конь стоялый!

Не хочу портить тебе настроение, но он как бы лабой для студня показывает тебе, как можно управлять адресацией машинных push/pop'ов на адреса из кучи, если ты вдруг этого не осознал (а твоё буйное веселье намекает, что ты таки не).

Программа может использовать любую память как стек.

Фсмысле??? Нет, не может. Сегмент стека программы это сегмент стека программы.

Т.е - у него Q.E.D.

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

а сегмент стека в приложении как жил своей жизнью, так и живёт,

Да и фиг бы с ним.

как С-программисту мне и в голову не пришло бы

Иногда полезно выходить из зоны комофрта и переставать быть «виужал-басик»- / фокспро- / dBase- / php- / java- / dotnet- / lisp- / scala- / clojure- / javascript- / typescript- / rust-программистом.

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

Бггг... =)))

Какая разница что там ядро думает про сегмент стека?

Угу… В Вашем случае да. Просто потому, что Вы даже не поняли что с сегментов стека Вы не работаете. =)))

Этим сегментом можно просто не пользоваться что и было продемонстрировано.

Вы продемонстрировали только то, что просто не понимаете что такое stack segment, извините. Не более. Вы его копируете в setjmp() даже не подозревая о его существовании. И восстанавливаете в longjmp() точно так же не подозревая о его существовании. Про какое-либо изменение сегмента стека тут и говорить не приходится.

ВНИМАНИЕ!

Более того. Я сделал над собою усилие и попробовал скомпилировать Ваш код. Вы знаете, он не работает. Угадаете почему? =))) Таких элементов структур, которыми Вы пытаетесь пользоваться, в Linux нет. Я говорю о первой «реализации» Вашего стека. Зачем Вы сунули сюда нерабочий код, я даже представить не могу.

то само по себе «ай-яй-яй», но пусть это останется на Вашей совести. Я же просто покажу что произойдёт в случае, если Вы возжелаете действительно изменить сегмент стека в аналогичном Вашему приложении (да, оно работает). =)))

Аааа… теперь погнали! Итак, вот исходник:

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

int main(int argc, char **argv) {
	jmp_buf env;
	int rc;

	rc = setjmp(env);
	if(rc) {
		printf("unmodified\n");
	}
	longjmp(env, 1);
	return 0;
}

Так как мне до безобразия лень писать и валидную обработку setjmp()/longjmp() и невадидную (с изменением stack segment), я написал одну, валидную в принципе, но дальше я буду использовать gdb для иллюстрации происходящего. Ну и команды сюда закидывать для вящего понимания происходящего.

Компилимся gcc jmp_test.c -g -o jmp_test. Дальше gdb jmp_test -q.

Теперь в gdb – break main, далее run, далее disassemble. Нам нужно найти и поменять содержимое %rsp для 64 бит (т.е., как и предлагает уважаемый @X512) поменять содержимое регистра стека приложения. Не просто сохранить/восстановить (чем он и занимается в своём коде, т.е., пытается заниматься), а именно поменять. Почему так? Ну потому что иначе подход, показанный (зачем-то) уважаемым @X512, это не стек, а какая-то фигня.

Нам нужен именно %rsp, т.к. это действително указатель на текущее значение регистра стека, в то время как %rbp указывает на весь сегмент стека приложения. В принципе, можно и его, но ближе к приведённому примеру именно %rsp. Т.е., мы понимаем что реальное смещение в stack segment считается как %rbp:%rsp. Ok, дальше поехали.

Ладно, дальше всё так же в gdb. print $rsp. Ага. Мы его нашли. $1 = (void *) 0x7fffffffdc90. Смотрим для гарантии чё там ещё есть в структуре envprint env. Ничего интересного. Несложно заметить что нажимая next мы доберёмся до места, где идёт вывод строки и, прерывая исполнение и запрашивая print $rsp, мы заметим изменения регистра. Нам в принципе, пофиг, но нельзя ли его просто взятьи поменять? Как? Да как угодно. Со слов уважаемого @X512 получается что оно всё ничего.

Отлично. Теперь, чтобы долго не рассусоливать, перезапускаем наше недоприложение, и делаем там break main, далее run, далее поменяем %rsp на соточку – set $rsp = 0x64, далее, пара next и наслаждаемся Program received signal SIGSEGV, Segmentation fault.

Что и требоалось доказать. И приведённый пример гов… «не компилится» и подход, при котором чтение/восстановление уже существующих сегментов и данных приложения без их реального изменения – туда же. Такой подход по своей сути тоже гов… «слышал звон, да не знает где он» и о чём речь вообще.

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

Испортить нестроение мне не получится...

Не хочу портить тебе настроение, но он как бы лабой для студня показывает тебе, как можно управлять адресацией машинных push/pop’ов на адреса из кучи, если ты вдруг этого не осознал (а твоё буйное веселье намекает, что ты таки не).

Т.к. этой самой лабой для студня, которая в Linux даже и не компилится (надеюсь, не испортил настроение?) он показыват только то, что он не понимает что такое stack segment в принципе. Про setjmp/longjmp я в курсе примерно с момента их ввода в стандарт. Именно это и развеселило меня. Не скрою, он потешен. =)))

Т.е - у него Q.E.D.

Нет. Он ни как не изменяет сегмент стека. Он тупо его копирует (среди прочих данных), даже не подозревая о том, что он это делает. Говорите, это у него Q.E.D.? Ну как-то жиденько он q.e.dнул-то, не отнять.

Он выдаёт свою нерабочую реализацию за работу с сегментом стека. Что не является таковой ни в какой мере.

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

ЛОЛШТА?!? Продемонстрируете? К коду, батенька, к коду… =))) Только сделайте милость – продемонстрируйте на текущем API, в Linux. Если у Вас в другие языки завезли другой API, то я с любопытством на это повзираю. Не сложно будет удивить? =)))

Да и фиг бы с ним.

ЩИТОПРАСТИТИ?!? Мы тут сборище программистов или курсы кройки и шитья? Что произойдёт при рукоблудии над сегментом стека, я выше показал. Ну хоть Вы бы постыдлись ужо, штоле? =)))

Иногда полезно выходить из зоны комофрта и переставать быть «виужал-басик»- / фокспро- / dBase- / php- / java- / dotnet- / lisp- / scala- / clojure- / javascript- / typescript- / rust-программистом.

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

В остальном… Как Вы там написали-то? Да и фиг бы с ним? Ну, как скажете. Приму на веру, так и быть. =)))

Moisha_Liberman ()

В стеке по умолчанию всего 8 мегабайт (для 64 битных систем). Можно конечно ключами компоновщика увеличить, но прирост скорости сомнителен.

dvetutnev ()
Ответ на: Бггг... =))) от Moisha_Liberman

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

Где я говорил про изменение «сегмента стека»? Я говорил только что им можно не пользоваться. Вместо него можно использовать стек выделенный с помощью malloc.

И восстанавливаете в longjmp()

Восстановление сделано для полноты примера. Можно и не восстанавливать, просто не вызывайте longjmp(oldCtx, 1);.

Таких элементов структур, которыми Вы пытаетесь пользоваться, в Linux нет.

Это Haiku 32 bit. В Линуксе вместо ctx[0].regs будет ctx[0].__jmpbuf.

Т.е., мы понимаем что реальное смещение в stack segment считается как %rbp:%rsp.

Что за бред? Вы не путаете с 16 битной сегментной моделью (SS:SP)? В 32 и 64 битных режимах линейная память и никаких сегментов нет кроме минимально необходимых (CS, DS).

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

Я не знаю что такое Haiku.

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

Вместо него можно использовать стек выделенный с помощью malloc.

Ну, то есть, до Вас начинает доходить что у Вас получился кривенький такой закос в сторону abstract data type типа stack. Да, если Вы сделаете над собой усилие и прочтёте то, что Вам уже написано, я Вам об этом прямо сразу и сказал.

Отлично. Теперь Вам остаётся понять что нет, stack segment с этим Вашим недозакосом не связан ни как, кроме слова stack в названии и дело в шляпе. =)))

P.S. А! Вижу! По-моему, уже поняли…

де я говорил про изменение «сегмента стека»? Я говорил только что им можно не пользоваться.

Да, долго же Вас пришлось «лечить». Но я мужик чертовски упорный (бываю иногда). =)))

Точка?

Moisha_Liberman ()
Последнее исправление: Moisha_Liberman (всего исправлений: 1)
Ответ на: Я не знаю что такое Haiku. от Moisha_Liberman

Теперь Вам остаётся понять что нет, stack segment с этим Вашим недозакосом не связан ни как, кроме слова stack в названии и дело в шляпе. =)))

Да я уже который раз повторяю что да, не связан. Но «сегмент стека» не обязателен, всё и без него работает. Функции прекрасно запускаются на стеке в malloc.

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

Оооо... Да Вы неугомонный? =)))

Но «сегмент стека» не обязателен, всё и без него работает.

Хмм… Видимо, сегмент стека не знает о Вашем мнении и теперь только остаётся понять зачем его копируют при setjmp и восстанавливают при longjmp?

Функции прекрасно запускаются на стеке в malloc.

Ну, то есть, без стекового сегмента. И Вы можете показать как Вы передаёте параметры (например) в такие функции? Только давайте «без рук», т.е. без энтого самого стекового сегмента. И в Linux.

Что за бред? Вы не путаете с 16 битной сегментной моделью (SS:SP)? В 32 и 64 битных режимах линейная память и никаких сегментов нет кроме минимально необходимых (CS, DS).

Вы знаете… Не скрою… Мне Вас даже жалко становится. Вот так вот прямо сразу… хоп:

  • rbp - register base pointer (start of stack)
  • rsp - register stack pointer (current location in stack, growing downwards)

Или вот тоже

Скажите пожалуйста, у Вас неприятие документации и здравого смыла это что-то личное или религиозные убеждения? =)))

Moisha_Liberman ()
Ответ на: Оооо... Да Вы неугомонный? =))) от Moisha_Liberman

rbp - register base pointer (start of stack)

Выкидывайте вашу документацию, она врёт. Регистра начала стека не существует. RBP — это указатель на текущий стековый кадр (frame pointer), а не на начало стека. И этот регистр не обязателен, есть опция компилятора -fomit-frame-pointer, которая его отключает. Работает так:

PUSH RBP      ; сохранили указатель на предыдущий фрейм
MOV RBP, RSP  ; установили указатель на текущий фрейм
SUB RSP, 16   ; выделили память на стеке под локальные переменные
...
MOV RSP, RBP  ; удалили память под локальные переменные
POP RBP       ; вернули указатель на предыдущий фрейм
RET           ; выход из функции
X512 ()
Ответ на: Испортить нестроение мне не получится... от Moisha_Liberman

Только сделайте милость – продемонстрируйте на текущем API, в Linux.

Пожалуйста:

SwitchStack.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
	size_t rbx, rbp, r12, r13, r14, r15, rsp, rip;
} JmpBuf[1];

int SetJmp(JmpBuf ctx) __THROWNL;
void LongJmp(JmpBuf ctx, int res) __THROWNL __attribute__ ((__noreturn__));

void SwitchStack(void *stack, size_t stackSize, void (*Entry)(JmpBuf, void*), void *arg)
{
	JmpBuf oldCtx, ctx;
	if (SetJmp(oldCtx) == 0) {
		size_t **fp = NULL;
		size_t *sp = (size_t*)((char*)stack + stackSize);
		printf("stack end: %p\n", sp);
		sp--; *sp = (size_t)arg;
		sp--; *sp = (size_t)oldCtx;
		sp--; *sp = 0; // return address
		memset(ctx, 0, sizeof(ctx));
		ctx[0].rbp = (size_t)fp;
		ctx[0].rsp = (size_t)sp;
		ctx[0].rip = (size_t)Entry;
		LongJmp(ctx, 1);
	}
}

// recursive function: demonstration of using custom stack
void Do(int n)
{
	printf("Do(%d)\n", n);
	if (n > 0)
		Do(n - 1);
}

void StackEntry()
{
	JmpBuf probeCtx;
	SetJmp(probeCtx);

	struct Args {
		JmpBuf *oldCtx;
		void *arg;
	} *args;
	args = (struct Args*)(probeCtx[0].rbp + 16);

	printf("+StackEntry\n");
	printf("arg: %s\n", (char*)args->arg);
	printf("SP: %p, ", probeCtx[0].rsp);
	printf("FP: %p\n", probeCtx[0].rbp);
	Do(5);
	printf("-StackEntry\n");
	LongJmp(*args->oldCtx, 1);
}

int main()
{
	printf("+main\n");
	JmpBuf probeCtx;
	SetJmp(probeCtx);
	printf("main = %p\n", (void*)main);
	printf("SP: %p, ", probeCtx[0].rsp);
	printf("FP: %p, ", probeCtx[0].rbp);
	size_t stackSize = 1024*1024;
	void *stack = malloc(stackSize);
	SwitchStack(stack, stackSize, StackEntry, (void*)"This is a test.");
	free(stack); stack = NULL;
	printf("-main\n");
	return 0;
}

SetJmp.S:

/* Copyright 2011-2012 Nicholas J. Kain, licensed under standard MIT license */

#include "asm_defs.h"

FUNCTION(SetJmp):
	mov %rbx,(%rdi)         /* rdi is jmp_buf, move registers onto it */
	mov %rbp,8(%rdi)
	mov %r12,16(%rdi)
	mov %r13,24(%rdi)
	mov %r14,32(%rdi)
	mov %r15,40(%rdi)
	lea 8(%rsp),%rdx        /* this is our rsp WITHOUT current ret addr */
	mov %rdx,48(%rdi)
	mov (%rsp),%rdx         /* save return addr ptr for new rip */
	mov %rdx,56(%rdi)
	xor %eax,%eax           /* always return 0 */
	ret
FUNCTION_END(SetJmp)

FUNCTION(LongJmp):
	xor %eax,%eax
	cmp $1,%esi             /* CF = val ? 0 : 1 */
	adc %esi,%eax           /* eax = val + !val */
	mov (%rdi),%rbx         /* rdi is the jmp_buf, restore regs from it */
	mov 8(%rdi),%rbp
	mov 16(%rdi),%r12
	mov 24(%rdi),%r13
	mov 32(%rdi),%r14
	mov 40(%rdi),%r15
	mov 48(%rdi),%rsp
	jmp *56(%rdi)           /* goto saved address without altering rsp */
FUNCTION_END(LongJmp)

asm_defs.h:

/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */
#ifndef SYSTEM_ARCH_X86_64_ASM_DEFS_H
#define SYSTEM_ARCH_X86_64_ASM_DEFS_H


#define SYMBOL(name)			.global name; name
#define SYMBOL_END(name)		1: .size name, 1b - name
#define STATIC_FUNCTION(name)	.type name, @function; name
#define FUNCTION(name)			.global name; .type name, @function; name
#define FUNCTION_END(name)		1: .size name, 1b - name


#endif	/* SYSTEM_ARCH_X86_64_ASM_DEFS_H */

Команда сборки gcc SwitchStack.c SetJmp.S -fno-omit-frame-pointer -o SwitchStack.

X512 ()
Ответ на: Оооо... Да Вы неугомонный? =))) от Moisha_Liberman

Вашем мнении и теперь только остаётся понять зачем его копируют при setjmp

Если вы внимательно смотрели, у меня jmp_buf конструируется с нуля, setjmp для ctx не вызывается и из «сегмента стека» ничего не копируется. Сохраняется только oldCtx, но это делать не обязательно, можно его удалить и заменить longjmp(oldCtx, 1) на exit(0). Понятное дело что после этого возврат к предыдущему стеку станет невозможен.

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

Не. Опять мимо.

RBP — это указатель на текущий стековый кадр (frame pointer), а не на начало стека.

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

И этот регистр не обязателен, есть опция компилятора -fomit-frame-pointer, которая его отключает. Работает так:

Как -fomit-frame-pointer работает я в курсе, т.к. у меня Gentoo, знаете ли. Но вот документацию бы Вам почитать что ли:

-fomit-frame-pointer

Don’t keep the frame pointer in a register for functions that don’t need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions. It also makes debugging impossible on some machines.

Т.е., -fomit-frame-pointer может быть использован, а может и не быть. Включение данного флага gcc не носит обязательного к исполнению характера. gcc даже при включённом флаге может сохранить и использовать фрейм стека.

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

Ожидать что gcc по щелчку пальцев везде вырубит stack frames несколько наивно.

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

Не продолжайте дальше, ненадо...

Если вы внимательно смотрели, у меня jmp_buf конструируется с нуля,

Последний Ваш спам я даже проверять не буду.

#include <setjmp.h>

jmp_buf probeCtx;

Вот это что ли «с нуля»?

Moisha_Liberman ()
Последнее исправление: Moisha_Liberman (всего исправлений: 1)
Ответ на: Нет. от Moisha_Liberman

ты же даже сам тут пишешь про clone(), в который надо передавать указатель на новый стек — выделенный родителем участок памяти.

Программа может использовать любую память как стек.

Фсмысле??? Нет, не может. Сегмент стека программы это сегмент стека программы.

а мойша либерман — это мойша либерман. штаны постирал хоть или так и ходишь?

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

Да. Именно.

ты же даже сам тут пишешь про clone(), в который надо передавать указатель на новый стек — выделенный родителем участок памяти.

А теперь дело за малым – мне тут уважаемый коллега рассказывает что можно нарукоблудить реализацию стекового сегмента на malloc(). Так что осталось всего ничего – передать этот стек в потомок и заставить потомок использовать его как стек. Т.е., по сути, полностью упразднить стековый сегмент. И в родителе и в потомке.

Moisha_Liberman ()
Ответ на: Оооо... Да Вы неугомонный? =))) от Moisha_Liberman

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

И вот чего я не понимаю, так это почему с появлением 32 бит, и уже тем более 64 бит, и с появлением меммапа, почему не прикрутили этот меммап к стеку? Хотя бы даже доп.опцией компиляции?

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

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

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

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

Сегмент стека это, само по себе,

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

Допустим, Вы вызвали некую функцию, передали параметры, вот здесь стек и нужен. Кстати, именно поэтому и рекомендуют в функции передавать параметры не по значению, а по ссылке. Так меньше работы для стека в случае более-менее значимых объёмов данных – ненужно копировать их. «По ссылке» всяко-разно короче получится.

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

Да, всё верно сделали. Обычно так и делают – менеджер памяти либо в куче (если предполагается что данные будут использоваться более-менее постоянно), либо через отмапленные сегменты памяти (если предполагается что в коде поработали с этими данными и забыли о них). В последнем случае программа не будет выжирать всю доступную память.

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

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

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

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

Уже в Firefox признали, что перепрыгивание защитной станицы стека — это дыра. Как долго до них доходило, ещё в Win16 умели проверять переполнение стека правда по другому.

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

в Firefox

Firefox это не C++. Firefox это программа, создаваемая с использованием в том числе и C++. Разработчики там делают много чего для усиления безопасности, но это осознанный выбор, а не что-то, навязанное C++. Например, там намеренно портят освобождаемую память, чтобы быстрее ловить use-after-free. Очевидно, требования отравлять освобождаемую память в стандарте C++ нет, и вряд ли какой-то компилятор будет делать так по умолчанию.

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

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

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

Всё это детали реализации.

wandrien ()
Ответ на: Испортить нестроение мне не получится... от Moisha_Liberman

Re: Мы тут сборище программистов или курсы кройки и шитья?

ЛОЛШТА?!? Продемонстрируете? К коду, батенька, к коду… =))) Только сделайте милость – продемонстрируйте на текущем API, в Linux. Если у Вас в другие языки завезли другой API, то я с любопытством на это повзираю. Не сложно будет удивить? =)))

Что за истерика? Вы что, прямо с курсов кройки и шитья к нам заглянули?

Пишете в гугле: «c coroutines library».

Вот вам ворох ссылок на корутины с кастомными стеками:

https://github.com/hnes/libaco

https://github.com/lewissbaker/cppcoro

http://libmill.org/documentation.html

https://swtch.com/libtask/

https://github.com/IoLanguage/io/blob/master/libs/coroutine/docs/index.html

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

Система может проверить, что сискол выполнен со стека вне пределов настоящего стека и грохнуть процесс как подвергшийся атаке.

Тогда посыпятся всякие скриптовые языки с JIT а также Go со своими гороутинами, которые работают в своём стеке. В движках баз данных вроде бы применяются короутины.

X512 ()
Последнее исправление: X512 (всего исправлений: 2)

Это троллинг такой?

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

Да, размер стека ограничен.

И сколько можно?

man ulimit

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

Смысл?

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

X512 ()
Последнее исправление: X512 (всего исправлений: 1)