Język asemblerowy

Język asemblera jest językiem programowania, który może być używany do bezpośredniego mówienia komputerowi, co ma robić. Język asemblera jest prawie dokładnie taki sam jak kod maszynowy, który komputer może zrozumieć, z wyjątkiem tego, że używa słów zamiast liczb. Komputer nie może tak naprawdę bezpośrednio zrozumieć programu w asemblerze. Może jednak łatwo zmienić program w kod maszynowy, zastępując słowa programu liczbami, które one oznaczają. Program, który to robi nazywany jest asemblerem.

Programy napisane w języku asemblera składają się zwykle z instrukcji, czyli małych zadań, które komputer wykonuje podczas uruchamiania programu. Są one nazywane instrukcjami, ponieważ programista używa ich do instruowania komputera, co ma robić. Część komputera, która wykonuje instrukcje, to procesor.

Język asemblera komputera jest językiem niskiego poziomu, co oznacza, że może być używany tylko do wykonywania prostych zadań, które komputer może zrozumieć bezpośrednio. Aby wykonać bardziej złożone zadania, trzeba powiedzieć komputerowi o każdym z prostych zadań, które są częścią złożonego zadania. Na przykład, komputer nie rozumie, jak wydrukować zdanie na ekranie. Zamiast tego, program napisany w asemblerze musi mu powiedzieć, jak wykonać wszystkie małe kroki, które są zaangażowane w drukowanie zdania.

Taki program składałby się z wielu, wielu instrukcji, które razem robią coś, co wydaje się bardzo proste i podstawowe dla człowieka. To czyni go trudnym do odczytania przez człowieka. W przeciwieństwie do tego, język programowania wysokiego poziomu może mieć pojedynczą instrukcję, taką jak PRINT "Witaj, świecie!", która powie komputerowi, aby wykonał wszystkie małe zadania dla Ciebie.

Rozwój języka asemblerowego

Kiedy informatycy po raz pierwszy zbudowali maszyny programowalne, programowali je bezpośrednio w kodzie maszynowym, który jest serią liczb instruujących komputer, co ma robić. Pisanie języka maszynowego było bardzo trudne i zajmowało dużo czasu, więc w końcu stworzono język asemblera. Język asemblera jest łatwiejszy do odczytania przez człowieka i może być pisany szybciej, ale nadal jest o wiele trudniejszy w użyciu dla człowieka niż język programowania wysokiego poziomu, który próbuje naśladować język ludzki.

Programowanie w kodzie maszynowym

Aby programować w kodzie maszynowym, programista musi wiedzieć, jak wygląda każda instrukcja w kodzie binarnym (lub szesnastkowym). O ile komputerowi łatwo jest szybko zorientować się, co oznacza kod maszynowy, o tyle programiście jest to trudne. Każda instrukcja może mieć kilka postaci, z których wszystkie dla ludzi wyglądają jak wiązka liczb. Każdy błąd, który ktoś popełni podczas pisania kodu maszynowego, zostanie zauważony dopiero wtedy, gdy komputer wykona niewłaściwą czynność. Wykrycie błędu jest trudne, ponieważ większość ludzi nie potrafi powiedzieć, co oznacza kod maszynowy, patrząc na niego. Przykład tego, jak wygląda kod maszynowy:

05 2A 00

Ten szesnastkowy kod maszynowy mówi procesorowi komputera x86, aby dodał 42 do akumulatora. Jest on bardzo trudny do odczytania i zrozumienia dla osoby, która zna kod maszynowy.

Używanie języka asemblera

W języku asemblera każda instrukcja może być zapisana jako krótkie słowo, zwane mnemonikiem, po którym następują inne rzeczy, takie jak liczby lub inne krótkie słowa. Mnemonik jest używany po to, aby programista nie musiał pamiętać dokładnych liczb w kodzie maszynowym potrzebnych do powiedzenia komputerowi, aby coś zrobił. Przykłady mnemoników w języku asemblera to add, który dodaje dane, i mov, który przenosi dane z jednego miejsca w drugie. Ponieważ "mnemonik" jest słowem mało popularnym, zamiast niego używa się czasem, często niepoprawnie, wyrażenia typu instrukcja lub po prostu instrukcja. Słowa i liczby występujące po pierwszym słowie dają więcej informacji o tym, co należy zrobić. Na przykład, rzeczy następujące po dodawaniu mogą oznaczać, jakie dwie rzeczy należy dodać razem, a rzeczy następujące po przeniesieniu mówią, co należy przenieść i gdzie to umieścić.

Na przykład, kod maszynowy z poprzedniej sekcji (05 2A 00) można zapisać w asemblerze jako:

 dodać ax,42

Język asemblera pozwala również programistom na łatwiejsze pisanie rzeczywistych danych, z których korzysta program. Większość języków asemblera posiada wsparcie dla łatwego tworzenia liczb i tekstu. W kodzie maszynowym każdy inny typ liczby, jak dodatni, ujemny czy dziesiętny, musiałby być ręcznie konwertowany na binarny, a tekst musiałby być definiowany po jednej literze na raz, jako liczby.

Język asemblera zapewnia to, co jest nazywane abstrakcją kodu maszynowego. Używając asemblera, programiści nie muszą znać szczegółów, co liczby oznaczają dla komputera, asembler zajmuje się tym zamiast tego. Język asemblera w rzeczywistości nadal pozwala programiście używać wszystkich funkcji procesora, które mógłby wykorzystać przy użyciu kodu maszynowego. W tym sensie, język asemblera ma bardzo dobrą, rzadką cechę: ma taką samą zdolność wyrażania rzeczy, jak rzecz, którą abstrahuje (kod maszynowy), będąc jednocześnie znacznie łatwiejszym w użyciu. Z tego powodu, kod maszynowy prawie nigdy nie jest używany jako język programowania.

Dezasemblacja i debugowanie

Kiedy programy są gotowe, zostały już przekształcone w kod maszynowy, tak że procesor może je faktycznie uruchomić. Czasami jednak, jeśli w programie jest błąd, programiści chcą być w stanie stwierdzić, co robi każda część kodu maszynowego. Dezasemblery są programami, które pomagają programistom to zrobić, przekształcając kod maszynowy programu z powrotem w język asemblera, który jest znacznie łatwiejszy do zrozumienia. Dezasemblery, które zamieniają kod maszynowy na język asemblera, działają odwrotnie niż asemblery, które zamieniają język asemblera na kod maszynowy.

Organizacja komputera

Zrozumienie tego, jak komputery są zorganizowane, jak wydają się działać na bardzo niskim poziomie, jest potrzebne do zrozumienia, jak działa program w języku asemblera. Na najbardziej uproszczonym poziomie, komputery mają trzy główne części:

  1. pamięć główna lub RAM, w której przechowywane są dane i instrukcje,
  2. procesor, który przetwarza dane poprzez wykonanie instrukcji, oraz
  3. wejście i wyjście (czasami skracane do I/O), które pozwalają komputerowi komunikować się ze światem zewnętrznym i przechowywać dane poza pamięcią główną, aby można je było później odzyskać.

Pamięć główna

W większości komputerów pamięć jest podzielona na bajty. Każdy bajt zawiera 8 bitów. Każdy bajt w pamięci ma również adres, który jest liczbą, która mówi, gdzie bajt jest w pamięci. Pierwszy bajt w pamięci ma adres 0, następny ma adres 1, i tak dalej. Podział pamięci na bajty sprawia, że jest ona adresowalna bajtowo, ponieważ każdy bajt otrzymuje unikalny adres. Adresy pamięci bajtowych nie mogą być używane do odnoszenia się do pojedynczego bitu bajtu. Bajt jest najmniejszym fragmentem pamięci, który może być adresowany.

Mimo że adres odnosi się do konkretnego bajtu w pamięci, procesory pozwalają na użycie kilku bajtów pamięci w rzędzie. Najczęstszym zastosowaniem tej funkcji jest użycie 2 lub 4 bajtów w rzędzie do reprezentowania liczby, zwykle całkowitej. Pojedyncze bajty są czasami również używane do reprezentowania liczb całkowitych, ale ponieważ mają one tylko 8 bitów długości, mogą pomieścić tylko 2 8lub 256 różnych możliwych wartości. Użycie 2 lub 4 bajtów w rzędzie zwiększa liczbę różnych możliwych wartości do odpowiednio 2 16, 65536 lub 2 32, 4294967296.

Kiedy program używa bajtu lub pewnej liczby bajtów w rzędzie do reprezentowania czegoś takiego jak litera, liczba lub cokolwiek innego, te bajty nazywane są obiektem, ponieważ wszystkie są częścią tej samej rzeczy. Nawet jeśli wszystkie obiekty są przechowywane w identycznych bajtach pamięci, są one traktowane tak, jakby miały "typ", który mówi, jak bajty powinny być rozumiane: albo jako liczba całkowita, albo znak, albo jakiś inny typ (jak wartość niecałkowita). O kodzie maszynowym można również myśleć jako o typie, który jest interpretowany jako instrukcje. Pojęcie typu jest bardzo, bardzo ważne, ponieważ definiuje, jakie rzeczy można i nie można zrobić z obiektem i jak interpretować bajty obiektu. Na przykład, nie jest ważne przechowywanie liczby ujemnej w obiekcie liczby dodatniej i nie jest ważne przechowywanie ułamka w liczbie całkowitej.

Adres, który wskazuje (jest adresem) obiektu wielobajtowego jest adresem do pierwszego bajtu tego obiektu - bajtu, który ma najniższy adres. Na marginesie, jedną ważną rzeczą, którą należy zauważyć jest to, że nie można określić typu obiektu - lub nawet jego rozmiaru - na podstawie jego adresu. W rzeczywistości, nie można nawet powiedzieć, jakiego typu jest obiekt, patrząc na niego. Program w języku asemblera musi śledzić, które adresy pamięci przechowują jakie obiekty i jak duże są te obiekty. Program, który to robi, jest bezpieczny dla typu, ponieważ robi tylko rzeczy z obiektami, które są bezpieczne dla ich typu. Program, który tego nie robi, prawdopodobnie nie będzie działał poprawnie. Zauważ, że większość programów w rzeczywistości nie przechowuje jawnie, jaki jest typ obiektu, po prostu dostęp do obiektów jest spójny - ten sam obiekt jest zawsze traktowany jako ten sam typ.

Procesor

Procesor uruchamia (wykonuje) instrukcje, które są przechowywane jako kod maszynowy w pamięci głównej. Oprócz możliwości dostępu do pamięci w celu przechowywania danych, większość procesorów posiada kilka małych, szybkich, o stałym rozmiarze przestrzeni do przechowywania obiektów, z którymi aktualnie pracuje. Przestrzenie te nazywane są rejestrami. Procesory zazwyczaj wykonują trzy typy instrukcji, chociaż niektóre instrukcje mogą być kombinacją tych typów. Poniżej przedstawiono kilka przykładów każdego typu w języku asemblera x86.

Instrukcje, które odczytują lub zapisują pamięć

Poniższa instrukcja języka asemblera x86 odczytuje (ładuje) 2-bajtowy obiekt z bajtu o adresie 4096 (0x1000 w systemie szesnastkowym) do 16-bitowego rejestru o nazwie 'ax':

        mov ax, [1000h]

W tym języku asemblera, nawiasy kwadratowe wokół liczby (lub nazwy rejestru) oznaczają, że liczba ta powinna być użyta jako adres do danych, które powinny być użyte. Użycie adresu do wskazania danych nazywane jest indirekcją. W następnym przykładzie, bez nawiasów kwadratowych, inny rejestr, bx, faktycznie otrzymuje wartość 20, która jest do niego załadowana.

        mov bx, 20

Ponieważ nie zostało użyte żadne pośrednictwo, do rejestru została wpisana sama wartość rzeczywista.

Jeśli operandy (rzeczy, które następują po mnemoniku), pojawiają się w odwrotnej kolejności, instrukcja, która ładuje coś z pamięci, zamiast tego zapisuje to do pamięci:

        mov [1000h], ax

Tutaj pamięć pod adresem 1000h otrzyma wartość ax. Jeśli ten przykład zostanie wykonany zaraz po poprzednim, to 2 bajty pod adresami 1000h i 1001h będą 2-bajtową liczbą całkowitą o wartości 20.

Instrukcje, które wykonują operacje matematyczne lub logiczne

Niektóre instrukcje robią takie rzeczy jak odejmowanie lub operacje logiczne jak nie:

Przykład kodu maszynowego wcześniej w tym artykule byłby tym w języku asemblera:

        dodać ax, 42

Tutaj 42 i ax są sumowane, a wynik jest przechowywany z powrotem w ax. W asemblerze x86 możliwe jest również połączenie dostępu do pamięci i operacji matematycznej w ten sposób:

        add ax, [1000h]

Instrukcja ta dodaje do ax wartość 2-bajtowej liczby całkowitej przechowywanej pod adresem 1000h i zapisuje odpowiedź w ax.

        lub ax, bx

Instrukcja ta oblicza or zawartości rejestrów ax i bx i zapisuje wynik z powrotem do ax.

Instrukcje, które decydują o tym, jaka będzie następna instrukcja

Zazwyczaj instrukcje są wykonywane w kolejności, w jakiej pojawiają się w pamięci, czyli w kolejności, w jakiej są wpisane w kodzie asemblera. Procesor po prostu wykonuje je jedna po drugiej. Jednak, aby procesory mogły wykonywać skomplikowane rzeczy, muszą wykonywać różne instrukcje w zależności od tego, jakie dane zostały im przekazane. Zdolność procesorów do wykonywania różnych instrukcji w zależności od wyniku czegoś nazywa się rozgałęzianiem. Instrukcje, które decydują o tym, jaka powinna być następna instrukcja nazywamy instrukcjami rozgałęzienia.

W tym przykładzie, załóżmy, że ktoś chce obliczyć ilość farby, której będzie potrzebował do pomalowania kwadratu o pewnej długości boku. Jednak ze względu na ekonomię skali sklep z farbami nie sprzeda im mniej niż ilość farby potrzebnej do pomalowania kwadratu o wymiarach 100 x 100.

Aby dowiedzieć się, ile farby będą musieli zdobyć w oparciu o długość kwadratu, który chcą pomalować, wymyślają ten zestaw kroków:

  • odjąć 100 od długości boku
  • jeśli odpowiedź jest mniejsza od zera, ustaw długość boku na 100
  • pomnożyć długość boku przez siebie

Algorytm ten można wyrazić w następującym kodzie, gdzie ax jest długością boku.

        mov bx, ax     sub bx, 100    jge continue   mov ax, 100 continue: mul ax

Ten przykład wprowadza kilka nowych rzeczy, ale pierwsze dwie instrukcje są znajome. Kopiują one wartość ax do bx, a następnie odejmują 100 od bx.

Jedną z nowych rzeczy w tym przykładzie jest etykieta, pojęcie występujące w językach asemblerowych w ogóle. Etykiety mogą być czymkolwiek programista chce (chyba, że jest to nazwa instrukcji, co mogłoby zmylić asembler). W tym przykładzie, etykietą jest 'continue'. Jest ona interpretowana przez asembler jako adres instrukcji. W tym przypadku jest to adres mult ax.

Kolejną nową koncepcją są flagi. W procesorach x86 wiele instrukcji ustawia "flagi" w procesorze, które mogą być użyte przez następną instrukcję do podjęcia decyzji, co zrobić. W tym przypadku, jeśli bx było mniejsze niż 100, sub ustawi flagę, która mówi, że wynik był mniejszy niż zero.

Następną instrukcją jest jge, która jest skrótem od 'Jump if Greater than or Equal to'. Jest to instrukcja rozgałęzienia. Jeśli flagi w procesorze określają, że wynik był większy lub równy zero, to zamiast przejść do następnej instrukcji, procesor skoczy do instrukcji z etykietą continue, czyli mul ax.

Ten przykład działa dobrze, ale nie jest tym, co napisałaby większość programistów. Instrukcja odejmowania ustawia flagę poprawnie, ale zmienia również wartość, na której operuje, co wymagało skopiowania ax do bx. Większość języków asemblerowych pozwala na instrukcje porównania, które nie zmieniają żadnego z przekazywanych argumentów, ale nadal ustawiają flagi poprawnie i asembler x86 nie jest tu wyjątkiem.

        cmp ax, 100    jge continue   mov ax, 100 continue: mul ax

Teraz, zamiast odejmować 100 od ax, sprawdzać czy ta liczba jest mniejsza od zera i przypisywać ją z powrotem do ax, ax pozostaje niezmieniona. Flagi są nadal ustawiane w ten sam sposób, a skok jest wykonywany w tych samych sytuacjach.

Wejście i wyjście

Podczas gdy wejście i wyjście są fundamentalną częścią obliczeń, nie ma jednego sposobu, w jaki są one wykonywane w języku asemblera. Dzieje się tak dlatego, że sposób działania I/O zależy od konfiguracji komputera i systemu operacyjnego, a nie tylko od rodzaju procesora. W sekcji przykładów przykład Hello World używa wywołań systemu operacyjnego MS-DOS, a przykład po nim używa wywołań systemu BIOS.

Możliwe jest wykonywanie operacji wejścia/wyjścia w języku asemblera. W rzeczy samej, język asemblera może wyrazić wszystko, co komputer jest w stanie zrobić. Jednakże, nawet jeśli istnieją instrukcje dodawania i rozgałęziania w języku asemblera, które zawsze będą robić to samo, nie ma instrukcji w języku asemblera, które zawsze wykonują operacje wejścia/wyjścia.

Ważną rzeczą, którą należy zauważyć jest to, że sposób działania I/O nie jest częścią żadnego języka asemblera, ponieważ nie jest częścią działania procesora.

Języki asemblerowe i przenośność

Nawet jeśli język asemblera nie jest bezpośrednio uruchamiany przez procesor - jest nim kod maszynowy - to i tak ma z nim wiele wspólnego. Każda rodzina procesorów obsługuje różne funkcje, instrukcje, zasady dotyczące tego, co instrukcje mogą zrobić, oraz zasady dotyczące tego, jakie kombinacje instrukcji są dozwolone w danym miejscu. Z tego powodu różne typy procesorów wciąż potrzebują różnych języków asemblera.

Ponieważ każda wersja języka asemblera jest związana z rodziną procesorów, brakuje mu czegoś, co nazywa się przenośnością. Coś, co jest przenośne, może być łatwo przenoszone z jednego typu komputera na inny. Podczas gdy inne rodzaje języków programowania są przenośne, język asemblera, ogólnie rzecz biorąc, nie jest.

Język asemblera i języki wysokiego poziomu

Chociaż język asemblera pozwala w prosty sposób wykorzystać wszystkie funkcje procesora, nie jest on używany we współczesnych projektach programistycznych z kilku powodów:

  • Wyrażenie prostego programu w asemblerze wymaga wiele wysiłku.
  • Chociaż nie jest tak podatny na błędy jak kod maszynowy, język asemblera nadal oferuje bardzo słabą ochronę przed błędami. Prawie wszystkie języki asemblacji nie wymuszają bezpieczeństwa typów.
  • Język asemblera nie promuje dobrych praktyk programistycznych, takich jak modularność.
  • Podczas gdy każda pojedyncza instrukcja języka asemblera jest łatwa do zrozumienia, trudno jest powiedzieć, jakie były intencje programisty, który ją napisał. W rzeczywistości język asemblera programu jest tak trudny do zrozumienia, że firmy nie przejmują się tym, że ludzie demontują (uzyskują język asemblera) ich programy.

W wyniku tych wad, języki wysokiego poziomu, takie jak Pascal, C i C++, są używane w większości projektów. Pozwalają one programistom wyrażać swoje pomysły w sposób bardziej bezpośredni, zamiast martwić się o mówienie procesorowi, co ma robić na każdym kroku. Są one nazywane wysokopoziomowymi, ponieważ idee, które programista może wyrazić w tej samej ilości kodu, są bardziej skomplikowane.

Programiści piszący kod w skompilowanych językach wysokiego poziomu używają programu zwanego kompilatorem, aby przekształcić swój kod na język asemblera. Kompilatory są znacznie trudniejsze do napisania niż asemblery. Ponadto, języki wysokiego poziomu nie zawsze pozwalają programistom na wykorzystanie wszystkich funkcji procesora. Dzieje się tak dlatego, że języki wysokiego poziomu są zaprojektowane do obsługi wszystkich rodzin procesorów. W przeciwieństwie do języków asemblerowych, które obsługują tylko jeden typ procesora, języki wysokiego poziomu są przenośne.

Mimo że kompilatory są bardziej skomplikowane niż asemblery, dekady tworzenia i badania kompilatorów sprawiły, że są one bardzo dobre. Obecnie nie ma już zbyt wielu powodów, by używać języka asemblera w większości projektów, ponieważ kompilatory zazwyczaj potrafią zrozumieć, jak wyrażać programy w języku asemblera równie dobrze lub lepiej niż programiści.

Przykładowe programy

Program Hello World napisany w x86 Assembly:

adosseg .model small .stack 100h .data hello_message db 'Hello, World! ',0dh,0ah,'$' .code main proc mov ax,@data mov ds,ax mov ah,9 mov dx,offset hello_message int 21h mov ax,4C00h int 21h main endp end main

Funkcja wypisująca liczbę na ekran za pomocą przerwań BIOS-u napisana w asemblerze NASM x86. Kod modułowy jest możliwy do napisania w asemblerze, ale wymaga to dodatkowego wysiłku. Zauważ, że wszystko co pojawia się po średniku w linii jest komentarzem i jest ignorowane przez asembler. Umieszczanie komentarzy w kodzie w języku asemblera jest bardzo ważne, ponieważ duże programy w języku asemblera są tak trudne do zrozumienia.

; void printn(int liczba, int podstawa); printn:     push    bp      mov     bp, sp push        ax      push    bx      pchać   cx      pchać   dx      pchać   si      mov     si, 0       mov     ax, [bp + 4]   ; liczba       mov     cx, [bp + 6]   ; base gloop:  inc        si             ; długość łańcucha     mov     dx, 0          ; zero dx      div        cx             ; dzielenie przez podstawę     cmp     dx, 10         ; czy to jest ge 10?    jge     num     add     dx, '0'        ; dodaj zero do dx     jmp     anum num:    add     dx, ('A'- 10)  ; wartość heksadecymalna, dodaj 'A' do dx - 10. anum: push        dx             ; umieść dx na stosie.         cmp     ax, 0          ; czy powinniśmy kontynuować?        jne     gloop  mov     bx, 7h         ; dla pętli przerwania:    pop     ax             ; pobierz jego wartość         mov     ah, 0eh         ; dla przerwania       int     10h            ; znak zapisu dec     si              usuń znak      jnz     tloop  pop     si      pop     dx      pop     cx      pop        bx      pop     ax      pop     bp      ret    

Pytania i odpowiedzi

P: Co to jest język asemblerowy?


O: Język asemblerowy to język programowania, za pomocą którego można bezpośrednio powiedzieć komputerowi, co ma robić. Jest to prawie dokładnie taki sam kod maszynowy, jaki rozumie komputer, z tą różnicą, że zamiast liczb używa się w nim słów.

P: Jak komputer rozumie program w asemblerze?


O: Komputer nie może bezpośrednio zrozumieć programu w asemblerze, ale może łatwo zmienić program w kod maszynowy, zastępując słowa programu liczbami, które one oznaczają. Proces ten odbywa się za pomocą asemblera.

P: Czym są instrukcje w języku asemblerowym?


O: Instrukcje w języku asemblerowym to małe zadania, które komputer wykonuje podczas wykonywania programu. Nazywa się je instrukcjami, ponieważ instruują komputer, co ma robić. Część komputera odpowiedzialna za wykonywanie tych instrukcji nazywana jest procesorem.

P: Jakim językiem programowania jest asembler?


O: Język asemblerowy jest językiem programowania niskiego poziomu, co oznacza, że można go używać tylko do wykonywania prostych zadań, które komputer może bezpośrednio zrozumieć. Aby wykonać bardziej złożone zadania, trzeba rozłożyć każde zadanie na poszczególne komponenty i podać instrukcje dla każdego komponentu osobno.

P: Czym to się różni od języków wysokiego poziomu?


O: Języki wysokiego poziomu mogą mieć pojedyncze polecenia, takie jak PRINT "Witaj, świecie!", które każą komputerowi wykonać wszystkie te małe zadania automatycznie, bez konieczności określania ich indywidualnie, jak w przypadku programu asemblerowego. Dzięki temu języki wysokiego poziomu są łatwiejsze do odczytania i zrozumienia przez człowieka niż programy asemblerowe składające się z wielu pojedynczych instrukcji.

P: Dlaczego czytanie programu asemblerowego może być trudne dla człowieka?


O: Ponieważ do wykonania skomplikowanego zadania, takiego jak wydrukowanie czegoś na ekranie lub wykonanie obliczeń na zbiorach danych - rzeczy, które wydają się bardzo podstawowe i proste, gdy są wyrażone w naturalnym języku ludzkim - trzeba podać wiele linii kodu składających się na jedną instrukcję, co sprawia, że ludzie, którzy nie znają wewnętrznej pracy komputerów na tak niskim poziomie, mają trudności ze śledzeniem i interpretacją tego, co się w nich dzieje.

AlegsaOnline.com - 2020 / 2023 - License CC3