stringtranslate.com

Язык программирования низкого уровня

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

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

Машинный код

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

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

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

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

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

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

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

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

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

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

фиб: movl %edi , %eax ; поместите аргумент в %eax testl %edi , %edi ; это ноль? je .return_from_fib ; да — вернуть 0, который уже есть в %eax cmpl $2 , %edi ; 2 больше или равно ему? jbe .return_1_from_fib ; да (т.е. это 1 или 2) — вернуть 1 movl %edi , %ecx ; нет - поместите его в %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 ; возвращаться                                                             

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

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

unsigned int fib ( unsigned int n ) { if ( ! n ) return 0 ; иначе , если ( n <= 2 ) вернуть 1 ; еще { unsigned int 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 и ESPOL (для больших систем Берроуза ) и C. . Одним из методов для этого является встроенная ассемблерная программа , в которой ассемблерный код встроен в язык высокого уровня, поддерживающий эту функцию. Некоторые из этих языков также позволяют директивам оптимизации компилятора в зависимости от архитектуры регулировать способ использования компилятором архитектуры целевого процессора.

Рекомендации

  1. ^ abcd «3.1: Структура программ низкого уровня». Рабочая сила LibreTexts . 05.03.2021 . Проверено 03 апреля 2023 г.
  2. ^ Леви, Стивен (1994). Хакеры: герои компьютерной революции . Книги о пингвинах. п. 32. ISBN 0-14-100051-1.