<bgdev />free

| |  


All tags 2023 9may ai algorithm alpha amd american api argon2 arm asm asmbb assembler attachment awareness balgaria bay888 bcrypt bender beta bgdev-next bgdev-next.👍 big.data bitchnigga bitcoin bmw boi borg brexit bug bulgaria business c cad chat cloud computer-names console crossorigin deprivation desktop dna dotnet email eupl falling feature forum foundation fp fresh fun game gcc github goats google gpl gpt gpt.3.5 gypsies happiness harvard hash improvement include investment it java javascript js kleta kleta.maqka.balg lambi language learning leftovers legend level levenshtein.dist libx license linkedlist linux m0 ma mcafee mele microsoft minimag minimalism negro net nginx nigga not.a.bug oop paradigm parler patterns perception persuasion pipe play.station politics populi pornhub pow pro programming protonmail python reba rust sci-fi scripting seks seo server shell sleep smartbeauty soft-skills sqlite srabska sse starship sugerface syntax tablet tailwindcss telegram theme thug troll80lvl tutanota typescript uacme ui uk unix untermensch upload uptime usa utilities ux vb via viber virtual.reality vox vps vulnerable war wasm weapons-grade web windows word x86 xbox xss youtube zig ziglang Übermensch БОКЕБЪЛГАРИН БЪ БЪлгария Белезниците Били Били.Белезниците БялДонор Веган Виста Възраждане ГЛУПАК Гана Глиста ЕС Казарма Копейкин Мода.и.овча.мисъ НЕКАДЪРНИК НРБ ПО-ЗЛЕ.И.ОТ.РАБИ Подкасти Разни Румен СИК СКУМ СетенЧук Скум ТИР Туче Украйна Урсула Яначков авангард аз айфонджия алгоритми амбиции анархизъм антиваксъри армения аудио аутисти бази.данни бакъп без без.пръчове безпросвета бенчмарк биготи биомаса бира боклук борисов ботев брадва булшит бъг бъгове бял ваксина вандал век венерика викинги вицове вишу война вървежен гана ганорник гей гейщина германия герои гешев глупак говеда групировка гюбек данъкоплатец двойни.стандарти дедотия демокрация дизайн дисциплина добитък докери долар донори држава дришльо дрон ебане еврогейски.съюз езици експеримент електроника електроника.s2 емиграция ендпойнт енум ерген ергономия жалкар задача затоплизъм защита здраве златен злато игри идеали идиократ идиократи идиокрация идиот избори избори.рабин изкуство икономика имбецили имейл инвестиране инокулация инструмента интервю ипад искам.да.си.реда казах камшикодържач капитализъм карабах караница картечница кино клавиатура ковид19 колайдер колям.кур комари комплексар комунизъм консолидация конспирации космонавтика кофа кофит-19 краставица криптовалути курви кучелюбци лайно лаладжия лаптоп либерастия литература лоши.практики луд лъжеучени лъжец любов майни майтапи малоумници мафия мениджмънт месо местене метавселена метафизика механика мистика мисъл мода мода.овча.мисъл модерация морал мутра мутри наука национализъм не.it негър некадърник некадърници неон нидерландия овча овчи олигофрени организация офтопик парички партия педал пенджури пенсия пишока плюскане победа погромист поезия политика порно посредствен почивка празници прасе превод предалщина програмиране проект проста простотии против.правилата проф пръч пръч.дришльо пръчка психика психични.болести психология пустиняк путин путката путьо рабин рабин.е.шибан.пе работа радост разврат разни разработка расизъм резерват рейтинг реклама рекламен религия рест ризи ропче ропчета русия руски.език рутина самоковска сасипаха секира село селяндур сериали сериозно.програм сетен сеянин симулация скопяване скръм слушалки сортиране софия софтуер софтуни социализъм спектрометър спринтове сране стандарти стил стуйо стюи сушилня сцена съвет съм сън сървър сърничка таб ташаци телевизия тема територията терминология термояд технологии титли традиция тролинг тръмп туба туче тъпак тъпанари тъпня уиндоус украйна умнокрасивци фалит фантастика фашизъм фейк.акаунти физика филми форум форумни.проекти футбол хазарт хамали харабия хардуер хахаха хомофобия хостинг храна хумор цайко цайси целофан цензура цензурра циганин чалга чалгар чекии чернокраки честота чипове чнг чужбина чук шпация щайга юан яката яко ям 🔨 😂 🪓


Атомарно вкарване на елемент в опашка – възможно ли е?

  

0 1 2 3


  bvbfan  Създадено на 06.01.2023, видяно: 722 пъти. #83873
Дон Реба

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

Не е по-добре, мутекса е системно извикване.



  johnfound  Създадено на 06.01.2023, видяно: 698 пъти. #83876
Дон Реба

ако се откажеш от комунистическата обсесия за Справедливост, няма нужда от фифо и следователно от опашка изобщо. тогава става и с обикновен динамичен масив без лимит на размера. аз никога през живота си не съм ползвал фифо, може и да е затова

Точно тука "справедливостта" и в най-широкият ѝ смисъл няма нищо общо. Имам X сървър, който обработва заявките по реда на подаването им. Съответно на някои от заявките връща отговор, но асинхронно, когато обработи заявката. Отговорите се приемат асинхронно от отделна нишка.

Съответно, този който изпраща заявката, регистрира и callback функция, която да обработи отговора, когато той дойде.

Няма проблем callback-а да се записва на произволно място в масив. Само че, когато нишката, която обработва отговорите от сървъра получи отговор, ще трябва да претърсва целият масив дали има в него callback точно за тази заявка. Което си е хамалогия. А когато callback функциите са във FIFO, то когато дойде отговорът, нужният запис е на изхода на опашката, готов да бъде обработен.

Тоест в случая, сървърът е който определя нужните структури данни, а не някакви идеологически съображения.

Освен това, според твоите представи излиза, че аз трябва да съм против използването на стек, поради безкрайно несправедливата му природа... А аз си умирам да използвам стекове във всякакви алгоритми. :-P



  Дон Реба  Създадено на 06.01.2023, видяно: 686 пъти. #83880

Имам X сървър, който обработва заявките по реда на подаването им.

да де, а защо си решил точно по тоя ред да бъде? ако някаква система не се справя със заявките, така или иначе ще се препълни и умре. ако се справя, голямо натрупване няма да има, дали ще обработваш "справедливо" по реда на заявките, или "несправедливо" хващайки за ушите произволна заявка, все тая, всички ще бъдат обработени и то бързо, някой ще получи 10 мс закъснение, голям праз.



  Реконструктор  Последно редактирано на 06.01.2023 от Реконструктор, видяно: 682 пъти. #83881
realinformatik

Начи с атомарна инструкция ще стане, ако проблема може да се реши от една такава инструкция. Но ако проблема е съставен, т.е. за решението му трябват повече от една инструкция (без значение атомарна или не), ще трябва да локнеш поредицата от тези инструкции.

Нещата първо се разделят на операции и чак тогава почваш да мислиш за инструкции и каквото и да е друго.

Сега си мисля следното извращение, за което, обаче, трябва да имаш RTOS. При това такава RTOS, която да ти позволява да скеджулваш код (в случая пъхането в опашката) точно в началото на тайм слайса. И ако:

1. Си наясно колко е тайм слайса за конкретната ОС

2. Успееш да вкараш необходимия код, така, че да не излиза извън тайм слайса при worst case scenario

то тогава може да не ти трябват синхронизационни примитиви



  johnfound  Създадено на 06.01.2023, видяно: 682 пъти. #83882
Дон Реба

Имам X сървър, който обработва заявките по реда на подаването им.

да де, а защо си решил точно по тоя ред да бъде?

Сървъра не съм го писал аз. Нито Х протокола. Аз пиша клиент. Но ако си представям правилно проблема, реда на обработка е важен, защото иначе ще се получи неправилна картинка в някои случаи. При това рандомно неправилна.



  BIGBUGEX  Създадено на 07.01.2023, видяно: 643 пъти. #83899

Понеже не ти разбирам много кода, го пренаписах. В примера само една нишка чете и произволен брой нишки пишат.


format PE console

section ".code" code readable executable
struc L {
	.next	dd ?
	.data	dd ?
}

macro strucdef [s] {
forward
	virtual at 0
		s s
		sizeof.#s:
	end virtual
	irps r, eax,ebx,ecx,edx,esi,edi,ebp,esp \{
	 virtual at r
	 	r\#.#s	s
	 end virtual
	\}
}

strucdef L

thread_write:
	sub	esp,0x10
	mov	[esp + 0],sizeof.L
	call	[malloc]
	mov	ecx,eax
	@@:
	mov	eax,[list]
	mov	[ecx.L.next],eax
	pause
	lock
	cmpxchg [list],ecx
	jne	@b
	add	esp,0x10
	ret
	
thread_read:
	sub	esp,0x10
	xor	ecx,ecx
	.1:
	mov	eax,[list]
	pause
	test	eax,eax
	jz	.1
	mov	edx,[eax.L.next]
	test	edx,edx
	jz	.remove
	cmp	ecx,[edx.L.next]
	je	.bottom
	@@:
	mov	eax,edx
	mov	edx,[edx.L.next]
	cmp	ecx,[edx.L.next]
	jnz	@b
	.bottom:
	mov	[eax.L.next],ecx
	mov	eax,edx
	add	esp,0x10
	ret
	.remove:
	lock
	cmpxchg	[list],edx
	jne	.1
	add	esp,0x10
	ret

section ".data" data writeable
list	dd 0

Не съм го тествал, но на око би трябвало да работи. Също така хийпа ти трябва да е защитен. Ако е мислен еднонишков може да бъгне. Може да се ползва втори списък от който да се пушват и попват елементи при алокация/деалокация.



  johnfound  Създадено на 07.01.2023, видяно: 608 пъти. #83902
BIGBUGEX

Понеже не ти разбирам много кода, го пренаписах. В примера само една нишка чете и произволен брой нишки пишат.

Скрит код на асемблер

format PE console

section ".code" code readable executable
struc L {
	.next	dd ?
	.data	dd ?
}

macro strucdef [s] {
forward
	virtual at 0
		s s
		sizeof.#s:
	end virtual
	irps r, eax,ebx,ecx,edx,esi,edi,ebp,esp \{
	 virtual at r
	 	r\#.#s	s
	 end virtual
	\}
}

strucdef L

thread_write:
	sub	esp,0x10
	mov	[esp + 0],sizeof.L
	call	[malloc]
	mov	ecx,eax
	@@:
	mov	eax,[list]
	mov	[ecx.L.next],eax
	pause
	lock
	cmpxchg [list],ecx
	jne	@b
	add	esp,0x10
	ret
	
thread_read:
	sub	esp,0x10
	xor	ecx,ecx
	.1:
	mov	eax,[list]
	pause
	test	eax,eax
	jz	.1
	mov	edx,[eax.L.next]
	test	edx,edx
	jz	.remove
	cmp	ecx,[edx.L.next]
	je	.bottom
	@@:
	mov	eax,edx
	mov	edx,[edx.L.next]
	cmp	ecx,[edx.L.next]
	jnz	@b
	.bottom:
	mov	[eax.L.next],ecx
	mov	eax,edx
	add	esp,0x10
	ret
	.remove:
	lock
	cmpxchg	[list],edx
	jne	.1
	add	esp,0x10
	ret

section ".data" data writeable
list	dd 0

Не съм го тествал, но на око би трябвало да работи. Също така хийпа ти трябва да е защитен. Ако е мислен еднонишков може да бъгне. Може да се ползва втори списък от който да се пушват и попват елементи при алокация/деалокация.

Нещо ваденето ми се струва странно. Променливата list началото на опашката ли е или краят? Ако вкарваш всеки път в нея, излиза, че е краят. Но тогава как взимаш елементи от началото?

Един съвет: конструкциите от вида [edx.L.next] са рожба на желанието да накараш асемблера да прилича на "нормалните езици". Тогава по-добре пиши на тях.

В твоята конструкция, двете точки представляват две различни същности. Първата – обикновено събиране, а втората е просто част от етикета.

На асемблер е по-добре да напишеш [edx + L.next] тъй като това отразява същността на операцията, че адресът се получава като сума от регистъра и отместването.



  BIGBUGEX  Създадено на 07.01.2023, видяно: 596 пъти. #83904
johnfound

Нещо ваденето ми се струва странно. Променливата list началото на опашката ли е или краят? Ако вкарваш всеки път в нея, излиза, че е краят. Но тогава как взимаш елементи от началото?

Обхождам всички елементи до началото и изрязвам първия. И го връщам през еах.

johnfound

Един съвет: конструкциите от вида [edx.L.next] са рожба на желанието да накараш асемблера да прилича на "нормалните езици". Тогава по-добре пиши на тях.

В твоята конструкция, двете точки представляват две различни същности. Първата – обикновено събиране, а втората е просто част от етикета.

На асемблер е по-добре да напишеш [edx + L.next] тъй като това отразява същността на операцията, че адресът се получава като сума от регистъра и отместването.

Аз съм адвенсед, чуек. Ползвам уникални синтаксиси щом става дума за флат асемблер. Имам типове с автоматично подравняване и пакетиране като в С++.



  johnfound  Създадено на 07.01.2023, видяно: 571 пъти. #83912
BIGBUGEX

Обхождам всички елементи до началото и изрязвам първия. И го връщам през еах.

Е-е-е! Не, така не ме кефи. Трябва да си помни първият елемент и да си почва от него директно. Почти съм го направил, ще го постна като е готово...

BIGBUGEX

Аз съм адвенсед, чуек. Ползвам уникални синтаксиси щом става дума за флат асемблер. Имам типове с автоматично подравняване и пакетиране като в С++.

А бе, както ти харесва. То на флат асемблер съм виждал реализирани практически езици от високо ниво. Но въпросът е – защо? Ако искаш език от високо ниво – компилатора на C++ ще се справи по-добре при всички случаи.



  BIGBUGEX  Последно редактирано на 07.01.2023 от BIGBUGEX, видяно: 569 пъти. #83914

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

edit: Е това имам предвид.

format PE console

section ".code" code readable executable
struc L {
	.next	dd ?
	.data	dd ?
}

macro strucdef [s] {
forward
	virtual at 0
		s s
		sizeof.#s:
	end virtual
	irps r, eax,ebx,ecx,edx,esi,edi,ebp,esp \{
	 virtual at r
	 	r\#.#s	s
	 end virtual
	\}
}

strucdef L

thread_write:
	sub	esp,0x10
	mov	[esp + 0],sizeof.L
	call	[malloc]
	mov	ecx,eax
	@@:
	mov	eax,[list]
	mov	[ecx.L.next],eax
	lock
	cmpxchg [list],ecx
	jne	@b
	add	esp,0x10
	ret
	
thread_read:
	sub	esp,0x10
	xor	ecx,ecx
	mov	eax,[list_r]
	cmp	ecx,eax
	jne	.read
	
	@@:
	mov	eax,[list]
	pause
	test	eax,eax
	jz	@b
	
	@@:
	lock
	cmpxchg	[list],ecx
	jne	@b
	
	mov	edx,[eax.L.next]
	mov	[eax.L.next],ecx
	test	edx,edx
	jz	.read
	
	@@:
	mov	ecx,[edx.L.next]
	mov	[edx.L.next],eax
	mov	eax,edx
	mov	edx,ecx
	test	ecx,ecx
	jnz	@b
	
	.read:
	mov	edx,[eax.L.next]
	mov	[list_r],edx
	add	esp,0x10
	ret

section ".data" data writeable
list	dd 0
align	64
list_r	dd 0

Така си проверява в list_r дали има обърнати данни, ако няма изтегля списък, обръща го, и си попълва list_r. Всичко това разбира се на теория.



  janbird  Създадено на 07.01.2023, видяно: 547 пъти. #83915

Spinlock



  Delegate  Последно редактирано на 07.01.2023 от Delegate, видяно: 541 пъти. #83917

Чакай да изплющя това тук :

Use the rep movsd instruction instead of looping over the elements of L one by one in the strucdef macro. This will copy the entire structure in one go, which will likely be faster than looping.

Avoid using the pause instruction in the thread_write and thread_read functions. The pause instruction is useful for reducing contention when spinning on a lock, but it is not necessary in this code because the lock is already held.

Consider using a different synchronization mechanism instead of the lock cmpxchg instruction. This instruction is relatively slow compared to other options such as atomic compare-and-swap (CAS) operations or spin locks.

Use registers more effectively to minimize memory accesses. For example, consider using a register to store the value of list instead of reloading it from memory every time it is needed.

Use the align directive to ensure that data is properly aligned in memory, which can improve performance on some processors.

Optimize the inner loop of the thread_read function. This loop currently compares ecx to edx.L.next and jumps back to the top of the loop if they are not equal. This can be made faster by using a loop counter and breaking out of the loop once the counter reaches a certain value.

Consider using the rep stosd instruction to clear the memory occupied by the L structure in the thread_read function when it is removed from the list. This will be faster than looping over the elements of L one by one.

Use the align 4 directive to ensure that the list variable is aligned on a 4-byte boundary. This may improve performance on some processors.



  johnfound  Последно редактирано на 07.01.2023 от johnfound, видяно: 534 пъти. #83918
Delegate

Чакай да изплющя това тук :

Use the rep movsd instruction instead of looping over the elements of L one by one in the strucdef macro. This will copy the entire structure in one go, which will likely be faster than looping.

Avoid using the pause instruction in the thread_write and thread_read functions. The pause instruction is useful for reducing contention when spinning on a lock, but it is not necessary in this code because the lock is already held.

Consider using a different synchronization mechanism instead of the lock cmpxchg instruction. This instruction is relatively slow compared to other options such as atomic compare-and-swap (CAS) operations or spin locks.

Use registers more effectively to minimize memory accesses. For example, consider using a register to store the value of list instead of reloading it from memory every time it is needed.

Use the align directive to ensure that data is properly aligned in memory, which can improve performance on some processors.

Optimize the inner loop of the thread_read function. This loop currently compares ecx to edx.L.next and jumps back to the top of the loop if they are not equal. This can be made faster by using a loop counter and breaking out of the loop once the counter reaches a certain value.

Consider using the rep stosd instruction to clear the memory occupied by the L structure in the thread_read function when it is removed from the list. This will be faster than looping over the elements of L one by one.

Use the align 4 directive to ensure that the list variable is aligned on a 4-byte boundary. This may improve performance on some processors.

Това пък какво е??? На места звучи безсмислено. ИИ?



  Delegate  Създадено на 07.01.2023, видяно: 528 пъти. #83919

Да, звучи безсмислено. Въпроса е, има ли поне един добър съвет в цялата плява ? Ти и БЪГЕКСА кажете, не съм по асемблера.



  BIGBUGEX  Последно редактирано на 07.01.2023 от BIGBUGEX, видяно: 526 пъти. #83920

Примера е непълен. Ако имаше стартиране на нишки и реална обработка щеше да е по-компетентен.

johnfound

Това пък какво е??? На места звучи безсмислено. ИИ?

chatgpt

chatgpt

Based on the description you provided, it looks like the thread_write and thread_read functions are intended to implement a thread-safe FIFO queue using a linked list. However, there are a few potential issues with the code as written:

The thread_write function does not check whether the call to malloc succeeded in allocating memory. If the allocation fails, the function will still attempt to insert the element into the list, which could cause a crash.

The thread_read function does not check whether the list is empty before attempting to remove an element. If the list is empty, the function will dereference a null pointer, which could cause a crash.

The thread_read function uses a spinlock to wait for an element to be added to the list. However, spinlocks can be inefficient if the contention for the lock is high, as they can consume a lot of CPU time.

The thread_read function does not protect against multiple threads calling it simultaneously. If multiple threads try to read from the queue at the same time, it could lead to data corruption or a crash.

I would suggest adding checks for null pointers and empty lists in both functions, and also considering using a different synchronization primitive, such as a mutex or a semaphore, to protect against concurrent access.

Това е коментар с малко повече контекст към последната версия.



  johnfound  Създадено на 07.01.2023, видяно: 508 пъти. #83921
BIGBUGEX

Примера е непълен. Ако имаше стартиране на нишки и реална обработка щеше да е по-компетентен.

johnfound

Това пък какво е??? На места звучи безсмислено. ИИ?

chatgpt

chatgpt

Based on the description you provided, it looks like the thread_write and thread_read functions are intended to implement a thread-safe FIFO queue using a linked list. However, there are a few potential issues with the code as written:

The thread_write function does not check whether the call to malloc succeeded in allocating memory. If the allocation fails, the function will still attempt to insert the element into the list, which could cause a crash.

The thread_read function does not check whether the list is empty before attempting to remove an element. If the list is empty, the function will dereference a null pointer, which could cause a crash.

The thread_read function uses a spinlock to wait for an element to be added to the list. However, spinlocks can be inefficient if the contention for the lock is high, as they can consume a lot of CPU time.

The thread_read function does not protect against multiple threads calling it simultaneously. If multiple threads try to read from the queue at the same time, it could lead to data corruption or a crash.

I would suggest adding checks for null pointers and empty lists in both functions, and also considering using a different synchronization primitive, such as a mutex or a semaphore, to protect against concurrent access.

Това е коментар с малко повече контекст към последната версия.

А бе, за ИИ е много добре! Още не съм отишъл да го пробвам, но много го хвалят.



  bvbfan  Създадено на 07.01.2023, видяно: 506 пъти. #83922

Оставете го AI, той греши и то много.



  BIGBUGEX  Създадено на 07.01.2023, видяно: 499 пъти. #83923

Попитах го за повече подробности относно проверката за празен списък. Явно не осъзнава, че със спинлока си гарантирам съществуващ елемент. Призна си грешката и почна да ме убеждава как спинлока е лошо нещо. Ще стане чуек от него.



  bvbfan  Създадено на 08.01.2023, видяно: 467 пъти. #83934
BIGBUGEX

Попитах го за повече подробности относно проверката за празен списък. Явно не осъзнава, че със спинлока си гарантирам съществуващ елемент. Призна си грешката и почна да ме убеждава как спинлока е лошо нещо. Ще стане чуек от него.

Започни нова сесия, повтори въпросите си и ще повтори всички грешки.



  johnfound  Последно редактирано на 08.01.2023 от johnfound, видяно: 455 пъти. #83942

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

При това и вкарването и изкарването са атомарни, тоест в опашката могат да вкарват няколко нишки и да изкарват няколко нишки:

Малко код с доста коментари
struct Element
  .pNext dd ?
  .pData:
ends


pFirst dd ?
pLast  dd ?


proc InitQueue
begin
        lea     eax, [pFirst]
        mov     [pLast], eax  ; Тук е половината магия. Ако опашката е празна,
                              ; [pLast] съдържа указател към pFirst.
        xor     eax, eax
        mov     [pFirst], eax
        return
endp


proc AddToQueue
begin
; алокейтваме елемента

        stdcall GetMem, sizeof.Element
        mov     edi, eax

; запълваме полетата му.
        xor     eax, eax
        mov     [edi+Element.pNext], eax

; атомарно заместваме указателя към последния елемент в главната структура.
; Да се отбележи, че [pLast] никога не е равно на 0.
; Долната инструкция ще запише edi в [pLast] и ще върне в eax указател към
; предишния последен елеемнт, или указател към pFirst ако опашката е празна.
        xor  eax, eax
        lock cmpxchg [pLast], edi

; Ако вече е имало последен елемент/и ги проследяваме и се записваме след последния.
.loop_last:
        mov     ecx, eax
        xor     eax, eax
        lock cmpxchg [ecx + Element.pNext], ebx
        jne     .loop_last

        return
endp



proc FetchElement
begin
        xor     esi, esi
        xchg    [pFirst], esi      ;xchg е винаги атомарен и без lock.

        test    esi, esi
        jz      .exit_empty

; разкачаме елемента от опашката

        mov     eax, esi            ; 2 варианта: Или това е последния елемент в опашката (esi == [pLast])
        lea     edx, [pFirst]       ; тогава нулираме до начално състояние, или има друг елемент и тогава е
        lock cmpxchg [pLast], edx   ; безопасно да манипулираме ListElement.pNext полето.
        je     .exit_ok   ; това е било последния елемент и опашката е в начално състояние.

        mov     eax, [esi+Element.next]
        mov     [pFirst], eax       ; това не е последния елемент, така че си го връщаме в pFirst

.exit_ok:
        mov     eax, esi      ; връщаме разкачения елемент
        clc
        return

.exit_empty:
        stc
        return
endp   

Edit: Добавих една забравена инструкция във FetchElement: mov eax, [esi+Element.next]


0 1 2 3


Атомарно вкарване на елемент в опашка – възможно ли е?

  



AsmBB v3.0 (check-in: 7544654b24928b93); SQLite v3.47.0 (check-in: 03a9703e27c44437);
©2016..2024 John Found; Licensed under EUPL; Powered by Assembly language Created with Fresh IDE