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 messages)
|
A low-level programming language is a programming language that provides little or no abstraction from a computer's instruction set architecture, memory or underlying physical hardware; commands or functions in the language are structurally similar to a processor's instructions. These languages provide the programmer with full control over program memory and the underlying machine code instructions. Because of the low level of abstraction (hence the term "low-level") 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 are directly converted to machine code with or without a compiler or interpreter—second-generation programming languages[5][6] depending on programming language. A program written in a low-level language can be made to run very quickly, with a small memory footprint. A program that written with those programming languages often end up becoming architecture dependent or operating system dependent, due to using low level APIs.[1]
Machine code
[edit]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 x86-64 machine code to calculate the nth Fibonacci number, with each line corresponding to one instruction:
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
[edit]Second-generation languages provide one abstraction level on top of the machine code. In the early days of coding on computers like TX-0 and PDP-1, the first thing MIT hackers did was to write assemblers.[7] Assembly language has little semantics or formal specification, being only a mapping of human-readable symbols, including symbolic addresses, to opcodes, addresses, numeric constants, strings and so on. Typically, one machine instruction is represented as one line of assembly code, commonly called a mnemonic.[8] Assemblers produce object files that can link with other object files or be loaded on their own.
Most assemblers provide macros to generate common sequences of instructions.
Example: The same Fibonacci number calculator as above, but in x86-64 assembly language using Intel syntax:
fib:
mov rax, rdi ; put the argument into rax
test rdi, rdi ; is it zero?
je .return_from_fib ; yes - return 0, which is already in rax
cmp rdi, 2 ; is 2 greater than or equal to it?
jbe .return_1_from_fib ; yes (i.e., it's 1 or 2) - return 1
mov rcx, rdi ; no - put it in rcx, for use as a counter
mov rdx, 1 ; the previous number in the sequence, which starts out as 1
mov rsi, 1 ; the number before that, which also starts out as 1
.fib_loop:
lea rax, [rsi + rdx] ; put the sum of the previous two numbers into rax
cmp rcx, 2 ; is the counter 2?
je .return_from_fib ; yes - rax contains the result
mov rsi, rdx ; make the previous number the number before the previous one
dec rcx ; decrement the counter
mov rdx, rax ; make the current number the previous number
jmp .fib_loop ; keep going
.return_1_from_fib:
mov rax, 1 ; 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 64-bit argument from rdi
in accordance to the System V application binary interface for x86-64 and performs its calculation by manipulating values in the rax
, rcx
, rsi
, and rdi
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 rax
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 rax
. 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 (ABI), 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-64 architecture typically (but not always) use the
rax
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, whereas the assembly language code above will only run on processors using the x86-64 architecture.
C
[edit]C has variously been described as low-level and high-level.[9] Traditionally considered high-level, C’s level of abstraction from the hardware is far lower than many subsequently developed languages, particularly interpreted languages. The direct interface C provides between the programmer and hardware memory allocation and management make C the lowest-level language of the 10 most popular languages currently in use.
C is architecture independent — the same C code may, in most cases, be compiled (by different machine-specific compilers) for use on a wide breadth of machine platforms. In many respects (including directory operations and memory allocation), C provides “an interface to system-dependent objects that is itself relatively system independent”.[10] This feature is considered “high-level” in comparison of platform-specific assembly languages.
Low-level programming in high-level languages
[edit]During the late 1960s and 1970s, high-level languages that included some degree of access to low-level programming functions, such as PL/S, BLISS, BCPL, extended ALGOL and NEWP (for Burroughs large systems/Unisys Clearpath MCP systems), and C, were introduced. One method for this is inline assembly, in which assembly code is embedded in a high-level language that supports this feature. Some of these languages also allow architecture-dependent compiler optimization directives to adjust the way a compiler uses the target processor architecture.
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
[edit]- ^ 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.
- ^ Levy, Stephen (1994). Hackers: Heroes of the Computer Revolution. Penguin Books. p. 32. ISBN 0-14-100051-1.
- ^ "Machine Language/Assembly Language/High Level Language". www.cs.mtsu.edu. Archived from the original on 2024-12-14. Retrieved 2024-04-27.
- ^ Jindal, G. et al., January 2013. "Comparative study of C, Objective C, C++ programming language", International Journal of Advanced Trends in Computer Science and Engineering, 203.
- ^ Kernighan, B., and Ritchie, D., 1988. The C Programming Language, 2nd Edition, 163
- ^ "Extended Asm (Using the GNU Compiler Collection (GCC))". gcc.gnu.org. Retrieved 2024-04-27.
Bibliography
[edit]- Zhirkov, Igor (2017). Low-level programming: C, assembly, and program execution on Intel 64 architecture. California: Apress. ISBN 978-1-4842-2402-1.