Low-level programming language
![]() | This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these template messages)
|
A low-level programming language is a programming language that provides little or no abstraction from a computer's instruction set architecture—commands or functions in the language map that are structurally similar to processor's instructions. Generally, this refers to either machine code or assembly language. Because of the low (hence the word) abstraction between the language and machine language, low-level languages are sometimes described as being "close to the hardware". Programs written in low-level languages tend to be relatively non-portable, due to being optimized for a certain type of system architecture.[1][2][3][4]
Low-level languages can convert to machine code without a
Machine code
![](http://upload.wikimedia.org/wikipedia/commons/thumb/8/8c/Digital_pdp8-e2.jpg/220px-Digital_pdp8-e2.jpg)
Machine code is the form in which code that can be directly executed is stored on a computer. It consists of machine language instructions, stored in memory, that perform operations such as moving values in and out of memory locations, arithmetic and boolean logic, and testing values and, based on the test, either executing the next instruction in memory or executing an instruction at another location.
Machine code is usually stored in memory as binary data. Programmers almost never write programs directly in machine code; instead, they write code in assembly language or higher-level programming languages.[1]
Although few programs are written in machine languages, programmers often become adept at reading it through working with core dumps or debugging from the front panel.
Example of a function in hexadecimal representation of
89 f8 85 ff 74 26 83 ff 02 76 1c 89 f9 ba 01 00 00 00 be 01 00 00 00 8d 04 16 83 f9 02 74 0d 89 d6 ff c9 89 c2 eb f0 b8 01 00 00 c3
Assembly language
Second-generation languages provide one abstraction level on top of the machine code. In the early days of coding on computers like
Assembly language has littleMost assemblers provide macros to generate common sequences of instructions.
Example: The same
fib:
movl %edi, %eax ; put the argument into %eax
testl %edi, %edi ; is it zero?
je .return_from_fib ; yes - return 0, which is already in %eax
cmpl $2, %edi ; is 2 greater than or equal to it?
jbe .return_1_from_fib ; yes (i.e., it's 1 or 2) - return 1
movl %edi, %ecx ; no - put it in %ecx, for use as a counter
movl $1, %edx ; the previous number in the sequence, which starts out as 1
movl $1, %esi ; the number before that, which also starts out as 1
.fib_loop:
leal (%rsi,%rdx), %eax ; put the sum of the previous two numbers into %eax
cmpl $2, %ecx ; is the counter 2?
je .return_from_fib ; yes - %eax contains the result
movl %edx, %esi ; make the previous number the number before the previous one
decl %ecx ; decrement the counter
movl %eax, %edx ; make the current number the previous number
jmp .fib_loop ; keep going
.return_1_from_fib:
movl $1, %eax ; set the return value to 1
.return_from_fib:
ret ; return
In this code example, the registers of the x86-64 processor are named and manipulated directly. The function loads its 32-bit argument from %edi
in accordance to the System V application binary interface for x86-64 and performs its calculation by manipulating values in the %eax
, %ecx
, %esi
, and %edi
registers until it has finished and returns. Note that in this assembly language, there is no concept of returning a value. The result having been stored in the %eax
register, again in accordance with System V application binary interface, the ret
instruction simply removes the top 64-bit element on the stack and causes the next instruction to be fetched from that location (that instruction is usually the instruction immediately after the one that called this function), with the result of the function being stored in %eax
. x86-64 assembly language imposes no standard for passing values to a function or returning values from a function (and in fact, has no concept of a function); those are defined by an application binary interface, such as the System V ABI for a particular instruction set.
Compare this with the same function in C:
unsigned int fib(unsigned int n)
{
if (!n)
{
return 0;
}
else if (n <= 2)
{
return 1;
}
else
{
unsigned int f_nminus2, f_nminus1, f_n;
for (f_nminus2 = f_nminus1 = 1, f_n = 0; ; --n)
{
f_n = f_nminus2 + f_nminus1;
if (n <= 2)
{
return f_n;
}
f_nminus2 = f_nminus1;
}
}
}
This code is similar in structure to the assembly language example but there are significant differences in terms of abstraction:
- The input (parameter
n
) is an abstraction that does not specify any storage location on the hardware. In practice, the C compiler follows one of many possible calling conventions to determine a storage location for the input. - The local variables
f_nminus2
,f_nminus1
, andf_n
are abstractions that do not specify any specific storage location on the hardware. The C compiler decides how to actually store them for the target architecture. - The return function specifies the value to return, but does not dictate how it is returned. The C compiler for any specific architecture implements a standard mechanism for returning the value. Compilers for the x86 architecture typically (but not always) use the
%eax
register to return a value, as in the assembly language example (the author of the assembly language example has chosen to use the System V application binary interface for x86-64 convention but assembly language does not require this).
These abstractions make the C code compilable without modification on any architecture for which a C compiler has been written. The x86 assembly language code is specific to the x86-64 architecture and the System V application binary interface for that architecture.
Low-level programming in high-level languages
During the late 1960s and 1970s,
Although a language like C is high-level, it does not fully abstract away the ability to manage memory like other languages.[9] In a high-level language like Python the programmer cannot directly access memory due to the abstractions between the interpreter and the machine. Thus C can allow more control by exposing memory management tools through tools like memory allocate (malloc).[10]
Furthermore, as referenced above, the following block of C is from the GNU Compiler and shows the inline assembly ability of C. Per the GCC documentation this is a simple copy and addition code. This code displays the interaction between a generally high level language like C and its middle/low level counter part Assembly. Although this may not make C a natively low level language these facilities express the interactions in a more direct way.[11]
int src = 1;
int dst;
asm ("mov %1, %0\n\t"
"add $1, %0"
: "=r" (dst)
: "r" (src));
printf("%d\n", dst);
References
- ^ a b c "3.1: Structure of low-level programs". Workforce LibreTexts. 2021-03-05. Retrieved 2023-04-03.
- ^ "What is a Low Level Language?". GeeksforGeeks. 2023-11-19. Retrieved 2024-04-27.
- ^ "Low Level Language? What You Need to Know | Lenovo US". www.lenovo.com. Retrieved 2024-04-27.
- ^ "Low-level languages - Classifying programming languages and translators - AQA - GCSE Computer Science Revision - AQA". BBC Bitesize. Retrieved 2024-04-27.
- ^ "Generation of Programming Languages". GeeksforGeeks. 2017-10-22. Retrieved 2024-04-27.
- ^ "What is a Generation Languages?". www.computerhope.com. Retrieved 2024-04-27.
- ISBN 0-14-100051-1.
- ^ "Machine Language/Assembly Language/High Level Language". www.cs.mtsu.edu. Retrieved 2024-04-27.
- ISBN 978-0-13-110362-7.
- ^ "malloc(3) - Linux manual page". man7.org. Retrieved 2024-04-21.
- ^ "Extended Asm (Using the GNU Compiler Collection (GCC))". gcc.gnu.org. Retrieved 2024-04-27.