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".
Machine code
[edit]
Machine code, classified as a first-generation programming language,[1][2] is data encoded and structured per the instruction set architecture of a CPU. The instructions imply operations such as moving values in and out of memory locations, Boolean logic, arithmetic, comparing values, and flow control (branching and jumping).
Programmers almost never program directly in machine code; instead, they use an assembly language or a higher-level programming language.[3] Although few programs are written in machine languages, some programmers learn to read it through experience with core dumps and debugging.
Assembly language
[edit]An assembly language, classified as a second-generation programming language,[1][2] provides a level of abstraction on top of machine code. A program written in assembly language is non-portable, due to being written and optimized for a particular architecture.[3][4][5][6]
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.[7] 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.
In the early days of coding on computers like TX-0 and PDP-1, the first thing MIT hackers did was to write assemblers.[8]
C programming language
[edit]The C programming language, a third-generation programming language,[1][2] is sometimes classified as high or low depending on what one means by high vs. low level.[9] The syntax of C is inherently higher level than that of an assembly language since an assembly language is syntactically platform dependent whereas the C syntax is platform independent. C does support low-level programming – directly accessing computer hardware – but other languages, sometimes considered higher level than C, also can access hardware directly. With C, developers might need to handle relatively low-level aspects that other languages abstract (provide higher level support for) such as memory management and pointer arithmetic. But, C can encode abstractions that hide details such as hardware access, memory management and pointer arithmetic such that at least part of a C codebase might be as conceptually high-level as if constructed in any other language. Whether C is classified as high or low level language is contended, but it is higher level than assembly languages (especially syntactically) and is lower level than many other languages in some aspects.
Although C is not architecture independent, it can be used to write code that is cross-platform even though doing so can be technically challenging. An aspect of C that facilitates cross-platform development is the C standard library that provides “an interface to system-dependent objects that is itself relatively system independent”.[10]
Comparison
[edit]The following is x86-64 machine code for an algorithm to calculate the nth Fibonacci number; with values in hexadecimal representation and 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
The following is the same algorithm written in x86-64 assembly language using Intel syntax. 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.
fib:
mov rax, rdi ; The argument is stored in rdi, put it into rax
test rdi, rdi ; Is the argument zero?
je .return_from_fib ; Yes - return 0, which is already in rax
cmp rdi, 2 ; No - compare the argument to 2
jbe .return_1_from_fib ; If it is less than or equal to 2, return 1
mov rcx, rdi ; Otherwise, put it in rcx, for use as a counter
mov rdx, 1 ; The first previous number starts out as 1, put it in rdx
mov rsi, 1 ; The second previous number also starts out as 1, put it in rsi
.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 ; No - make the first previous number the second previous number
dec rcx ; Decrement the counter
mov rdx, rax ; Make the current number the first 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
The following is the same algorithm again, but in C. This is similar in structure to the assembly example but there are significant differences in 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 for any architecture that is supported by a C compiler; whereas the assembly code above only runs on processors using the x86-64 architecture.
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;
f_nminus1 = f_n;
}
}
}
Low-level programming in high-level languages
[edit]Some high-level languages, such as PL/S, BLISS, BCPL, extended ALGOL and NEWP, and C, can access lower-level programming languages. One method for doing this is inline assembly, in which assembly code is embedded in the high-level language code. Some of these languages also allow architecture-dependent compiler optimization directives to adjust the way a compiler uses the target processor architecture.
The following block of C code from the GNU C Compiler (GCC) demonstrates its inline assembly feature.[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 "Generation of Programming Languages". GeeksforGeeks. 2017-10-22. Retrieved 2024-04-27.
- ^ a b c "What is a Generation Languages?". www.computerhope.com. Retrieved 2024-04-27.
- ^ a b "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. Archived from the original on 2024-07-24. Retrieved 2024-04-27.
- ^ "Low-level languages - Classifying programming languages and translators - AQA - GCSE Computer Science Revision - AQA". BBC Bitesize. Retrieved 2024-04-27.
- ^ "Machine Language/Assembly Language/High Level Language". www.cs.mtsu.edu. Archived from the original on 2024-12-14. Retrieved 2024-04-27.
- ^ Levy, Stephen (1994). Hackers: Heroes of the Computer Revolution. Penguin Books. p. 32. ISBN 0-14-100051-1.
- ^ Jindal, G.; Khurana, P.; Goel, T. (January 2013). "Comparative study of C, Objective C, C++ programming language". International Journal of Advanced Trends in Computer Science and Engineering. 2 (1): 203.
- ^ Kernighan, B.; Ritchie, D. (1988). The C Programming Language, 2nd Edition. p. 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.