stringtranslate.com

Низкоуровневый язык программирования

Язык программирования низкого уровня — это язык программирования , который обеспечивает небольшую или никакую абстракцию от архитектуры набора инструкций компьютера ; команды или функции в языке структурно похожи на инструкции процессора. Как правило, это относится либо к машинному коду , либо к языку ассемблера . Из-за низкой (отсюда и само слово) абстракции между языком и машинным языком, языки низкого уровня иногда описываются как «близкие к оборудованию». Программы, написанные на языках низкого уровня, как правило, относительно непереносимы , поскольку оптимизированы для определенного типа архитектуры системы. [1] [2] [3] [4]

Низкоуровневые языки могут преобразовываться в машинный код без компилятора или интерпретатораязыки программирования второго поколения [5] [6] используют более простой процессор, называемый ассемблером , — и полученный код выполняется непосредственно на процессоре. Программа, написанная на низкоуровневом языке, может работать очень быстро, с небольшим объемом памяти . Эквивалентная программа на высокоуровневом языке может быть менее эффективной и использовать больше памяти. Низкоуровневые языки просты, но считаются сложными в использовании из-за многочисленных технических деталей, которые программист должен помнить. Для сравнения, высокоуровневый язык программирования изолирует семантику выполнения компьютерной архитектуры от спецификации программы, что упрощает разработку. [1]

Машинный код

Передняя панель миникомпьютера PDP-8/E. Ряд переключателей внизу может использоваться для переключения в программе на машинном языке.

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

Машинный код обычно хранится в памяти в виде двоичных данных. Программисты почти никогда не пишут программы непосредственно в машинном коде; вместо этого они пишут код на языке ассемблера или языках программирования более высокого уровня. [1]

Хотя на машинных языках написано мало программ, программисты часто осваивают их, работая с дампами ядра или выполняя отладку с передней панели.

Пример функции в шестнадцатеричном представлении машинного кода x86-64 для вычисления n-го числа Фибоначчи , где каждая строка соответствует одной инструкции:

89 ф885 и далее74 2683 и далее 0276 1с89 ф9ба 01 00 00 00быть 01 00 00 008д 04 1683 ф9 0274 0д89 д6фф с989 с2еб ф0б8 01 00 00с3

язык ассемблера

Языки второго поколения предоставляют один уровень абстракции поверх машинного кода. На заре кодирования на компьютерах, таких как TX-0 и PDP-1 , первым делом хакеры MIT написали ассемблеры. [7] Язык ассемблера имеет мало семантики или формальной спецификации, являясь лишь отображением понятных человеку символов, включая символические адреса, на коды операций , адреса , числовые константы, строки и так далее. Обычно одна машинная инструкция представлена ​​в виде одной строки ассемблерного кода, обычно называемого мнемоникой. [8] Ассемблеры создают объектные файлы , которые могут связываться с другими объектными файлами или загружаться самостоятельно.

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

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

fib: movl %edi , %eax ; поместить аргумент в %eax testl %edi , %edi ; равен ли он нулю? je .return_from_fib ; yes - вернуть 0, который уже есть в %eax cmpl $2 , %edi ; 2 больше или равно ему? jbe .return_1_from_fib ; yes (т.е. это 1 или 2) - вернуть 1 movl %edi , %ecx ; no - поместить его в %ecx для использования в качестве счетчика movl $1 , %edx ; предыдущее число в последовательности, которое начинается с 1 movl $1 , %esi ; число перед ним, которое также начинается с 1 .fib_loop: leal ( %rsi , %rdx ), %eax ; поместить сумму двух предыдущих чисел в %eax cmpl $2 , %ecx ; счетчик равен 2? je .return_from_fib ; да - %eax содержит результат movl %edx , %esi ; сделать предыдущее число числом перед предыдущим decl %ecx ; уменьшить счетчик movl %eax , %edx ; сделать текущее число предыдущим числом jmp .fib_loop ; продолжить .return_1_from_fib: movl $1 , %eax ; установить возвращаемое значение равным 1 .return_from_fib: ret ; return                                                             

В этом примере кода регистры процессора x86-64 названы и управляются напрямую. Функция загружает свой 32-битный аргумент из %ediв соответствии с двоичным интерфейсом приложения System V для x86-64 и выполняет свои вычисления, манипулируя значениями в регистрах %eax, %ecx, %esi, и %edi, пока не завершится и не вернется. Обратите внимание, что в этом языке ассемблера нет концепции возврата значения. Результат был сохранен в регистре %eax, снова в соответствии с двоичным интерфейсом приложения System V, retинструкция просто удаляет верхний 64-битный элемент в стеке и вызывает выборку следующей инструкции из этого места (эта инструкция обычно является инструкцией, следующей сразу после той, которая вызвала эту функцию), при этом результат функции сохраняется в %eax. Язык ассемблера x86-64 не устанавливает стандарт для передачи значений в функцию или возврата значений из функции (и фактически не имеет концепции функции); они определяются двоичным интерфейсом приложения , таким как System V ABI для конкретного набора инструкций.

Сравните это с той же функцией на языке C :

беззнаковое целое число fib ( беззнаковое целое число n ) { если ( ! n ) { вернуть 0 ; } иначе если ( n <= 2 ) { вернуть 1 ; } иначе { беззнаковое целое число f_nminus2 , f_nminus1 , f_n ; для ( f_nminus2 = f_nminus1 = 1 , f_n = 0 ; ; -- n ) { f_n = f_nminus2 + f_nminus1 ; если ( n <= 2 ) { вернуть f_n ; } f_nminus2 = f_nminus1 ; } } }                                                         

Этот код по структуре похож на пример на языке ассемблера, но есть существенные различия с точки зрения абстракции:

Эти абстракции делают код C компилируемым без изменений на любой архитектуре, для которой написан компилятор C. Код языка ассемблера x86 специфичен для архитектуры x86-64 и двоичного интерфейса приложения System V для этой архитектуры.

Низкоуровневое программирование на языках высокого уровня

В конце 1960-х и 1970-х годах были введены высокоуровневые языки , которые включали некоторую степень доступа к низкоуровневым функциям программирования, таким как PL/S , BLISS , BCPL , расширенный ALGOL и NEWP (для больших систем Burroughs /систем Unisys Clearpath MCP) и C. Одним из методов для этого является встроенный ассемблер , в котором ассемблерный код встроен в высокоуровневый язык, поддерживающий эту функцию. Некоторые из этих языков также позволяют директивам оптимизации компилятора, зависящим от архитектуры , настраивать способ, которым компилятор использует целевую архитектуру процессора.

Хотя такой язык, как C, является высокоуровневым, он не полностью абстрагируется от возможности управления памятью, как другие языки. [9] В таком высокоуровневом языке, как Python, программист не может напрямую обращаться к памяти из-за абстракций между интерпретатором и машиной. Таким образом, C может обеспечить больший контроль, предоставляя инструменты управления памятью через такие инструменты, как выделение памяти (malloc). [10]

Кроме того, как упоминалось выше, следующий блок C взят из компилятора GNU и демонстрирует встроенную ассемблерную способность C. Согласно документации GCC, это простой код копирования и добавления. Этот код отображает взаимодействие между языком высокого уровня, таким как C, и его средне-низкоуровневым аналогом Assembly. Хотя это может и не сделать C изначально низкоуровневым языком, эти возможности выражают взаимодействие более прямым способом. [11]

int src = 1 ; int dst ;     asm ( "mov %1, %0 \n\t " "add $1, %0" : "=r" ( dst ) : "r" ( src ));         printf ( "%d \n " , dst ); 

Ссылки

  1. ^ abc "3.1: Структура низкоуровневых программ". Workforce LibreTexts . 2021-03-05 . Получено 2023-04-03 .
  2. ^ «Что такое язык низкого уровня?». GeeksforGeeks . 2023-11-19 . Получено 2024-04-27 .
  3. ^ "Язык низкого уровня? Что вам нужно знать | Lenovo US". www.lenovo.com . Получено 2024-04-27 .
  4. ^ "Языки низкого уровня - Классификация языков программирования и трансляторов - AQA - GCSE Computer Science Revision - AQA". BBC Bitesize . Получено 27.04.2024 .
  5. ^ "Генерация языков программирования". GeeksforGeeks . 2017-10-22 . Получено 2024-04-27 .
  6. ^ «Что такое Generation Languages?». www.computerhope.com . Получено 2024-04-27 .
  7. ^ Леви, Стивен (1994). Хакеры: Герои компьютерной революции . Penguin Books. стр. 32. ISBN 0-14-100051-1.
  8. ^ "Машинный язык/Язык ассемблера/Язык высокого уровня". www.cs.mtsu.edu . Получено 2024-04-27 .
  9. ^ Керниган, Брайан В.; Ритчи, Деннис М. (2014). Язык программирования C. Серия программного обеспечения Prentice-Hall (2-е изд., 52-е печатное изд.). Upper Saddle River, NJ: Prentice-Hall PTR. ISBN 978-0-13-110362-7.
  10. ^ "malloc(3) - страница руководства Linux". man7.org . Получено 2024-04-21 .
  11. ^ "Расширенный Asm (использование коллекции компиляторов GNU (GCC))". gcc.gnu.org . Получено 2024-04-27 .