more bigger faster

This commit is contained in:
Perry Kivolowitz 2023-01-14 14:39:41 -06:00
parent 918ce23f57
commit 29b1367730
13 changed files with 339 additions and 345 deletions

View file

@ -62,12 +62,12 @@ In this book we will use the ARM LINUX conventions. This means:
hope to add a chapter detailing the Windows calling convention.
You'll notice right away that we make use of the C-runtime directly
rather than make OS service calls. So, for instance, if we want to
call `write()`, we call `write` from the assembly language. This
version of the system call `write` is a wrapper function built into
the C-runtime which handles the low level details of performing a
system call. See the [chapter](./not_written_yet.md) on what actually
happens inside these wrapper functions.
rather than make OS service calls. So, for instance, if we want to call
`write()`, we call `write` from the assembly language. This version of
the system call `write` is a wrapper function built into the C-runtime
which handles the low level details of performing a system call. See the
[here](./more/system_calls/README.md) on what actually happens inside
these wrapper functions.
## A Lot of Names
@ -217,7 +217,7 @@ knowledge - how cool is that!
| 6 | Functions | |
| .... a | [.... Calling and Returning](./section_1/funcs/README.md) | NA |
| .... b | [.... Passing Parameters](./section_1/funcs/README2.md) | NA |
| .... c | [.... Calling common C runtime functions](./section_1/funcs/README3.md) | NA |
| .... c | [.... Example of calling some common C runtime functions](./section_1/funcs/README3.md) | NA |
| 7 | [FizzBuzz - a Complete Program](./section_1/fizzbuzz/README.md) | NA |
| 8 | Structs | |
| .... a | [.... Alignment](./section_1/structs/alignment.md) | NA |
@ -261,6 +261,7 @@ What would a book about assembly language be without bit bashing?
| Chapter | Markdown | PDF |
| ------- | -------- | --- |
| --- | [Determining string literal lengths for C functions](./more/strlen_for_c/README.md) | NA |
| --- | [Under the hood: System Calls](./more/system_calls/README.md) | NA |
## Projects

178
more/system_calls/README.md Normal file
View file

@ -0,0 +1,178 @@
# Under the hood: System Calls
The term "function" is used many places in this book and needs no
additional explanation here. The term "system call" has also been used
in many places, often with a comment that making a system call through
the C runtime is actually just calling an ordinary function acting as
a wrapper. An explanation of this has been promised...
## Wrappers
"Wrapper" is a term used to describe a function which hides the details
of something else, often another function or functions. Hiding details
is a form of abstraction and can be a good thing. Broadly speaking,
an API (Application Programmer's Interface) is itself another example
of wrappers in common use.
## C runtime as a wrapper
Many C runtime functions are just wrappers for system calls. For example
if you call `open()` from the C runtime, the function will perform a few
bookkeeping operations and then make the actual system call.
## What IS a system call?
The short answer is a system call is a sort-of function call that is
serviced by the operating system itself, within its own private region
of memory and with access to internal features and data structures.
Our programs run in "userland". The technical name for userland on the
ARM64 processor is EL0 (Exception Level 0).
We can operate within the kernel's space only through carefully
controlled mechanisms - such as system calls. The technical name for
where the kernel (or system) generally operates is called EL1.
There are two higher Exception Levels (EL2 and EL3) which are beyond
the scope of this book.
## Mechanism of making a system call
First, like any function call, parameters need to be set up. The first
parameter goes in the first register, etc.
Second, a number associated with the specific system call we wish to
make is loaded in a specific register (`w8`).
Finally, a special instruction `svc` causes a trap which elevates us out
of userland into kernel space. Said differently, `svc` causes a
transition from EL0 to EL1. There, various checks are done and the
actual code for the system call is run.
A description of returning from a system call is beyond the scope of
this book. Hint: just as there's a special instruction that escalates
from EL0 to EL1, there is a special instruction that does the reverse.
## What is the number associated with a particular system call?
Hard question.
In a perfect world, each Linux distribution would use the same set of
system call numbers. But no.
[This](https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html)
is he most comprehensive list of system call numbers we have seen. It
shows system call numbers for many architectures and distributions.
## Example: calling `getpid()`
The system call `getpid()` fetches the running process's process ID.
Every executing entity has one.
We present four different versions of the same program:
1. Written in C++
2. Written in C
3. Written using C runtime from assembly language
4. Calling the system call directly from assembly language
### Written in C++
```c++
#include <iostream> // 1
#include <unistd.h> // 2
// 3
using std::cout; // 4
using std::endl; // 5
// 6
int main() { // 7
cout << "Greetings from: " << getpid() << endl; // 8
return 0; // 9
} // 10
```
### Written in C
```c
#include <stdio.h> // 1
#include <unistd.h> // 2
// 3
int main() { // 4
printf("Greetings from: %d\n", getpid()); // 5
return 0; // 6
} // 7
```
### Written in assembly language using C runtime
```text
.global main // 1
.text // 2
.align 2 // 3
// 4
main: stp x29, x30, [sp, -16]! // 5
bl getpid // 6
mov w1, w0 // 7
ldr x0, =fmt // 8
bl printf // 9
ldp x29, x30, [sp], 16 // 10
mov w0, wzr // 11
ret // 12
// 13
.data // 14
fmt: .asciz "Greetings from: %d\n" // 15
// 16
.end // 17
```
### And finally: calling the system call directly
```text
.global main // 1
.text // 2
.align 2 // 3
// 4
main: stp x29, x30, [sp, -16]! // 5
mov x8, 172 // getpid on ARM64 // 6
svc 0 // trap to EL1 // 7
mov w1, w0 // 8
ldr x0, =fmt // 9
bl printf // 10
ldp x29, x30, [sp], 16 // 11
mov w0, wzr // 12
ret // 13
// 14
.data // 15
fmt: .asciz "Greetings from: %d\n" // 16
// 17
.end // 18
```
We chose `getpid()` because it doesn't require any parameters. Using
the C runtime, we simply `bl` to it. Calling the system call directly
is different in that we must first load `x8` with the number that
corresponds to `getpid()` for the AARCH64 architecture.
Consulting [this](https://marcin.juszkiewicz.com.pl/download/tables/syscalls.html)
awesome website, we find that the number we want is 172.
![here](./getpid/getpid.png)
The constant specific to the system call we want is loaded into `x8`.
Recall that `x0` through `x7` are scratch registers.
Then on line 7, the `svc` with the argument 0 initiates the escalation
from EL0 to EL1 where the kernel implements our desired functionality
and returns to us.
### Review
System calls are functions implemented inside the operating system.
To get there, at some point perhaps behind a wrapper function, a
specific system call number is placed in `x8` with other scratch
registers getting the system call's documented parameters and the `svc`
instruction is executed.

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

View file

@ -0,0 +1,7 @@
#include <stdio.h>
#include <unistd.h>
int main() {
printf("Greetings from: %d\n", getpid());
return 0;
}

View file

@ -0,0 +1,10 @@
#include <iostream>
#include <unistd.h>
using std::cout;
using std::endl;
int main() {
cout << "Greetings from: " << getpid() << endl;
return 0;
}

View file

@ -0,0 +1,17 @@
.global main
.text
.align 2
main: stp x29, x30, [sp, -16]!
bl getpid
mov w1, w0
ldr x0, =fmt
bl printf
ldp x29, x30, [sp], 16
mov w0, wzr
ret
.data
fmt: .asciz "Greetings from: %d\n"
.end

View file

@ -0,0 +1,18 @@
.global main
.text
.align 2
main: stp x29, x30, [sp, -16]!
mov x8, 172 // getpid on ARM64
svc 0 // trap to EL1
mov w1, w0
ldr x0, =fmt
bl printf
ldp x29, x30, [sp], 16
mov w0, wzr
ret
.data
fmt: .asciz "Greetings from: %d\n"
.end

View file

@ -1,9 +1,57 @@
# Section 1 / Examples of calling common C runtime functions
# Section 1 / Example of calling some common C runtime functions
This chapter gives examples of calling common C runtime functions from
assembly language.
This chapter gives an example of calling the most common C I/O functions
from assembly language.
There are, by the way, two broad types of functions within the C
runtime. Some are implemented largely in the C runtime itself. Others
that exist in the C runtime act as wrappers for functions implemented
within the OS itself. These are called "system calls".
For the purposes of calling functions in the C runtime, there is no
practical difference between these two types. Note however, there are
ways of calling system calls directly using the `svc` instruction. We
will cover this way of making system calls as well. See
[here](../../more/system_calls/README.md).
## Low level file operations
The following example shows `open()`, `close()`, `read()`, `write()`
and `lseek()`.
This example [program](./file_ops.s) makes use of `open()`, `close()`,
`read()`, `write()` and `lseek()`. These are implemented in the C
runtime as wrappers for system calls.
The program will
* create a file,
* write a small amount of text to it,
* rewind (seek) back to the beginning of the file,
* read back and print the contents of the file and then
* close the file.
A lot of error checking is also implemented (frankly speaking: until we
got bored writing the example).
Doing all of these ballooned the example program to about 200 lines. As
such we won't explain the code line by line but, in compensation, the
code is liberally commented.
Here is just a bit:
```text
/* off_t lseek(int fd, off_t offset, int whence);
*/
seek_zero:
stp x29, x30, [sp, -16]!
mov w0, fd // file descriptor
mov x1, xzr // beginning of file
mov w2, wzr // SEEK_SET - absolute offset
bl lseek
ldp x29, x30, [sp], 16
ret
```
Calling this function rewinds the read / write "head" to position 0.

View file

@ -1,313 +0,0 @@
.arch armv8-a
.file "fo.cpp"
.text
.section .text._ZStanSt13_Ios_FmtflagsS_,"axG",@progbits,_ZStanSt13_Ios_FmtflagsS_,comdat
.align 2
.weak _ZStanSt13_Ios_FmtflagsS_
.type _ZStanSt13_Ios_FmtflagsS_, %function
_ZStanSt13_Ios_FmtflagsS_:
.LFB1316:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str w0, [sp, 12]
str w1, [sp, 8]
ldr w1, [sp, 12]
ldr w0, [sp, 8]
and w0, w1, w0
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1316:
.size _ZStanSt13_Ios_FmtflagsS_, .-_ZStanSt13_Ios_FmtflagsS_
.section .text._ZStorSt13_Ios_FmtflagsS_,"axG",@progbits,_ZStorSt13_Ios_FmtflagsS_,comdat
.align 2
.weak _ZStorSt13_Ios_FmtflagsS_
.type _ZStorSt13_Ios_FmtflagsS_, %function
_ZStorSt13_Ios_FmtflagsS_:
.LFB1317:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str w0, [sp, 12]
str w1, [sp, 8]
ldr w1, [sp, 12]
ldr w0, [sp, 8]
orr w0, w1, w0
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1317:
.size _ZStorSt13_Ios_FmtflagsS_, .-_ZStorSt13_Ios_FmtflagsS_
.section .text._ZStcoSt13_Ios_Fmtflags,"axG",@progbits,_ZStcoSt13_Ios_Fmtflags,comdat
.align 2
.weak _ZStcoSt13_Ios_Fmtflags
.type _ZStcoSt13_Ios_Fmtflags, %function
_ZStcoSt13_Ios_Fmtflags:
.LFB1319:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str w0, [sp, 12]
ldr w0, [sp, 12]
mvn w0, w0
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1319:
.size _ZStcoSt13_Ios_Fmtflags, .-_ZStcoSt13_Ios_Fmtflags
.section .text._ZStoRRSt13_Ios_FmtflagsS_,"axG",@progbits,_ZStoRRSt13_Ios_FmtflagsS_,comdat
.align 2
.weak _ZStoRRSt13_Ios_FmtflagsS_
.type _ZStoRRSt13_Ios_FmtflagsS_, %function
_ZStoRRSt13_Ios_FmtflagsS_:
.LFB1320:
.cfi_startproc
stp x29, x30, [sp, -32]!
.cfi_def_cfa_offset 32
.cfi_offset 29, -32
.cfi_offset 30, -24
mov x29, sp
str x0, [sp, 24]
str w1, [sp, 20]
ldr x0, [sp, 24]
ldr w0, [x0]
ldr w1, [sp, 20]
bl _ZStorSt13_Ios_FmtflagsS_
mov w1, w0
ldr x0, [sp, 24]
str w1, [x0]
ldr x0, [sp, 24]
ldp x29, x30, [sp], 32
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1320:
.size _ZStoRRSt13_Ios_FmtflagsS_, .-_ZStoRRSt13_Ios_FmtflagsS_
.section .text._ZStaNRSt13_Ios_FmtflagsS_,"axG",@progbits,_ZStaNRSt13_Ios_FmtflagsS_,comdat
.align 2
.weak _ZStaNRSt13_Ios_FmtflagsS_
.type _ZStaNRSt13_Ios_FmtflagsS_, %function
_ZStaNRSt13_Ios_FmtflagsS_:
.LFB1321:
.cfi_startproc
stp x29, x30, [sp, -32]!
.cfi_def_cfa_offset 32
.cfi_offset 29, -32
.cfi_offset 30, -24
mov x29, sp
str x0, [sp, 24]
str w1, [sp, 20]
ldr x0, [sp, 24]
ldr w0, [x0]
ldr w1, [sp, 20]
bl _ZStanSt13_Ios_FmtflagsS_
mov w1, w0
ldr x0, [sp, 24]
str w1, [x0]
ldr x0, [sp, 24]
ldp x29, x30, [sp], 32
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1321:
.size _ZStaNRSt13_Ios_FmtflagsS_, .-_ZStaNRSt13_Ios_FmtflagsS_
.section .text._ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_,"axG",@progbits,_ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_,comdat
.align 2
.weak _ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_
.type _ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_, %function
_ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_:
.LFB1350:
.cfi_startproc
stp x29, x30, [sp, -48]!
.cfi_def_cfa_offset 48
.cfi_offset 29, -48
.cfi_offset 30, -40
mov x29, sp
str x0, [sp, 24]
str w1, [sp, 20]
str w2, [sp, 16]
ldr x0, [sp, 24]
ldr w0, [x0, 24]
str w0, [sp, 44]
ldr w0, [sp, 16]
bl _ZStcoSt13_Ios_Fmtflags
mov w1, w0
ldr x0, [sp, 24]
add x0, x0, 24
bl _ZStaNRSt13_Ios_FmtflagsS_
ldr w1, [sp, 16]
ldr w0, [sp, 20]
bl _ZStanSt13_Ios_FmtflagsS_
mov w1, w0
ldr x0, [sp, 24]
add x0, x0, 24
bl _ZStoRRSt13_Ios_FmtflagsS_
ldr w0, [sp, 44]
ldp x29, x30, [sp], 48
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1350:
.size _ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_, .-_ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_
.section .text._ZSt3hexRSt8ios_base,"axG",@progbits,_ZSt3hexRSt8ios_base,comdat
.align 2
.weak _ZSt3hexRSt8ios_base
.type _ZSt3hexRSt8ios_base, %function
_ZSt3hexRSt8ios_base:
.LFB1378:
.cfi_startproc
stp x29, x30, [sp, -32]!
.cfi_def_cfa_offset 32
.cfi_offset 29, -32
.cfi_offset 30, -24
mov x29, sp
str x0, [sp, 24]
mov w2, 74
mov w1, 8
ldr x0, [sp, 24]
bl _ZNSt8ios_base4setfESt13_Ios_FmtflagsS0_
ldr x0, [sp, 24]
ldp x29, x30, [sp], 32
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1378:
.size _ZSt3hexRSt8ios_base, .-_ZSt3hexRSt8ios_base
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,8
.section .rodata
.align 3
.LC0:
.string "test.txt"
.text
.align 2
.global main
.type main, %function
main:
.LFB1729:
.cfi_startproc
stp x29, x30, [sp, -32]!
.cfi_def_cfa_offset 32
.cfi_offset 29, -32
.cfi_offset 30, -24
mov x29, sp
mov w2, 438
mov w1, 66
adrp x0, .LC0
add x0, x0, :lo12:.LC0
bl open
str w0, [sp, 28]
ldr w1, [sp, 28]
adrp x0, :got:_ZSt4cout
ldr x0, [x0, #:got_lo12:_ZSt4cout]
bl _ZNSolsEi
mov x2, x0
adrp x0, :got:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
ldr x1, [x0, #:got_lo12:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_]
mov x0, x2
bl _ZNSolsEPFRSoS_E
adrp x0, _ZSt3hexRSt8ios_base
add x1, x0, :lo12:_ZSt3hexRSt8ios_base
adrp x0, :got:_ZSt4cout
ldr x0, [x0, #:got_lo12:_ZSt4cout]
bl _ZNSolsEPFRSt8ios_baseS0_E
mov w1, 66
bl _ZNSolsEi
mov x2, x0
adrp x0, :got:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
ldr x1, [x0, #:got_lo12:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_]
mov x0, x2
bl _ZNSolsEPFRSoS_E
ldr w0, [sp, 28]
cmp w0, 0
blt .L16
ldr w0, [sp, 28]
bl close
.L16:
mov w0, 0
ldp x29, x30, [sp], 32
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1729:
.size main, .-main
.align 2
.type _Z41__static_initialization_and_destruction_0ii, %function
_Z41__static_initialization_and_destruction_0ii:
.LFB2230:
.cfi_startproc
stp x29, x30, [sp, -32]!
.cfi_def_cfa_offset 32
.cfi_offset 29, -32
.cfi_offset 30, -24
mov x29, sp
str w0, [sp, 28]
str w1, [sp, 24]
ldr w0, [sp, 28]
cmp w0, 1
bne .L20
ldr w1, [sp, 24]
mov w0, 65535
cmp w1, w0
bne .L20
adrp x0, _ZStL8__ioinit
add x0, x0, :lo12:_ZStL8__ioinit
bl _ZNSt8ios_base4InitC1Ev
adrp x0, __dso_handle
add x2, x0, :lo12:__dso_handle
adrp x0, _ZStL8__ioinit
add x1, x0, :lo12:_ZStL8__ioinit
adrp x0, :got:_ZNSt8ios_base4InitD1Ev
ldr x0, [x0, #:got_lo12:_ZNSt8ios_base4InitD1Ev]
bl __cxa_atexit
.L20:
nop
ldp x29, x30, [sp], 32
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE2230:
.size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
.align 2
.type _GLOBAL__sub_I_main, %function
_GLOBAL__sub_I_main:
.LFB2231:
.cfi_startproc
stp x29, x30, [sp, -16]!
.cfi_def_cfa_offset 16
.cfi_offset 29, -16
.cfi_offset 30, -8
mov x29, sp
mov w1, 65535
mov w0, 1
bl _Z41__static_initialization_and_destruction_0ii
ldp x29, x30, [sp], 16
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE2231:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .init_array,"aw"
.align 3
.xword _GLOBAL__sub_I_main
.hidden __dso_handle
.ident "GCC: (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0"
.section .note.GNU-stack,"",@progbits

6
section_1/funcs/seek.c Normal file
View file

@ -0,0 +1,6 @@
#include <unistd.h>
void Foo() {
write(0, 0, 0);
return;
}

21
section_1/funcs/seek.s Normal file
View file

@ -0,0 +1,21 @@
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 13, 0 sdk_version 13, 1
.globl _Foo ; -- Begin function Foo
.p2align 2
_Foo: ; @Foo
.cfi_startproc
; %bb.0:
stp x29, x30, [sp, #-16]! ; 16-byte Folded Spill
mov x29, sp
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
mov w0, #0
mov x1, #0
mov x2, #0
bl _write
ldp x29, x30, [sp], #16 ; 16-byte Folded Reload
ret
.cfi_endproc
; -- End function
.subsections_via_symbols

1
section_1/funcs/test.txt Normal file
View file

@ -0,0 +1 @@
some data

View file

@ -13,27 +13,24 @@ The TL;DR of endianness is this:
* suppose you have a pointer to a `short`
* is the *byte* at the address contained in the pointer the first byte
* is the byte at the address contained in the pointer the first byte
of the `short` or the second?
* this extends to `int` and to `long` as well
That's the essential nugget.
This extends to `int` and to `long` as well.
## Why talk about this?
Endianness is mostly hidden from you. The value of an `int` is the value
you think it is and how the `int` is constructed isn't even on your
you think it is and how the `int` is constructed probably isn't on your
radar.
Endianness is a big deal for people who code network applications where
early on a standard was required to determine which byte of an `int` is
early on, a standard was required to determine which byte of an `int` is
the first one transmitted over the wire. Network Byte Order is big
endian. Unfortunately, most of todays very popular processors are
little endian.
endian. Most of todays very popular processors are little endian.
For us assembly language coders, endianness comes into play when we're
using our debuggers. After all, when we examine memory, memory is a
For us assembly language coders, endianness especially comes into play
when we're using our debuggers. When we examine memory, memory is a
linear stream of bytes. Without understanding endianness, you might be
confused about what you're seeing.
@ -48,8 +45,10 @@ end.
Please read the entirety of Gulliver's Travels keeping in mind how
absolutely nasty Swift's portrayal of 18th century politics can be. You
won't be disappointed. And no, the classic cartoon version, Max
Fleischer's 1939 masterpiece, doesn't do the book justice.
won't be disappointed.
The classic cartoon version, Max Fleischer's 1939 masterpiece, doesn't
do the book justice.
## How do the terms apply?
@ -59,7 +58,7 @@ significant byte lives. Thus the terms...
If the most significant byte comes first, the architecture is said to
be big-endian. If the least significant byte comes first, it is little
endian.
endian. There's a little more to it than that, but not much.
**Notice I have not discussed strings.**
@ -122,17 +121,17 @@ string Dump(T & i) { // 15
} // 24
```
First, you might not be familiar with templated functions. Notice
line 14 tells the compiler that the next function is templated and that
`T` be take on the value matching the parameter that is actually given
to the function.
You might not be familiar with templated functions. Notice line 14 tells
the compiler that the next function is templated and that `T` will stand
in for the type matching the parameter that is actually given to the
function.
Thus, at compile time, the compiler write a different function for each
Thus, at compile time, the compiler writes a different function for each
of `int`, `short` and `long`. When the compiler gets to the `sizeof()`
on line 20, the size of the "right" type is taken.
Using the templated approach we need write this function only once
rather than three times for each of `int`, `short` and `long`.
Using the templated approach, we need write this function only once
rather than three times (for each of `int`, `short` and `long`).
## Output on a little endian machine
@ -169,6 +168,7 @@ i64: 89abcdef01234567
```
Notice the values for `i16` and `i32` match the right hand column above.
The value for `i64` is borked in that we specified it in the C code as a
`long`. We then tried specifying the `long` as a `long long`. Apparently
there is little support for 64 bit numbers on this ancient but