mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-23 07:28:04 +08:00
clean up
This commit is contained in:
parent
5fd2a3e22a
commit
ac47f303e9
2 changed files with 336 additions and 212 deletions
16
README.md
16
README.md
|
|
@ -1,4 +1,4 @@
|
||||||
# A Gentle Introduction to Assembly Language Programming
|
=# A Gentle Introduction to Assembly Language Programming
|
||||||
|
|
||||||
This textbook provides a gentle introduction to assembly language
|
This textbook provides a gentle introduction to assembly language
|
||||||
programming. What makes this introduction "gentle" is that it assumes
|
programming. What makes this introduction "gentle" is that it assumes
|
||||||
|
|
@ -34,11 +34,13 @@ the ARM ISA far more topical. Perhaps even more "cool".
|
||||||
|
|
||||||
## Calling Convention Used In This Book
|
## Calling Convention Used In This Book
|
||||||
|
|
||||||
Assembly language programming is quite closely intertwined with both the
|
Assembly language programming is quite closely dependent upon the
|
||||||
underlying hardware architecture and the host operating system. A
|
underlying hardware architecture. The host operating environment plays
|
||||||
"calling convention" refers to how functions are called and how
|
an outsized role in determining how assembly language programs are
|
||||||
parameters are passed. In this book we will use the ARM LINUX
|
constructed. A "calling convention" refers to how functions are called
|
||||||
conventions. This means:
|
and how parameters are passed.
|
||||||
|
|
||||||
|
In this book we will use the ARM LINUX conventions. This means:
|
||||||
|
|
||||||
* You will need to run a ARM Linux VM on the Macintosh - even on
|
* You will need to run a ARM Linux VM on the Macintosh - even on
|
||||||
ARM-based Macs. Why? Apple. That's why.
|
ARM-based Macs. Why? Apple. That's why.
|
||||||
|
|
@ -56,6 +58,8 @@ V8 are taken to be synonyms for the 64 bit ARM Instruction Set
|
||||||
Architecture (ISA). It is very difficult to find documentation at the
|
Architecture (ISA). It is very difficult to find documentation at the
|
||||||
ARM site because they have *so many versions*.
|
ARM site because they have *so many versions*.
|
||||||
|
|
||||||
|
Within the text we will provide germane links as appropriate.
|
||||||
|
|
||||||
## Section 1 - Bridging from C / C++ to Assembly Language
|
## Section 1 - Bridging from C / C++ to Assembly Language
|
||||||
|
|
||||||
We start by providing what we're calling "bridging" from C and C++ to
|
We start by providing what we're calling "bridging" from C and C++ to
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
We start out with a C++
|
We start out with a C++ program kind-of-like "Hello World" and break it
|
||||||
program kind-of-like "Hello World" and break it down into several versions
|
down into several versions which are closer and closer to a high level
|
||||||
which are closer and closer to a high level assembly language (otherwise
|
assembly language (otherwise known as C). At the last step, we convert
|
||||||
known as C). At the last step, we convert the C into ARM V8 assembly language.
|
the C into ARM V8 assembly language.
|
||||||
|
|
||||||
At every step, we'll completely explain the code and document what has
|
At every step, we'll completely explain the code and document what has
|
||||||
changed from version to version so that little background is assumed.
|
changed from version to version so that little background is assumed.
|
||||||
|
|
@ -17,16 +17,16 @@ of `argv`, that is: the command line arguments specified when the
|
||||||
program is run from the shell (command line).
|
program is run from the shell (command line).
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
#include <iostream> // 1
|
#include <iostream> // 1
|
||||||
// 2
|
// 2
|
||||||
using namespace std; // 3
|
using namespace std; // 3
|
||||||
// 4
|
// 4
|
||||||
int main(int argc, char * argv[]) { // 5
|
int main(int argc, char * argv[]) { // 5
|
||||||
while (*argv) { // 6
|
while (*argv) { // 6
|
||||||
cout << *(argv++) << endl; // 7
|
cout << *(argv++) << endl; // 7
|
||||||
} // 8
|
} // 8
|
||||||
return 0; // 9
|
return 0; // 9
|
||||||
} // 10
|
} // 10
|
||||||
```
|
```
|
||||||
|
|
||||||
[Here](./v1.cpp) is a link to the program without line numbers.
|
[Here](./v1.cpp) is a link to the program without line numbers.
|
||||||
|
|
@ -42,24 +42,34 @@ three plus four
|
||||||
%
|
%
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see in the output, the program printed each of the command line parameters (arguments) in the order in which they were specified. These come to your program stored in an array called
|
As you can see in the output, the program printed each of the command
|
||||||
(by convention) `argv` as the second parameter to `main()`.
|
line parameters (arguments) in the order in which they were specified.
|
||||||
|
These come to your program stored in an array called (by convention)
|
||||||
|
`argv` as the second parameter to `main()`.
|
||||||
|
|
||||||
### Line 1
|
### Line 1
|
||||||
|
|
||||||
`Line 1` makes available the default output stream `cout`. `cout` stands for `c`onsole `out`put. The angle brackets (`<` and `>`) indicate the include file `iostream` comes from a language or system supplied directory as opposed to an include file written by you.
|
`Line 1` makes available the default output stream `cout`. `cout` stands
|
||||||
|
for `c`onsole `out`put. The angle brackets (`<` and `>`) indicate the
|
||||||
|
include file `iostream` comes from a language or system supplied
|
||||||
|
directory as opposed to an include file written by you.
|
||||||
|
|
||||||
For an explanation of what an `include` file is and how it fits into the compilation workflow see [here](https://youtu.be/Iv3psS4n9j8).
|
For an explanation of what an `include` file is and how it fits into the compilation workflow see [here](https://youtu.be/Iv3psS4n9j8).
|
||||||
|
|
||||||
### Line 3
|
### Line 3
|
||||||
|
|
||||||
`Line 3` is a common statement in C++ programs. It allows the use of many standard library features such as `cout` by typing fewer characters. In this case, for example, `line 7` without the `line 3` `using` would read:
|
`Line 3` is a common statement in C++ programs. It allows the use of
|
||||||
|
many standard library features such as `cout` by typing fewer
|
||||||
|
characters. In this case, for example, `line 7` without the `line 3`
|
||||||
|
`using` would read:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
std::cout << *(argv++) << std::endl;
|
std::cout << *(argv++) << std::endl;
|
||||||
```
|
```
|
||||||
|
|
||||||
There are other reasons to specify a `using namespace` and even some reasons *not* to specify a `using namespace`. These however, are not relevant to this discussion.
|
There are other reasons to specify a `using namespace` and even some
|
||||||
|
reasons *not* to specify a `using namespace`. These however, are not
|
||||||
|
relevant to this discussion.
|
||||||
|
|
||||||
### Line 5
|
### Line 5
|
||||||
|
|
||||||
|
|
@ -67,30 +77,34 @@ There are other reasons to specify a `using namespace` and even some reasons *no
|
||||||
programs (and indeed in many non-command line programs), a function
|
programs (and indeed in many non-command line programs), a function
|
||||||
called `main` is necessary.
|
called `main` is necessary.
|
||||||
|
|
||||||
In all respects save one, `main` is an
|
In all respects save one, `main` is an ordinary user-written function.
|
||||||
ordinary user-written function. What makes `main` special is its name
|
|
||||||
and its parameters (typically called `argc` and `argv`). A function named
|
What makes `main` special is its name and its parameters (typically
|
||||||
`main` is special because by default it is the function at which your
|
called `argc` and `argv`). A function named `main` is special because by
|
||||||
code will begin execution.
|
default it is the function at which your code will begin execution.
|
||||||
|
|
||||||
`argc` is an integer argument which specifies the number of *non-null*
|
`argc` is an integer argument which specifies the number of *non-null*
|
||||||
arguments found by following the *pointers* contained in in `argv`. We will
|
arguments found by following the *pointers* contained in the array
|
||||||
explain *non-null* and *pointers* later.
|
`argv`. We will explain *non-null* and *pointers* later.
|
||||||
|
|
||||||
In the case of the execution portrayed above, `argc` would have the value
|
In the case of the execution portrayed above, `argc` would have the
|
||||||
of `4`. `argc` **always** has a value of at least 1. This is because the
|
value of `4`. `argc` **always** has a value of at least 1. This is
|
||||||
first command line argument accessible via `argv` is the *path* to the
|
because the first command line argument accessible via `argv` is the
|
||||||
program being executed. For our purposes, think of the *path* as like the *name* of the program.
|
*path* to the program being executed. For our purposes, think of the
|
||||||
|
*path* as like the *name* of the program.
|
||||||
|
|
||||||
`argv` is declared as a *pointer to one or more pointers to chars*. The
|
`argv` is declared as a *pointer to one or more pointers to chars*. The
|
||||||
concept of a *pointer* is essential to understanding assembly language.
|
concept of a *pointer* is essential to understanding assembly language.
|
||||||
*Pointers* are scary for new programmers. They don't have to be. When
|
*Pointers* are scary for new programmers. They don't have to be. When
|
||||||
you see the word *pointer* used, think *address of* something.
|
you see the word *pointer*, think *address of* something.
|
||||||
|
|
||||||
*"pointer to a pointer"* sounds even more scary but if you think of pointers as *address of*, then *"pointer to a pointer"* means something which contains the address of something else which itself hold the address of a thing.
|
*"pointer to a pointer"* like `argv` sounds even more scary but if you
|
||||||
|
think of pointers as *address of*, then *"pointer to a pointer"* means
|
||||||
|
something which contains the address of something else which itself hold
|
||||||
|
the address of a thing.
|
||||||
|
|
||||||
In this case, the first *something* is `argv`. It contains the address of
|
In this case, the first *something* is `argv`. It contains the address
|
||||||
an array holding 1 or more addresses of null terminated strings.
|
of an array holding 1 or more addresses of null terminated strings.
|
||||||
|
|
||||||
Here is a picture depicting this:
|
Here is a picture depicting this:
|
||||||
|
|
||||||
|
|
@ -101,15 +115,20 @@ Here is a picture depicting this:
|
||||||
The above diagram also illustrates what we mean by *non-null*.
|
The above diagram also illustrates what we mean by *non-null*.
|
||||||
|
|
||||||
*argc* contains the value of 4 in the case depicted by the image.
|
*argc* contains the value of 4 in the case depicted by the image.
|
||||||
Looking at the array pointed to by
|
Looking at the array pointed to by `argv` you will notice **5** boxes
|
||||||
`argv` you will notice **5** boxes (or *elements*) arranged in a succession
|
(or *elements*) arranged in a succession of memory locations. The last
|
||||||
of memory locations. The last is filled with a 0 or `NULL`. The first 4 entries are non-null (i.e. they contain a value other than 0).
|
is filled with a 0 or `NULL`. The first 4 entries are non-null (i.e.
|
||||||
|
they contain a value other than 0).
|
||||||
|
|
||||||
The last element in the array contains a `NULL` in C (or
|
The last element in the array contains a `NULL` in C (or `nullptr` in
|
||||||
`nullptr` in C++) is not counted by `argc` because it is, in fact, a null.
|
C++) is not counted by `argc` because it is, in fact, a null.
|
||||||
|
|
||||||
Be reminded that null is the value of `0`. We will use this
|
Be reminded that null is the value of `0`. We will use this fact (that
|
||||||
fact (that the last value in the array is `0` to our advantage).
|
the last value in the array is `0` to our advantage).
|
||||||
|
|
||||||
|
In our enumeration of `argv` we will leverage the fact that the last
|
||||||
|
element is `NULL` to avoid the overhead of a loop variable serving as an
|
||||||
|
index.
|
||||||
|
|
||||||
### Line 5 Continued
|
### Line 5 Continued
|
||||||
|
|
||||||
|
|
@ -126,7 +145,9 @@ have been written:
|
||||||
char ** argv
|
char ** argv
|
||||||
```
|
```
|
||||||
|
|
||||||
accentuating the *pointer to a pointer* (i.e. two successive `*`) quality of `argv`. Here the `*` indicates *pointer*. Two in a row means *pointer to a pointer*.
|
accentuating the *pointer to a pointer* (i.e. two successive `*`)
|
||||||
|
quality of `argv`. Here the `*` indicates *pointer*. Two in a row means
|
||||||
|
*pointer to a pointer*.
|
||||||
|
|
||||||
### Line 6
|
### Line 6
|
||||||
|
|
||||||
|
|
@ -135,12 +156,13 @@ while (*argv) {
|
||||||
```
|
```
|
||||||
|
|
||||||
introduces a `while` loop. The code (i.e. the *body* of the loop) will
|
introduces a `while` loop. The code (i.e. the *body* of the loop) will
|
||||||
repeatedly execute as long as the value inside the parenthesis is found
|
repeatedly execute *as long as* the value inside the parenthesis is
|
||||||
to be `true` (i.e. non-zero or specifically in our case *non-null*).
|
found to be `true` (i.e. non-zero or specifically in our case
|
||||||
This loop will stop when `*argv` contains 0 (NULL).
|
*non-null*). The loop will stop when `*argv` contains 0 (NULL).
|
||||||
|
|
||||||
Somewhere inside the body of the loop, the value of `argv` will be changed.
|
Somewhere inside the body of the loop, the value of `argv` will be
|
||||||
If it were not, the loop would not terminate (i.e. an *infinite loop*).
|
changed. If it were not, the loop would not terminate (i.e. an *infinite
|
||||||
|
loop*).
|
||||||
|
|
||||||
#### Line 6 could be redone as a `for` loop
|
#### Line 6 could be redone as a `for` loop
|
||||||
|
|
||||||
|
|
@ -153,15 +175,16 @@ for (int index = 0; index < argc; index++)
|
||||||
Using this approach will result in more assembly language code being
|
Using this approach will result in more assembly language code being
|
||||||
generated including the introduction of an otherwise unneeded variable
|
generated including the introduction of an otherwise unneeded variable
|
||||||
`index`. `index` will range from `0` to `3` (stopping when index ceases
|
`index`. `index` will range from `0` to `3` (stopping when index ceases
|
||||||
to be less than `4`). `index` would be used in figuring out which
|
to be less than `4`). `index` would be used in figuring out which member
|
||||||
member of `argv` is examined in each loop. We claim `index` is unneeded in
|
of `argv` is examined in each loop. We claim `index` is unneeded in this
|
||||||
this case as we have a different way of moving through the `argv` array and,
|
case as we have a different way of moving through the `argv` array and,
|
||||||
most importantly, knowing when to stop.
|
most importantly, knowing when to stop.
|
||||||
|
|
||||||
### Line 7
|
### Line 7
|
||||||
|
|
||||||
`Line 7` is where is action is. Firstly, `cout` will receive some value for printing. `cout` is an output stream and the `<<` indicates something is being
|
`Line 7` is where is action is. Firstly, `cout` will receive some value
|
||||||
shoved into it - i.e. is being output.
|
for printing. `cout` is an output stream and the `<<` indicates
|
||||||
|
something is being shoved into it - i.e. is being output.
|
||||||
|
|
||||||
At the end of `line 7` is `endl`. This is a C++ shorthand for printing a
|
At the end of `line 7` is `endl`. This is a C++ shorthand for printing a
|
||||||
new line. In total, `line 7` prints something followed by advancing the
|
new line. In total, `line 7` prints something followed by advancing the
|
||||||
|
|
@ -175,41 +198,44 @@ We examine what is inside the parentheses first (as demanded by the rules
|
||||||
governing the *order of operations*).
|
governing the *order of operations*).
|
||||||
|
|
||||||
The value of `argv` is captured first. Recall this value is the address
|
The value of `argv` is captured first. Recall this value is the address
|
||||||
of an address of some characters. This value is put aside for a moment but
|
of an address of some characters. This value is put aside for a moment
|
||||||
will be used soon.
|
but will be used very soon.
|
||||||
|
|
||||||
Next the value of `argv` is incremented (the `++`). We know the value of
|
Next the value of `argv` is incremented (the `++`). We know the value of
|
||||||
`argv` is captured first because the `++` comes *after* `argv`. This is
|
`argv` is captured first because the `++` comes *after* `argv`. This is
|
||||||
how `argv` changes so as to step through the elements of the array. At
|
how `argv` changes so as to step through the elements of the array. At
|
||||||
some point `argv` will contain the address of a value `0` - and that's what
|
some point `argv` will contain the address of a value `0` - and that's
|
||||||
will terminate the `while` loop.
|
what will terminate the `while` loop.
|
||||||
|
|
||||||
After `argv` is incremented, its **previous** value is *dereferenced* indicated
|
After `argv` is incremented, its **previous** value is *dereferenced*
|
||||||
by the `*` outside the parentheses. Remember, we put the value aside before
|
indicated by the `*` outside the parentheses. Remember, we put the value
|
||||||
incrementing it.
|
aside before incrementing it.
|
||||||
|
|
||||||
`argv` contains the address of something.
|
`argv` contains the address of something. Dereferencing `argv` means "go
|
||||||
Dereferencing `argv` means "go fetch what is found at the address specified
|
fetch what is found at the address specified by `argv`".
|
||||||
by `argv`".
|
|
||||||
|
|
||||||
That, dear reader, is the address of the string of characters to be printed.
|
That, dear reader, is the address of the string of characters to be
|
||||||
|
printed. Or, it is a NULL, telling us to stop.
|
||||||
|
|
||||||
### Line 8
|
### Line 8
|
||||||
|
|
||||||
`Line 8` contains a matching brace for the opening brace found line `line 6`.
|
`Line 8` contains a matching brace for the opening brace found line
|
||||||
This marks the end of the `while` loop's *body*. The `}` causes a **jump**
|
`line 6`. This marks the end of the `while` loop's *body*. The `}`
|
||||||
back to evaluating what is pointed to by argv to see if it is now null (which
|
causes a **jump** back to evaluating what is pointed to by argv to see
|
||||||
exits the loop). A synonym for **jump** is **branch** - remember this.
|
if it is now null (which exits the loop). A synonym for **jump** is
|
||||||
|
**branch** - remember this.
|
||||||
|
|
||||||
Also remember that braces in a higher level language can mean a branch or jump in
|
Also remember that braces in a higher level language can mean a branch
|
||||||
assembly language. A brace in a higher level language can also mean a *target* or landing place
|
or jump in assembly language. A brace in a higher level language can
|
||||||
for a jump / branch elsewhere in the code.
|
also mean a *target* or landing place for a jump / branch elsewhere in
|
||||||
|
the code.
|
||||||
|
|
||||||
### Line 9
|
### Line 9
|
||||||
|
|
||||||
This program is itself invoked by another program (in this case the shell).
|
This program is itself invoked by another program (in this case the
|
||||||
The value returned by `main` is received by the program that launched this program. `Line 9` causes the shell to be able to receive the value 0 which,
|
shell). The value returned by `main` is received by the program that
|
||||||
by convention, means our program exited normally.
|
launched this program. `Line 9` causes the shell to be able to receive
|
||||||
|
the value 0 which, by convention, means our program exited normally.
|
||||||
|
|
||||||
Here's how to see a program's return value:
|
Here's how to see a program's return value:
|
||||||
|
|
||||||
|
|
@ -227,102 +253,117 @@ The `0` is the program's return code.
|
||||||
Here is version 2 of our program:
|
Here is version 2 of our program:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
#include <iostream> // 1
|
#include <iostream> // 1
|
||||||
// 2
|
// 2
|
||||||
using namespace std; // 3
|
using namespace std; // 3
|
||||||
// 4
|
// 4
|
||||||
int main(int argc, char * argv[]) { // 5
|
int main(int argc, char * argv[]) { // 5
|
||||||
top: // 6
|
top: // 6
|
||||||
if (*argv) { // 7
|
if (*argv) { // 7
|
||||||
cout << *(argv++) << endl; // 8
|
cout << *(argv++) << endl; // 8
|
||||||
goto top; // 9
|
goto top; // 9
|
||||||
} // 10
|
} // 10
|
||||||
return 0; // 11
|
return 0; // 11
|
||||||
} // 12
|
} // 12
|
||||||
```
|
```
|
||||||
|
|
||||||
[Here](./v2.cpp) is the original file.
|
[Here](./v2.cpp) is the original file.
|
||||||
|
|
||||||
In this version, we've moved a bit closer to assembly language by eliminating
|
In this version, we've moved a bit closer to assembly language by
|
||||||
the `while` loop replacing it with an `if` statement, a `label` and a `goto`.
|
eliminating the `while` loop replacing it with an `if` statement, a
|
||||||
|
`label` and a `goto`.
|
||||||
|
|
||||||
### Line 6
|
### Line 6
|
||||||
|
|
||||||
This line is a `label`. This is not an instruction, rather it is a way of specifying the address of an instruction (or data). Labels exist in assembly language, `while` loops do not, per se. Rather, you must code them yourself using some kind of *branch* instruction (remember above the word *branch*?) in this case the `goto`.
|
This line is a `label`. This is not an instruction, rather it is a way
|
||||||
|
of specifying the address of an instruction (or data). Labels exist in
|
||||||
|
assembly language, `while` loops do not, per se. Rather, you must code
|
||||||
|
them yourself using some kind of *branch* instruction (remember above
|
||||||
|
the word *branch*?) in this case the `goto`.
|
||||||
|
|
||||||
### Line 7
|
### Line 7
|
||||||
|
|
||||||
The `while` loop has been removed. It has been replaced with explicit use of
|
The `while` loop has been removed. It has been replaced with explicit
|
||||||
an `if` statement at what was the top of the loop and a `goto` branch at what
|
use of an `if` statement at what was the top of the loop and a `goto`
|
||||||
was the bottom. This is how `while` loops are implemented - now we're
|
branch at what was the bottom. This is how `while` loops are
|
||||||
explicitly making this visible. For more information on `while` loops
|
implemented. Now we're explicitly making this visible. For more
|
||||||
see [here](../while/README.md)
|
information on `while` loops see [here](../while/README.md)
|
||||||
|
|
||||||
### Line 9
|
### Line 9
|
||||||
|
|
||||||
The use of `goto` is normally frowned upon in modern higher level languages.
|
The use of `goto` is normally frowned upon in modern higher level
|
||||||
However, the feature or ability to use it still remains, left over from the earliest days of C.
|
languages. However, the feature or ability to use it still remains, left
|
||||||
The keyword `goto` is
|
over from the earliest days of C. The keyword `goto` is followed by the
|
||||||
followed by the label to which control should transfer. `goto` is an example of a branch and the label `top` is the *target* of the branch.
|
label to which control should transfer. `goto` is an example of a branch
|
||||||
|
and the label `top` is the *target* of the branch.
|
||||||
|
|
||||||
## V3
|
## V3
|
||||||
|
|
||||||
In version 3 we eliminate the C++'ism of `cout`. `cout` doesn't exist in assembly language so
|
In version 3 we eliminate the C++'ism of `cout`. `cout` doesn't exist in
|
||||||
we'll use `puts` instead to implement the same behavior
|
assembly language so we'll use `puts()` instead to implement the same
|
||||||
of the use of `cout` - namely the printing out of what is pointed to by
|
behavior of the use of `cout` - namely the printing out of what is
|
||||||
`*argv` *and* printing out a new line (done internally for us by `puts`).
|
pointed to by `*argv` *and* printing out a new line (done internally for
|
||||||
|
us by `puts()`).
|
||||||
|
|
||||||
|
`puts()` comes to us from the standard C runtime.
|
||||||
|
|
||||||
At this point, there is no C++ left - only C.
|
At this point, there is no C++ left - only C.
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
#include <stdio.h> // 1
|
#include <stdio.h> // 1
|
||||||
// 2
|
// 2
|
||||||
int main(int argc, char * argv[]) { // 3
|
int main(int argc, char * argv[]) { // 3
|
||||||
top: // 4
|
top: // 4
|
||||||
if (*argv) { // 5
|
if (*argv) { // 5
|
||||||
puts(*(argv++)); // 6
|
puts(*(argv++)); // 6
|
||||||
goto top; // 7
|
goto top; // 7
|
||||||
} // 8
|
} // 8
|
||||||
return 0; // 9
|
return 0; // 9
|
||||||
} // 10
|
} // 10
|
||||||
```
|
```
|
||||||
|
|
||||||
[Here](./v3.cpp) is the original code.
|
[Here](./v3.cpp) is the original code.
|
||||||
|
|
||||||
### Line 6
|
### Line 6
|
||||||
|
|
||||||
`puts` as described above takes the address of a C string and prints it out with the addition of a trailing new line. What's going on inside the parentheses is identical to the previous versions.
|
`puts()` as described above takes the address of a C string and prints
|
||||||
|
it out with the addition of a trailing new line. What's going on inside
|
||||||
|
the parentheses is identical to the previous versions.
|
||||||
|
|
||||||
To review, the current value of `argv` is put aside for reuse in a moment.
|
To review:
|
||||||
Then `argv` is incremented. Recall that `argv` is "the address of a variable holding the address
|
|
||||||
of a string." Incrementing `argv` has the effect of moving on to the next string for the *next*
|
|
||||||
iteration of the loop.
|
|
||||||
|
|
||||||
Then, the *previous* value of `argv` which we set aside, is dereferenced. `*argv` is the address
|
* the current value of `argv` is put aside for reuse in a
|
||||||
of a string. That string is emitted by `puts` followed
|
moment. Then `argv` is incremented. Recall that `argv` is "the address
|
||||||
by a new line.
|
of a variable holding the address of a string." Incrementing `argv` has
|
||||||
|
the effect of moving on to the next string for the *next* iteration of
|
||||||
|
the loop or, causes the loop to terminate.
|
||||||
|
|
||||||
|
* then, the *previous* value of `argv` which we set aside, is
|
||||||
|
dereferenced. `*argv` is the address of a string. That string is emitted
|
||||||
|
by `puts()` followed by a new line.
|
||||||
|
|
||||||
## Version 4
|
## Version 4
|
||||||
|
|
||||||
In this version we're decomposing the `if` statement even further so as to
|
In this version we're decomposing the `if` statement even further so as
|
||||||
eliminate the braces that were part of the previous version's `if` statement.
|
to eliminate the braces that were part of the previous version's `if`
|
||||||
|
statement.
|
||||||
|
|
||||||
In general, braces in the higher level language serve as either branches or
|
In general, braces in the higher level language serve as either branches
|
||||||
as labels.
|
or as labels in assembly language.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#include <stdio.h> /* 1 */
|
#include <stdio.h> /* 1 */
|
||||||
/* 2 */
|
/* 2 */
|
||||||
int main(int argc, char * argv[]) { /* 3 */
|
int main(int argc, char * argv[]) { /* 3 */
|
||||||
top: /* 4 */
|
top: /* 4 */
|
||||||
if (*argv == NULL) /* 5 */
|
if (*argv == NULL) /* 5 */
|
||||||
goto bottom; /* 6 */
|
goto bottom; /* 6 */
|
||||||
puts(*(argv++)); /* 7 */
|
puts(*(argv++)); /* 7 */
|
||||||
goto top; /* 8 */
|
goto top; /* 8 */
|
||||||
/* 9 */
|
/* 9 */
|
||||||
bottom: /* 10 */
|
bottom: /* 10 */
|
||||||
return 0; /* 11 */
|
return 0; /* 11 */
|
||||||
} /* 12 */
|
} /* 12 */
|
||||||
```
|
```
|
||||||
|
|
||||||
[Here](./v4.c) is the original code.
|
[Here](./v4.c) is the original code.
|
||||||
|
|
@ -330,23 +371,23 @@ int main(int argc, char * argv[]) { /* 3 */
|
||||||
### Line 5
|
### Line 5
|
||||||
|
|
||||||
Notice how the sense of the `if` statement has reversed compared to the
|
Notice how the sense of the `if` statement has reversed compared to the
|
||||||
previous version.
|
previous version. This is a convenience.
|
||||||
|
|
||||||
In the previous version, we call `puts` only if the value of `*argv` is not
|
In the previous version, we call `puts()` only if the value of `*argv`
|
||||||
null. By flipping the sense of the `if` statement, it means "if the value of
|
is not null. By flipping the sense of the `if` statement, it means "if
|
||||||
`*argv` **is** null, skip calling `puts`."
|
the value of `*argv` **is** null, skip calling `puts()`."
|
||||||
|
|
||||||
This isn't a requirement. In this case, flipping the sense of the `if` statement
|
This isn't a requirement. In this case, flipping the sense of the `if`
|
||||||
results in fewer lines of assembly language.
|
statement results in fewer lines of assembly language.
|
||||||
|
|
||||||
### Line 6
|
### Line 6
|
||||||
|
|
||||||
We exit our decomposed loop by branching to a label beyond the `goto`
|
We exit our decomposed loop by branching to a label beyond the `goto`
|
||||||
implementing the bottom of what was our `while` loop.
|
implementing the bottom of what was our `while` loop.
|
||||||
|
|
||||||
At this point we have devolved our program into just barely above the level
|
At this point we have devolved our program into just barely above the
|
||||||
of assembly language. In the next version, which is written in ARM V8
|
level of assembly language. In the next version, which is written in ARM
|
||||||
assembly language, you'll see that just about every instruction has a
|
V8 assembly language, you'll see that just about every instruction has a
|
||||||
one to one correspondence to the C code in version 4.
|
one to one correspondence to the C code in version 4.
|
||||||
|
|
||||||
## Version 5 - in Assembly Language
|
## Version 5 - in Assembly Language
|
||||||
|
|
@ -375,32 +416,40 @@ bottom: // 12
|
||||||
|
|
||||||
[Here](./v5.s) is the original code.
|
[Here](./v5.s) is the original code.
|
||||||
|
|
||||||
Get your bearings by noticing the labels. They are the same as in our previous
|
Get your bearings by noticing the labels. They are the same as in our
|
||||||
version and perform the same roles.
|
previous version and perform the same roles.
|
||||||
|
|
||||||
### Line 1
|
### Line 1
|
||||||
|
|
||||||
`main` is a function that is specially named. `Line 1` instructs the assembler to make the name
|
`main()` is a function that is specially named. `Line 1` instructs the
|
||||||
and location of `main` visible to the *linker*. To refresh your knowledge of the linker, see
|
assembler to make the name and location of `main()` visible to the
|
||||||
|
*linker*. To refresh your knowledge of the linker, see
|
||||||
[here](https://youtu.be/Iv3psS4n9j8).
|
[here](https://youtu.be/Iv3psS4n9j8).
|
||||||
|
|
||||||
Without `Line 1`, building the executable will fail with an unresolved symbol error -
|
Without `Line 1`, building the executable will fail with an unresolved
|
||||||
namely that the linker could not find `main`.
|
symbol error - namely that the linker could not find `main`.
|
||||||
|
|
||||||
### Line 2
|
### Line 2
|
||||||
|
|
||||||
In `Line 1` we told the assembler to publish the location of the label `main`. In `Line 2` we're
|
In `Line 1` we told the assembler to publish the location of the label
|
||||||
actually specifying the value of `main`. Contrast `main` with `top` and `bottom`. The difference
|
`main`. In `Line 2` we're actually specifying the value of `main`.
|
||||||
between them is that only `main` is made visible outside this file.
|
Contrast `main` with `top` and `bottom`. The difference between them is
|
||||||
|
that only `main` is made visible outside this file.
|
||||||
|
|
||||||
Again, in the case of `main`, the label must be specified as `global` so that the linker can find
|
Again, in the case of `main`, the label must be specified as `global` so
|
||||||
it. `top` and `bottom` are also labels but they are not published outside this one source file.
|
that the linker can find it. `top` and `bottom` are also labels but they
|
||||||
|
are not published outside this one source file.
|
||||||
|
|
||||||
### Line 3
|
### Line 3
|
||||||
|
|
||||||
This instruction copies the value in two *registers* onto your *stack*. There's a lot of new information here.
|
This instruction copies the value in two *registers* onto your *stack*.
|
||||||
|
There's a lot of new information here.
|
||||||
|
|
||||||
*Registers* are ultra high speed storage locations built into the circuitry of the processor. On the ARM, all computation takes place in the registers (with very few exceptions). Memory, with very few exceptions, is used to persist data (and hold instructions). In a higher level language, when you say:
|
*Registers* are ultra high speed storage locations built into the
|
||||||
|
circuitry of the processor. On the ARM, all computation takes place in
|
||||||
|
the registers (with very few exceptions). Memory, with very few
|
||||||
|
exceptions, is used to persist data (and hold instructions). In a higher
|
||||||
|
level language, when you say:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
x = x + 1;
|
x = x + 1;
|
||||||
|
|
@ -408,86 +457,135 @@ x = x + 1;
|
||||||
|
|
||||||
the assembly language this looks like:
|
the assembly language this looks like:
|
||||||
|
|
||||||
```text
|
|
||||||
1. Load the memory address of x into a register.
|
1. Load the memory address of x into a register.
|
||||||
|
|
||||||
2. Go out to that memory address and fetch what it contains into a register (a dereference).
|
2. Go out to that memory address and fetch what it contains into a register (a dereference).
|
||||||
|
|
||||||
3. Add one to that value (in the register).
|
3. Add one to that value (in the register).
|
||||||
|
|
||||||
4. Store the value back to memory using the address loaded on line 1.
|
4. Store the value back to memory using the address loaded on line 1.
|
||||||
```
|
|
||||||
|
|
||||||
The thing to note here is that the increment of x didn't happen in memory - it happened in a register. The value in x had to be loaded into a register, incremented in the register and finally written back to memory. By careful design, use of memory for persisting data
|
The thing to note here is that the increment of x didn't happen in
|
||||||
can be avoided completely. This makes for very fast execution because registers are
|
memory - it happened in a register. The value in x had to be loaded into
|
||||||
one or more orders of magnitude faster than RAM.
|
a register, incremented in the register and finally written back to
|
||||||
|
memory. By careful design, use of memory for persisting data can be
|
||||||
|
avoided completely. This makes for very fast execution because registers
|
||||||
|
are one or more orders of magnitude faster than RAM.
|
||||||
|
|
||||||
The *stack* is a region of memory used to store *local* variables as well as the trail of breadcrumbs which allows functions to return from whence they were invoked. In a high level language, you don't manage the stack yourself. Stacks happen.
|
The *stack* is a region of memory used to store *local* variables as
|
||||||
|
well as the trail of breadcrumbs which allows functions to return from
|
||||||
|
whence they were invoked. In a high level language, you don't manage the
|
||||||
|
stack yourself. Stacks just happen.
|
||||||
|
|
||||||
In a higher level language, values go onto the stack (push) and leave the stack (pop) passively
|
In a higher level language, values go onto the stack (push) and leave
|
||||||
by virtue of having made function calls. In assembly language *you* manage the stack!
|
the stack (pop) passively by virtue of having made function calls and
|
||||||
|
declaring local variables. In assembly language *you* manage the stack!
|
||||||
|
|
||||||
`Line 3` `st`ores a `p`air of registers on the stack. `stp` means *store pair*. The registers being copied to the stack are `x21` and `x30`. `x30` is special as it contains the address to which this function should return. It is the "breadcrumb" mentioned before.
|
`Line 3` `st`ores a `p`air of registers on the stack. `stp` means *store
|
||||||
|
pair*. The registers being copied to the stack are `x21` and `x30`.
|
||||||
|
`x30` is special as it contains the address to which this function
|
||||||
|
should return. It is the "breadcrumb" mentioned before.
|
||||||
|
|
||||||
`x30` gets overwritten every time a function call is made. If `main()` made no function calls itself, `x30` would not have to be backed up. However, this `main()` does make function calls (to `puts()`).
|
`x30` gets overwritten every time a function call is made. If `main()`
|
||||||
|
made no function calls itself, `x30` would not have to be backed up.
|
||||||
|
However, this `main()` does make function calls (to `puts()`).
|
||||||
|
|
||||||
If we don't *save* `x30` on the stack when `main` initially enters, our ability to properly return to whoever called `main` would be broken by the function call to `puts()`. In all likelihood when this program ended it would cause a crash.
|
If we don't *save* `x30` on the stack when `main` initially enters, our
|
||||||
|
ability to properly return to whoever called `main` would be broken by
|
||||||
|
the function call to `puts()`. In all likelihood when this program ended
|
||||||
|
it would cause a crash.
|
||||||
|
|
||||||
`x21` is also being saved on the stack. *Calling conventions* specify some registers can be blown away (used as scratch) while some registers must be preserved and restored to their previous values upon leaving the function. `x21` is one of those registers.
|
`x21` is also being saved on the stack. *Calling conventions* specify
|
||||||
|
some registers can be blown away (used as scratch) while some registers
|
||||||
|
must be preserved and restored to their previous values upon leaving the
|
||||||
|
function. `x21` is one of those registers.
|
||||||
|
|
||||||
`x21` will be used in `main` so its original value must be preserved.
|
`x21` will be used in `main` so its original value must be preserved.
|
||||||
|
|
||||||
Finally let's look at `[sp, -16]!`. There's a lot going on here.
|
Finally let's look at `[sp, -16]!`. There's a lot going on here.
|
||||||
|
|
||||||
First, the `[` and `]` serve the same purpose of the asterisk in C and C++ indicating "dereference." It means use what's inside the brackets as an address for going out to memory.
|
First, the `[` and `]` serve the same purpose of the asterisk in C and
|
||||||
|
C++ indicating "dereference." It means use what's inside the brackets as
|
||||||
|
an address for going out to memory.
|
||||||
|
|
||||||
Next, `sp` means use the stack pointer - a register which keeps track of where your stack currently is. The `-16` subtracts 16 from the current value of the stack register. `x` registers like `x21` and `x30` are each 8 bytes (64 bits) wide. This accounts for the value 16 (i.e. 2 \* 8.
|
Next, `sp` means use the stack pointer - a register which keeps track of
|
||||||
|
where your stack currently is. The `-16` subtracts 16 from the current
|
||||||
|
value of the stack register. `x` registers like `x21` and `x30` are each
|
||||||
|
8 bytes (64 bits) wide. This accounts for the value 16 (i.e. 2 \* 8.
|
||||||
|
|
||||||
Lastly, the exclamation point means that the stack pointer should be changed (i.e. the -16 applied to it) *before* the value of the stack pointer is used as the address in memory to which the registers will be copied. Again, this is a predecrement.
|
Lastly, the exclamation point means that the stack pointer should be
|
||||||
|
changed (i.e. the -16 applied to it) *before* the value of the stack
|
||||||
|
pointer is used as the address in memory to which the registers will be
|
||||||
|
copied. Again, this is a predecrement.
|
||||||
|
|
||||||
**The stack pointer in ARM V8 can only be manipulated in multiples of 16.**
|
**The stack pointer in ARM V8 can only be manipulated in multiples of
|
||||||
|
16.**
|
||||||
|
|
||||||
**The stack pointer in ARM V8 can only be manipulated in multiples of 16.**
|
**The stack pointer in ARM V8 can only be manipulated in multiples of
|
||||||
|
16.**
|
||||||
|
|
||||||
**The stack pointer in ARM V8 can only be manipulated in multiples of 16.**
|
**The stack pointer in ARM V8 can only be manipulated in multiples of
|
||||||
|
16.**
|
||||||
|
|
||||||
In a higher level language `Line 3` would look like this:
|
In a higher level language `Line 3` would look like this:
|
||||||
|
|
||||||
```c++=
|
```c++
|
||||||
*(--sp) = x21;
|
*(--sp) = x21;
|
||||||
*(--sp) = x30;
|
*(--sp) = x30;
|
||||||
```
|
```
|
||||||
|
|
||||||
That is, subtract 8 from the stack pointer and copy `x21` to that location. Then, subtract 8 from the stack pointer and copy `x30` to that location.
|
That is, subtract 8 from the stack pointer and copy `x21` to that
|
||||||
|
location. Then, subtract 8 from the stack pointer and copy `x30` to that
|
||||||
|
location.
|
||||||
|
|
||||||
**The stack pointer in ARM V8 can only be manipulated in multiples of 16.**
|
**The stack pointer in ARM V8 can only be manipulated in multiples of
|
||||||
|
16.**
|
||||||
|
|
||||||
**The stack pointer in ARM V8 can only be manipulated in multiples of 16.**
|
**The stack pointer in ARM V8 can only be manipulated in multiples of
|
||||||
|
16.**
|
||||||
|
|
||||||
**The stack pointer in ARM V8 can only be manipulated in multiples of 16.**
|
**The stack pointer in ARM V8 can only be manipulated in multiples of
|
||||||
|
16.**
|
||||||
|
|
||||||
### Line 4
|
### Line 4
|
||||||
|
|
||||||
When a function is passed parameters, up to 8 of them can be found in the first 8 scratch registers (`x0` through `x7`). For example, recall:
|
When a function is passed parameters, up to 8 of them can be found in
|
||||||
|
the first 8 scratch registers (`x0` through `x7`). For example, recall:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
main(int argc, char ** argv)
|
main(int argc, char ** argv)
|
||||||
```
|
```
|
||||||
|
|
||||||
`argc` is the first parameter. It shows up to the function in register `x0`. This is a slight oversimplification because `x` registers are 64 bits wide and `int` is 32 bits wide. The simplification isn't relevant here so let's continue.
|
`argc` is the first parameter. It shows up to the function in register
|
||||||
|
`x0`. This is a slight oversimplification because `x` registers are 64
|
||||||
|
bits wide and `int` is 32 bits wide. The simplification isn't relevant
|
||||||
|
here so let's continue.
|
||||||
|
|
||||||
`argv` is the second parameter to `main`. Being second, it shows up in `main` in register `x1`.
|
`argv` is the second parameter to `main`. Being second, it shows up in
|
||||||
|
`main` in register `x1`.
|
||||||
|
|
||||||
`x0` through `x7` are truly scratch registers - they can be overwritten with new values at any time by you or when calling other functions (like `main` will call `puts`). Because of this, `argv` that arrives in `x1` is preserved in `x21` (whose original value we already preserved on the stack).
|
`x0` through `x7` are truly scratch registers - they can be overwritten
|
||||||
|
with new values at any time by you or when calling other functions (like
|
||||||
|
`main` will call `puts`). Because of this, `argv` that arrives in `x1`
|
||||||
|
is preserved in `x21` (whose original value we already preserved on the
|
||||||
|
stack).
|
||||||
|
|
||||||
```asm
|
```asm
|
||||||
mov x21, x1
|
mov x21, x1
|
||||||
```
|
```
|
||||||
|
|
||||||
can be read as `copy what is in x1 into x21`. I.e. read the register use from right to left.
|
can be read as `copy what is in x1 into x21`. I.e. read the register use
|
||||||
|
from right to left.
|
||||||
|
|
||||||
The `mov` instruction doesn't *move* anything anywhere. It *copies*.
|
The `mov` instruction doesn't *move* anything anywhere. It *copies*.
|
||||||
|
|
||||||
## Line 6
|
## Line 6
|
||||||
|
|
||||||
This line contains the label `top`. The instruction that follows (the `ldr`) is stored at some address. The value of `top` is that address. The unconditional branch on `line 10` specifies `top` as the destination of the branch. You can think of `line 10` as the closing brace of the original while loop.
|
This line contains the label `top`. The instruction that follows (the
|
||||||
|
`ldr`) is stored at some address. The value of `top` is that address.
|
||||||
|
The unconditional branch on `line 10` specifies `top` as the destination
|
||||||
|
of the branch. You can think of `line 10` as the closing brace of the
|
||||||
|
original while loop.
|
||||||
|
|
||||||
## Lines 7, 8 and 9
|
## Lines 7, 8 and 9
|
||||||
|
|
||||||
|
|
@ -499,7 +597,8 @@ Version 4 contains:
|
||||||
7) puts(*(argv++));
|
7) puts(*(argv++));
|
||||||
```
|
```
|
||||||
|
|
||||||
These three lines are implemented on `lines 7, 8 and 9` in the assembly language. These instructions are:
|
These three lines are implemented on `lines 7, 8 and 9` in the assembly
|
||||||
|
language. These instructions are:
|
||||||
|
|
||||||
```asm
|
```asm
|
||||||
7) ldr x0, [x21], 8
|
7) ldr x0, [x21], 8
|
||||||
|
|
@ -507,44 +606,65 @@ These three lines are implemented on `lines 7, 8 and 9` in the assembly language
|
||||||
9) bl puts
|
9) bl puts
|
||||||
```
|
```
|
||||||
|
|
||||||
The action of the assembly language statement differs slightly in the order in which the C++ operates.
|
The action of the assembly language statement differs slightly in the
|
||||||
|
order in which the C++ operates.
|
||||||
|
|
||||||
In both cases, `argv` is dereferenced first. In C++ this is done with `*argv`. In the assembly language, this is done with `[x21]` (recall, we put `x1` into `x21`).
|
In both cases, `argv` is dereferenced first. In C++ this is done with
|
||||||
|
`*argv`. In the assembly language, this is done with `[x21]` (recall, we
|
||||||
|
put `x1` into `x21`).
|
||||||
|
|
||||||
In C++ the increment of `argv` is done on line 7 - the `++` post increment. In the assembly language, the post increment is done on `line 7` which is the *first* instruction of the three whereas in C++ the post increment happens on the *last* line of three.
|
In C++ the increment of `argv` is done on line 7 - the `++` post
|
||||||
|
increment. In the assembly language, the post increment is done on `line
|
||||||
|
7` which is the *first* instruction of the three whereas in C++ the post
|
||||||
|
increment happens on the *last* line of three.
|
||||||
|
|
||||||
This difference is OK because the older value of `argv` is preserved in `x0` for the call to
|
This difference is OK because the older value of `argv` is preserved in
|
||||||
`puts()`. As long as we can get at the value of `argv` before the increment, it doesn't
|
`x0` for the call to `puts()`. As long as we can get at the value of
|
||||||
matter when the increment is done.
|
`argv` before the increment, it doesn't matter when the increment is
|
||||||
|
done.
|
||||||
|
|
||||||
The *if* happens on the first line of the C++ but done on the middle line of the assembly language. `cbz` stands for *`C`onditionally `B`ranch if `Z`ero*.
|
The *if* happens on the first line of the C++ but done on the middle
|
||||||
|
line of the assembly language. `cbz` stands for *`C`onditionally
|
||||||
|
`B`ranch if `Z`ero*.
|
||||||
|
|
||||||
The `goto` or branch happens on the middle line (`line 8`) of the assembly language. Very economical in terms of code!
|
The `goto` or branch happens on the middle line (`line 8`) of the
|
||||||
|
assembly language. Very economical in terms of code!
|
||||||
|
|
||||||
`puts` is called with the un-incremented version of `argv` in the C++ version - again notice the use of post increment. In the assembly language version this is also the case. How? `argv` before
|
`puts()` is called with the un-incremented version of `argv` in the C++
|
||||||
the increment was put in `x0`.
|
version - again notice the use of post increment. In the assembly
|
||||||
That value is still sitting in `x0` when the function call (`bl`) is made.
|
language version this is also the case. How? `argv` before the increment
|
||||||
|
was put in `x0`. That value is still sitting in `x0` when the function
|
||||||
|
call (`bl`) is made.
|
||||||
|
|
||||||
A word about `bl`: `B`ranch with `L`ink puts the address of the *next* (`line 10`) instruction into `x30` behind the scene. This is why we backed up `x30` on `line 3`. When `puts` executes its return (via `ret`), control will branch to `line 10`.
|
A word about `bl`: `B`ranch with `L`ink puts the address of the *next*
|
||||||
|
(`line 10`) instruction into `x30` behind the scene. This is why we
|
||||||
|
backed up `x30` on `line 3`. When `puts` executes its return (via
|
||||||
|
`ret`), control will branch to `line 10`.
|
||||||
|
|
||||||
## Line 10
|
## Line 10
|
||||||
|
|
||||||
`Line 10` is exactly the same as `line 8` of Version 4. It hides out as the closing brace on `line 8` of Version 1.
|
`Line 10` is exactly the same as `line 8` of Version 4. It hides out as
|
||||||
|
the closing brace on `line 8` of Version 1.
|
||||||
|
|
||||||
## Lines 13, 14 and 15
|
## Lines 13, 14 and 15
|
||||||
|
|
||||||
`Lines 13` through `15` implement the return of zero found on `line 11` of Version 4. The original values of `x21` and `x30` are restored. The stack pointer is post incremented back to where it started. Zero is put in `x0` and `main` returns.
|
`Lines 13` through `15` implement the return of zero found on `line 11`
|
||||||
|
of Version 4. The original values of `x21` and `x30` are restored. The
|
||||||
|
stack pointer is post incremented back to where it started. Zero is put
|
||||||
|
in `x0` and `main` returns.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Assembly language is scary to a lot of people. It doesn't need to be.
|
Assembly language is scary to a lot of people. It doesn't need to be.
|
||||||
|
|
||||||
We have shown one small example of how close C is to assembly language. With a little practice,
|
We have shown one small example of how close C is to assembly language.
|
||||||
one can code in assembly language at pretty much the same speed as C. We are not advocating the
|
With a little practice, one can code in assembly language at pretty much
|
||||||
ditching of your high level languages rather... always use the *right* tool for the *right* job.
|
the same speed as C. We are not advocating the ditching of your high
|
||||||
|
level languages rather... always use the *right* tool for the *right*
|
||||||
|
job.
|
||||||
|
|
||||||
We do maintain that understanding assembly language principles will improve your higher level
|
We do maintain that understanding assembly language principles will
|
||||||
language coding.
|
improve your higher level language coding.
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue