mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-23 23:46:48 +08:00
added short chapter on variadic functions
This commit is contained in:
parent
69c2a4c465
commit
f3f3f84b3c
5 changed files with 153 additions and 0 deletions
|
|
@ -303,6 +303,7 @@ What would a book about assembly language be without bit bashing?
|
||||||
| --- | [Under the hood: System Calls](./more/system_calls/README.md) | [Link](./more/system_calls/README.pdf) |
|
| --- | [Under the hood: System Calls](./more/system_calls/README.md) | [Link](./more/system_calls/README.pdf) |
|
||||||
| --- | [Apple Silicon](./more/apple_silicon/README.md) | [Link](./more/apple_silicon/README.pdf) |
|
| --- | [Apple Silicon](./more/apple_silicon/README.md) | [Link](./more/apple_silicon/README.pdf) |
|
||||||
| --- | [Apple / Linux Convergence](./macros) | [Link](./macros/README.pdf) |
|
| --- | [Apple / Linux Convergence](./macros) | [Link](./macros/README.pdf) |
|
||||||
|
| --- | [Variadic Functions](./more/varargs/README.md) | [Link](./more/varargs/README.pdf) |
|
||||||
|
|
||||||
## Macro Suite
|
## Macro Suite
|
||||||
|
|
||||||
|
|
|
||||||
BIN
README.pdf
BIN
README.pdf
Binary file not shown.
69
more/varargs/README.md
Normal file
69
more/varargs/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# varargs
|
||||||
|
|
||||||
|
Mentioned elsewhere, how Apple and Linux handle variadic functions are
|
||||||
|
quite different. In this chapter we will explore how variadic functions
|
||||||
|
work by looking at an example in C++. Then, we'll review the differences
|
||||||
|
between Apple and Linux.
|
||||||
|
|
||||||
|
## How Variadic Functions Determine Number of Parameters
|
||||||
|
|
||||||
|
The TL;DR is that something among the *initial* arguments tells the
|
||||||
|
function how many other arguments to expect. Commonly, it is the very
|
||||||
|
first argument that contains this information but it is possible to
|
||||||
|
encode this information in a parameter other than the first as long as
|
||||||
|
no optional parameter precedes it.
|
||||||
|
|
||||||
|
For example, take our friend and yours: `printf`. The first argument is
|
||||||
|
a template or formatting string that may contain zero or more instances
|
||||||
|
of placeholders. For example:
|
||||||
|
|
||||||
|
| Sample | Expected Arguments |
|
||||||
|
| ------ | ------------------ |
|
||||||
|
| `printf("Foo");` | None |
|
||||||
|
| `printf("%%d");` | None - notice the double % |
|
||||||
|
| `printf("%d %ld");` | An int and a long |
|
||||||
|
| `printf("%p %f");` | A pointer and a double |
|
||||||
|
|
||||||
|
The `printf` function figures out how many arguments to expect and what
|
||||||
|
types to expect from the first parameter.
|
||||||
|
|
||||||
|
## Writing Variadic Functions in C or C++
|
||||||
|
|
||||||
|
You'll need to include `cstdarg` in C++ or `stdarg.h` in C++ or C.
|
||||||
|
|
||||||
|
Then, you'll use the **macros** `va_start()` and `va_end()` plus a
|
||||||
|
variable of type `va_list`.
|
||||||
|
|
||||||
|
* `va_start()` is used to initialize the `va_list` variable with the
|
||||||
|
address of the last required argument. This enables the variadic support
|
||||||
|
functions to find the variadic parameters on the stack or in registers.
|
||||||
|
|
||||||
|
* `va_arg()`, another macro fetches the next variadic argument. The type
|
||||||
|
of the argument is given as a parameter to `va_arg()`.
|
||||||
|
|
||||||
|
* `va_end()` is used to clean up the internal data structure allocated
|
||||||
|
for you, behind your back.
|
||||||
|
|
||||||
|
Take a look at `Simple()` in [this source code](./main.cpp).
|
||||||
|
|
||||||
|
For a more sophisticated example, look at `LessSimple()` in the same
|
||||||
|
file for something closer to `printf()`.
|
||||||
|
|
||||||
|
The above referenced source code contains ample commenting to explain
|
||||||
|
what it is doing.
|
||||||
|
|
||||||
|
## Differences Between Apple and Linux
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
Linux uses the first 8 scratch registers as normal. If you need to
|
||||||
|
specify more than 8 arguments, the ninth and beyond go on the stack. It
|
||||||
|
is up to you to determine how many arguments to expect (which you must
|
||||||
|
do anyway) to determine where to find them.
|
||||||
|
|
||||||
|
### Apple
|
||||||
|
|
||||||
|
Apple puts the first argument in `x0` as usual but all remaining
|
||||||
|
arguments go on the stack in right to left order. There are some
|
||||||
|
other restrictions - it is best to refer to [this](https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
|
||||||
|
) cryptically written document.
|
||||||
BIN
more/varargs/README.pdf
Normal file
BIN
more/varargs/README.pdf
Normal file
Binary file not shown.
83
more/varargs/main.cpp
Normal file
83
more/varargs/main.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/* va_start() and va_end() are MACROS not functions themselves. The implementation
|
||||||
|
of these are architecture dependent. A common implementation is that va_start()
|
||||||
|
takes the address of the last non-variadic variable which identifies where on
|
||||||
|
the stack the following arguments can be found. The format string (or whatever)
|
||||||
|
determines how to advance the pointer. Some architectures are quite complicated.
|
||||||
|
|
||||||
|
For example, here is a comment from the implementation of va_start:
|
||||||
|
//
|
||||||
|
* In GNU the stack is not necessarily arranged very neatly in order to
|
||||||
|
* pack shorts and such into a smaller argument list. Fortunately a
|
||||||
|
* neatly arranged version is available through the use of __builtin_next_arg.
|
||||||
|
//
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Simple() - a first example of a variadic function in which all
|
||||||
|
parameters are assumed to be pointers to char.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Simple(uint32_t count, ...) {
|
||||||
|
int digits_needed = (int) ceil(log10((double) count)) + 1;
|
||||||
|
va_list args;
|
||||||
|
va_start(args, count);
|
||||||
|
for (uint32_t index = 0; index < count; index++) {
|
||||||
|
cout << "Index: [" << setw(digits_needed) << index << "] " << va_arg(args, char *) << endl;
|
||||||
|
}
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LessSimple() is a bit more like printf in that multiple data types are
|
||||||
|
supported, passed in in a format string. This function is an important
|
||||||
|
lesson in the types of data types that are supported by varargs. Note
|
||||||
|
that a float is passed as a double and char and short are passed by as
|
||||||
|
ints. The patterns is that if a data type is automatically promotable,
|
||||||
|
you must handle it as the highest level of promotion.
|
||||||
|
|
||||||
|
For example, a char is an int so it must be handled like an int. Floats
|
||||||
|
are automatically promotable to double, so they must be supported as
|
||||||
|
doubles. An int is not automatically promoted to long, so longs can
|
||||||
|
be dealt with directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void LessSimple(string fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
for (uint32_t index = 0; index < fmt.size(); index++) {
|
||||||
|
switch (fmt.at(index)) {
|
||||||
|
case 'i':
|
||||||
|
cout << "i: " << va_arg(args, int64_t) << endl;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
cout << "f: " << (float) va_arg(args, double) << endl;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
cout << "d: " << va_arg(args, double) << endl;
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
cout << "c: " << (int8_t) va_arg(args, int32_t) << endl;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
cout << "s: " << (int16_t) va_arg(args, int32_t) << endl;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Simple(5, "Foo", "is", "a", "Variadic", "Function");
|
||||||
|
Simple(13, "Foo", "is", "a", "Variadic", "Function", "which means", "it", "can", "take", "any", "number", "of", "args");
|
||||||
|
LessSimple(string("ifdcs"), 99L, 4.4f, 4.4, 'c', (int16_t) 30000);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue