Жадыны қолмен басқару: C, C++ тілдеріндегі жадты бөлу/босату әдістері, қоқыс жинау және автоматты сілтеме санау туралы ақпарат. Программалау үшін маңызды!
Ағылшыншамен салыстырыңыз: абзацты басыңыз — түпнұсқа терезеде ашылады. Абзац астындағы EN түймесі оны мәтін ішінде көрсетеді.
Мазмұны
Кіріспе
Компьютерлік жадыны басқару әдістемесі
Computer memory management methodology
Компьютерлік ғылымда қолмен жадыны басқару – бағдарламашының пайдаланылмаған объектілерді немесе қоқысты анықтап, босату үшін қолмен берілген нұсқауларды пайдалануын білдіреді. 1990 жылдардың ортасына дейін өнеркәсіпте қолданылатын бағдарламалау тілдерінің көпшілігі қолмен жадыны басқаруды қолдады, бірақ қоқыс жинау 1959 жылдан бері, Lisp тілімен енгізілгеннен бастап бар еді. Дегенмен, қазіргі кезде Java сияқты қоқыс жинауды қолдайтын тілдер кеңінен танымал болып келеді, ал Objective C және Swift тілдері Автоматты сілтеме санау арқылы ұқсас мүмкіндіктерді ұсынады. Бүгінгі күні кеңінен қолданылатын негізгі қолмен басқарылатын тілдер – C және C++ тілдері. (C динамикалық жадының бөлінуіне қараңыз).
In computer science, manual memory management refers to the usage of manual instructions by the programmer to identify and deallocate unused objects, or garbage. Up until the mid 1990s, the majority of programming languages used in industry supported manual memory management, though garbage collection has existed since 1959, when it was introduced with Lisp. Today, however, languages with garbage collection such as Java are increasingly popular and the languages Objective C and Swift provide similar functionality through Automatic Reference Counting. The main manually managed languages still in widespread use today are C and C++ – see C dynamic memory allocation.
Сипаттама
Көптеген бағдарламалау тілдері бос жадтан жаңа нысанды қашан бөлу керектігін анықтау үшін қолмен техникаларды қолданады. C тілі malloc функциясын пайдаланады; C++ және Java тілдері new операторын пайдаланады; ал көптеген басқа тілдер (мысалы, Python) барлық нысандарды бос жадтан бөледі. Нысанды қашан жасау керектігін анықтау (нысан жасау) көбінесе оңай және қиындықсыз, бірақ нысан қоймасы сияқты техникалар нысанды дереу пайдаланудан бұрын жасауға мүмкіндік береді. Шын мәніндегі қиындық – нысанды жою, яғни нысанның енді қажет еместігін (қоқыс) анықтау және оның негізгі жадын қайта пайдалану үшін бос жадқа қайтаруды ұйымдастыру. Қолмен жадты бөлуде мұны бағдарламашы да қолмен анықтайды; C-де free, ал C++-де delete операторы сияқты функциялар арқылы. Бұл автоматты айнымалыларда сақталған нысандардың автоматты түрде жойылуымен қарама-қарсы, атап айтқанда (статикалық емес) функциялардың жергілікті айнымалылары, олар C және C++ тілдерінде өмір аяқталысымен жойылады.
Many programming languages use manual techniques to determine when to allocate a new object from the free store. C uses the malloc function; C++ and Java use the new operator; and many other languages (such as Python) allocate all objects from the free store. Determining when an object ought to be created (object creation) is generally trivial and unproblematic, though techniques such as object pools mean an object may be created before immediate use. The real challenge is object destruction – determination of when an object is no longer needed (i. e. is garbage), and arranging for its underlying storage to be returned to the free store for re use. In manual memory allocation, this is also specified manually by the programmer; via functions such as free in C, or the delete operator in C++ – this contrasts with automatic destruction of objects held in automatic variables, notably (non static) local variables of functions, which are destroyed at the end of their scope in C and C++.
Қолмен басқару және дұрыстығы
Жадты қолмен басқару дұрыс қолданылмаған жағдайда бағдарламаға бірнеше маңызды қателерді енгізеді, атап айтқанда жад қауіпсіздігін бұзу немесе жадтың ағуына себеп болады. Бұл қауіпсіздік бұзушылықтарының маңызды көзі болып табылады. Қолданылмаған нысан ешқашан бос жадтан босатылмаса, бұл жадтың ағуы деп аталады. Кейбір жағдайларда жадтың ағуы көтере алады, мысалы, өмірлік циклі бойына шектелген мөлшерде жадты "ағу" жасайтын бағдарлама немесе операциялық жүйеге сеніп, тоқтағанда ресурстарын босататын қысқа мерзімді бағдарлама. Дегенмен, көбінесе жадтың ағуы ұзақ уақыт бойы жұмыс істейтін бағдарламаларда кездеседі, және мұндай жағдайларда шексіз мөлшерде жад ағуы мүмкін. Мұндай жағдайда қол жетімді бос жад көлемі уақыт өте келе азаяды; ол толығымен толғанда бағдарлама құлайды. Объектінің жады бірнеше рет жойылса; объект бірнеше рет жойылса; егер бос жадта бөлінбеген объектіні басқару үшін көрсеткіш қолданылса, бағдарламашы осы көрсеткіштің нысанының жадын босатуға тырысса; немесе белгілі бір сыртқы тапсырма, жіп немесе процесс басқаратын жадтың кездейсоқ бөлігі арқылы объектіні басқару кезінде бағдарламашы объектінің күйін бұзса, мүмкін оның шегінен тыс жазып, жадты басқару деректерін зақымдаса, динамикалық жадты басқару жүйесінің күрт сәтсіздігіне әкелуі мүмкін. Мұндай әрекеттердің салдары үйіндінің бұзылуына, жаңадан құрылған, бірақ бірнеше рет жойылған объектінің орнында тұрған басқа объектінің мерзімінен бұрын жойылуына, жадты қорғау ережесін бұзудан туындаған сегментация қатесіне және басқа да белгісіз мінез-құлық түрлеріне алып келуі мүмкін. Жойылған нысандарға сілтемелер жойылғаннан кейін қолданылса, "жабайы көрсеткіштерге" айналады; мұндай көрсеткіштерді қолдануға тырысу қателерді анықтауды қиындатады. Тек қоқыс жинауды қолданатын тілдер мұндай екі түрлі қатеден сақтануға мүмкіндік береді. Жадтың ағуы орын алуы мүмкін (және шектелген ағу жиі ұрпақтық немесе консервативті қоқыс жинаумен кездеседі), бірақ әдетте қолмен басқару жүйелеріндегі жадтың ағуына қарағанда аз зиянды болады.
Manual memory management is known to enable several major classes of bugs into a program when used incorrectly, notably violations of memory safety or memory leaks. These are a significant source of security bugs. When an unused object is never released back to the free store, this is known as a memory leak. In some cases, memory leaks may be tolerable, such as a program which "leaks" a bounded amount of memory over its lifetime, or a short running program which relies on an operating system to deallocate its resources when it terminates. However, in many cases memory leaks occur in long running programs, and in such cases an unbounded amount of memory is leaked. When this occurs, the size of the available free store continues to decrease over time; when it is finally exhausted, the program then crashes. Catastrophic failure of the dynamic memory management system may result when an object's backing memory is deleted out from under it more than once; an object is explicitly destroyed more than once; when, while using a pointer to manipulate an object not allocated on the free store, a programmer attempts to release said pointer's target object's backing memory; or when, while manipulating an object via a pointer to another, arbitrary area of memory managed by an unknown external task, thread, or process, a programmer corrupts that object's state, possibly in such a way as to write outside of its bounds and corrupt its memory management data. The result of such actions can include heap corruption, premature destruction of a different (and newly created) object which happens to occupy the same location in memory as the multiply deleted object, program crashes due to a segmentation fault (violation of memory protection) and other forms of undefined behavior. Pointers to deleted objects become wild pointers if used post deletion; attempting to use such pointers can result in difficult to diagnose bugs. Languages which exclusively use garbage collection are known to avoid the last two classes of defects. Memory leaks can still occur (and bounded leaks frequently occur with generational or conservative garbage collection), but are generally less severe than memory leaks in manual systems.
Ресурстарды алу - инициализациялау
Қолмен жадты басқарудың бір артықшылығы бар, ол автоматты ресурстарды басқаруға мүмкіндік береді, «Ресурсты алу – инициализациялау» (RAII) парадигмасы арқылы. Бұл, егер объектілерде сирек кездесетін жүйелік ресурстар (мысалы, графикалық ресурстар, файл дескрипторлары немесе деректер базасы қосылымдары) болса, олар объекті жойылған кезде босатылуы тиіс болғанда пайда болады – яғни, ресурс иелігінің мерзімі объектінің мерзімімен байланысты болуы керек. Қолмен басқаруды қолдайтын тілдер мұны объектіні инициализациялау кезінде (конструкторда) ресурсты алу және объектіні жою кезінде (деструкторда) босату арқылы іске асыра алады, бұл нақты уақытта орындалады. Бұл «Ресурсты алу – инициализациялау» деп аталады. Бұл детерминистік сілтеме санауымен де қолданылуы мүмкін. C++ тілінде бұл мүмкіндік жадты босатуды автоматтандыру үшін одан әрі қолданылады, тілдің стандартты кітапханасындағы `shared_ptr` шаблонын қолдану арқылы жадты басқаруды жүзеге асыру – бұл кең таралған тәсіл. Дегенмен, `shared_ptr` барлық объектілерді пайдалану жағдайларына қолайлы емес. Бұл тәсіл көптеген қоқыс жиналатын тілдерде қолданылмайды – әсіресе іздеуге негіделген қоқыс жинағыштарда немесе жетілдірілген сілтеме санауында, себебі түпкілікті әдіс детерминистік емес, және кейде осы әдіс мүлдем орындалмайды. Яғни, түпкілікті әдісті қашан және қалай шақыруға болатынын анықтау қиын; бұл көбінесе «түпкілікті әдіс мәселесі» деп аталады. Java және басқа GC тілдері жадтан басқа сирек кездесетін жүйелік ресурстарды басқару үшін жиі қолмен басқаруды қолданады: ресурстарды басқаратын кез келген объектінің `dispose` әдісін іске асыруы күтіледі, ол осы ресурстарды босатады және объектіні белсенді емес деп белгілейді. Бағдарламашылар графикалық ресурстардың «жоғалуын» болдырмау үшін қажет болған жағдайда `dispose` әдісін қолмен шақыруы керек. Графикалық ресурстарды босату үшін `finalize` әдісіне (Java-да түпкілікті әдістерді қалай іске асырады) сүйену Java бағдарламашылары арасында жаман практика ретінде қарастырылады, сондай-ақ Python-дағы `del` әдісіне де ресурстарды босату үшін сенуге болмайды. Стек ресурстары үшін (бір код блогында алынып, босатылатын ресурстар) бұл Python-дағы `with`, C#-тағы `using` немесе Java-дағы `try-with-resources` сияқты әртүрлі тіл құрылымдарымен автоматтандырылуы мүмкін.
Manual memory management has one correctness advantage, which is that it allows automatic resource management via the Resource Acquisition Is Initialization (RAII) paradigm. This arises when objects own scarce system resources (like graphics resources, file handles, or database connections) which must be relinquished when an object is destroyed – when the lifetime of the resource ownership should be tied to the lifetime of the object. Languages with manual management can arrange this by acquiring the resource during object initialization (in the constructor), and releasing during object destruction (in the destructor), which occurs at a precise time. This is known as Resource Acquisition Is Initialization. This can also be used with deterministic reference counting. In C++, this ability is put to further use to automate memory deallocation within an otherwise manual framework, use of the shared ptr template in the language's standard library to perform memory management is a common paradigm. shared ptr is not suitable for all object usage patterns, however. This approach is not usable in most garbage collected languages – notably tracing garbage collectors or more advanced reference counting – due to finalization being non deterministic, and sometimes not occurring at all. That is, it is difficult to define (or determine) when or if a finalizer method might be called; this is commonly known as the finalizer problem. Java and other GC'd languages frequently use manual management for scarce system resources besides memory via the dispose pattern: any object which manages resources is expected to implement the dispose method, which releases any such resources and marks the object as inactive. Programmers are expected to invoke dispose manually as appropriate to prevent "leaking" of scarce graphics resources. Depending on the finalize method (how Java implements finalizers) to release graphics resources is widely viewed as poor programming practice among Java programmers, and similarly the analogous del method in Python cannot be relied on for releasing resources. For stack resources (resources acquired and released within a single block of code), this can be automated by various language constructs, such as Python's with, C#'s using or Java's try with resources.