**Нізкаўзроўневая мова праграмавання** — гэта мова праграмавання, якая практычна не абстрагуецца ад архітэктуры набору інструкцый камп’ютара: каманды ці функцыі ў моўнай карце структурна падобныя з інструкцыямі працэсара. Звычайна гэта адносіцца альбо да машыннага кода, альбо да мовы асэмблера. З-за нізкай (адсюль і слова) абстракцыі паміж мовай і машыннай мовай мовы нізкага ўзроўню часам называюць «блізкімі да апаратуры». Праграмы, напісаныя на нізкаўзроўневых мовах, як правіла, адносна не партатыўныя, паколькі аптымізаваны пад пэўны тып сістэмнай архітэктуры.

Мовы нізкага ўзроўню могуць пераўтварацца ў машынны код без кампілятара ці інтэрпрэтатара — у мовах праграмавання другога пакалення выкарыстоўваецца прасцейшы працэсар, званы асэмблерам. У выніку код выконваецца непасрэдна на працэсары. Праграма, напісаная на мове нізкага ўзроўню, можа выконвацца вельмі хутка і займаць мала месца ў памяці. Аналагічная праграма на мове высокага ўзроўню можа быць менш эфектыўнай і займаць больш памяці. Мовы нізкага ўзроўню простыя, але лічацца складанымі ў выкарыстанні з прычыны вялікай колькасці тэхнічных дэталяў, якія праграміст павінен памятаць. Для параўнання, мова праграмавання высокага ўзроўня ізалюе семантыку выканання архітэктуры камп’ютара ад спецыфікацыі праграмы, што спрашчае распрацоўку.

Машынны код
-----------

 Пярэдняя панэль мінікамп’ютара PDP-8/E. Шэраг пераключальнікаў унізе можа быць скарыстаны для пераключэння ў праграме на машыннай мове.
Асноўны артыкул: **Машынны код**
Машынны код — гэта адзіная мова, якую камп’ютар можа апрацоўваць наўпрост, без папярэдняга пераўтварання. У цяперашні час праграмісты практычна ніколі не пішуць праграмы непасрэдна на машынным кодзе, паколькі гэта патрабуе ўвагі да шматлікіх дэталяў, якія мова высокага ўзроўню апрацоўвае аўтаматычна. Акрамя таго, ён патрабуе запамінання ці пошуку лічбавых кодаў для кожнай інструкцыі і вельмі складаны для мадыфікацыі.

Сапраўдны машынны код уяўляе сабой паток неапрацаваных, як правіла, бінарных даных. Праграміст машыннага кода звычайна праграмуе інструкцыі і даныя ў больш чытэльнай форме, напрыклад у дзесятковай, васьмярковай ці шаснаццатковай сістэме, якія перакладаюцца ва ўнутраны фармат праграмай, званай загрузчыкам, ці ўводзяцца ў памяць камп’ютара з пярэдней панэлі.

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

Прыклад функцыі ў шаснаццатковым уяўленні 32-разраднага машыннага кода x86 для вылічэння n-ай лічбы Фібаначы:

```
8B542408 83FA0077 06B80000 0000C383
FA027706 B8010000 00C353BB 01000000
B9010000 008D0419 83FA0376 078BD989
C14AEBF1 5BC3
```

Мова асэмблера
--------------

Асноўны артыкул: **Мова асэмблера**
Мовы другога пакалення забяспечваюць адзін узровень абстракцыі па-над машынным кодам. На світанку кадавання на такіх камп’ютарах, як TX-0 і PDP-1, першае, што рабілі хакеры з Масачусецкага тэхналагічнага інстытута — гэта пісалі асэмблеры. Мова асэмблера мае мала семантыкі і фармальных спецыфікацый, з’яўляючыся адлюстраваннем чалавекачытальных сімвалаў, уключаючы сімвальныя адрасы, кады аперацый, адрасы, лічбавыя канстанты, радкі і г.д. Як правіла, адна машынная інструкцыя прадстаўлена адным радком асэмблернага кода. Асэмблеры ствараюць аб’ектныя файлы, якія могуць звязвацца з іншымі аб’ектнымі файламі ці загружацца самастойна.

Большасць асэмблераў прадстаўляюць макрасы для генерацыі агульных паслядоўнасцяў інструкцый.

Прыклад: Той жа калькулятар лічбы Фібаначы, што і вышэй, але на мове асэмблера x86-64 з выкарыстаннем сінтаксісу AT&T:

```
\_fib:
 movl $1, %eax
 xorl %ebx, %ebx
.fib\_loop:
 cmpl $1, %edi
 jbe .fib\_done
 movl %eax, %ecx
 addl %ebx, %eax
 movl %ecx, %ebx
 subl $1, %edi
 jmp .fib\_loop
.fib\_done:
 ret

```

У гэтым прыкладзе кода рэгістры працэсара x86-64 названы і маніпулююцца непасрэдна. Функцыя загружае свае ўваходныя даныя з *%edi* ў адпаведнасці з System V ABI і выконвае разлік, маніпулюючы значэннямі ў рэгістрах **EAX**, **EBX** і **ECX**, пакуль не скончыцца і не вернецца. Звярніце ўвагу, што ў гэтай мове асэмблера няма канцэпцыі вяртання значэння. Калі вынік быў захаваны ў рэгістры **EAX**, каманда **RET** проста перамяшчае апрацоўку кода ў месца, якое захоўваецца ў стэку (звычайна інструкцыя адразу пасля той, якая выклікала гэту функцыю), і гэта залежыць ад аўтара кода выкліку. ведаць, што гэтая функцыя захоўвае свой вынік у **EAX** і можа атрымаць яго адтуль. Мова асэмблера x86-64 не навязвае стандарт для вяртання значэнняў з функцыі (і фактычна не мае паняцця функцыі); код выкліку павінен праверыць стан пасля вяртання працэдуры, калі яму трэба атрымаць значэнне.

Параўнайце з той жа функцыяй на Cі:

```
unsigned int fib(unsigned int n) {
 if (!n)
 return 0;
 else if (n <= 2)
 return 1;
 else {
 unsigned int a, c;
 for (a = c = 1; ; --n) {
 c += a;
 if (n <= 2) return c;
 a = c - a;
 }
 }
}

```

Нізкаўзроўневае праграмаванне ў мовах высокага ўзроўню
------------------------------------------------------

У канцы 1960-х гадоў мовы высокага ўзроўню, такія як PL/S, BLISS, BCPL, пашыраны ALGOL (для вялікіх сістэм Burroughs) і C, прадугледжвалі пэўную ступень доступу да функцый нізкаўзроўневага праграмавання. Адным з метадаў такога доступу з’яўляецца ўбудаваны асэмблер, пры якім асэмблерны код убудоўваецца ў мову высокага ўзроўню, які падтрымлівае гэтую магчымасць. Некаторыя з гэтых моў таксама дазваляюць выкарыстоўваць архітэктурна-залежныя дырэктывы аптымізацыі кампілятара, каб наладзіць кампілятар на выкарыстанне архітэктуры мэтавага працэсара.

Зноскі
------

1. 1 2 3 4  3.1: Structure of low-level programs (англ.). *Workforce LibreTexts* (5 сакавіка 2021). Праверана 3 красавіка 2023.