//
unteren 8 Bits von esp, ebp, esi, edi
jetzt adressierbar
8 neue GPR hinzugefügt und nach Größe beschriftet (d
- DWORD, w
- WORD, b
- BYTE)
EFLAGS auch auf 64-bit erweitert
(!) bei schreibendem Zugriff auf 32-bit Register (und nur auf 32-bit Register, nicht auf kleinere) werden die oberen 32-bits von dem dazugehörigen 64-bit Register auf 0 gesetzt
mov eax, 0xffffffff
führt dazu, dass im Register rax
jetzt 0x00000000ffffffff
liegtmov
erlaubt
mov rax, 0xaaaabbbbccccdddd
erlaubtadd rax, 0xaaaabbbbccccdddd
nicht erlaubtadd rax, 0xffffffff
führt dazu, dass man eigentlich 0xffffffffffffffff
auf rax
“addiert”r
: Registeroperand mit Größe in Bitsm
: Speicheroperandimm
: Immediatesyscall
rdi, rsi, rdx, r10, r8, r9
rax
weitergeleitetrcx
gespeichertrax
geschrieben
-4095
(inkl.) und -1
(inkl.) bedeutet, dass ein Fehler aufgetreten ist_start
) (im Datei-Header)readelf
anzeigenexecve
./testprog arg1 arg2 ...
argc
: Argument Count (hier zeigt rsp
!)argv[argc]
: Argument Vector
argv[0]
: Programmpfadargv[1], ... , argv[argc-1]
: Programmargumentewrite
und stdout
ret
oder keine weiteren Instruktionen reichen nicht aus_exit
(60) oder exit_group
(231)
stdint.h
)char
und _Bool
unsigned
wird angegeben, dass das Integer nicht vorzeichenbehaftet ist
unsigned int
kann int
weggelassen werdensigned
_Bool
s sind standardmäßig 0 und werden bei der Zuweisung eines Wertes ungleich 0 wird dieser automatisch zu 1 konvertiertunsigned long l = 42; signed char c = -1; unsigned i = UINT_MAX; // impl. unsigned int
unsigned long l = 42;
signed char c = -1;
unsigned i = UINT_MAX; // impl. unsigned int
void
*
hervorgerufenvoid foo ( int n ) ; // <-- Deklaration void foo ( int n ) { // <-- Definition ... } void bar ( unsigned n ) { // <-- Deklaration + Definition ... }
void foo ( int n ) ; // <-- Deklaration
void foo ( int n ) { // <-- Definition
...
}
void bar ( unsigned n ) { // <-- Deklaration + Definition
...
}
void
als Parameterlisteint foo ( void ) ; // <-- RICHTIG : akzeptiert keine Parameter int bar () ; // <-- FALSCH : kann mit beliebigen Parametern // definiert / aufgerufen werden
int foo ( void ) ; // <-- RICHTIG : akzeptiert keine Parameter
int bar () ; // <-- FALSCH : kann mit beliebigen Parametern
// definiert / aufgerufen werden
return
und Rückgabewert (optional und ohne Rückgabewert bei void
-Funktionen)void foo ( unsigned n , short s ) { ... return; // <-- kein Rückgabewert, hier optional } int bar ( long long multi_word_parameter ) { ... return -42; // <-- int als Rückgabewert }
void foo ( unsigned n , short s ) {
...
return; // <-- kein Rückgabewert, hier optional
}
int bar ( long long multi_word_parameter ) {
...
return -42; // <-- int als Rückgabewert
}
main
-Funktionmain
und endet, sobald main
fertig istEXIT_SUCCESS
und EXIT_FAILURE
(unabhängig von Implementierung)int main ( void ) { // keine Parameter ... return EXIT_SUCCESS ; // 0 } int main ( int argc , const char ** argv ) { // 2 Parameter (Anz. der Kommandozeilenargumente in argc und die Argumente als Array von Strings in argv) // erstes Kommandozeilenargument ist üblicherweise Name des Programms ... return 1; // Implementation-defined error code }
int main ( void ) { // keine Parameter
...
return EXIT_SUCCESS ; // 0
}
int main ( int argc , const char ** argv ) { // 2 Parameter (Anz. der Kommandozeilenargumente in argc und die Argumente als Array von Strings in argv)
// erstes Kommandozeilenargument ist üblicherweise Name des Programms
...
return 1; // Implementation-defined error code
}
TYPE_NAME [ = VALUE ]; // Deklaration const TYPE_NAME = VALUE; // Deklaration und Zuweisung einer konstanten Variable
TYPE_NAME [ = VALUE ]; // Deklaration
const TYPE_NAME = VALUE; // Deklaration und Zuweisung einer konstanten Variable
const int a; a = 4; // COMPILER-FEHLER
const int a;
a = 4; // COMPILER-FEHLER
const
mit Pointer!const TYPE* PTR [= ADDR ]; // Pointer auf konstante Daten TYPE* const PTR = ADDR ; // Konstanter Pointer auf // variable Daten const TYPE* const PTR = ADDR ; // Konstanter Pointer auf // konstante Daten
const TYPE* PTR [= ADDR ]; // Pointer auf konstante Daten
TYPE* const PTR = ADDR ; // Konstanter Pointer auf
// variable Daten
const TYPE* const PTR = ADDR ; // Konstanter Pointer auf
// konstante Daten
void foo () { int a = 42; } void bar () { int b = a ; // FEHLER : a ist nur in foo Sichtbar { int c = b ; // OK } int d = b ; // OK int e = c ; // FEHLER : c ist hier nicht mehr sichtbar }
void foo () {
int a = 42;
}
void bar () {
int b = a ; // FEHLER : a ist nur in foo Sichtbar
{
int c = b ; // OK
}
int d = b ; // OK
int e = c ; // FEHLER : c ist hier nicht mehr sichtbar
}
int i ; i = -2; // negative Konstante im Dezimalsystem i = 0xDEADBEEF ; // Konstante im Hexadezimalsystem i = 011; // Konstante im Oktalsystem ( führende Null !!) i = 'A'; // "character literal" - hier wird automatisch // der entsprechende numerische Wert für den // Buchstaben "A" eingefügt // Siehe 'man 7 ascii' für eine Tabelle. double d; d = 2.0; // double - Konstante d = 2.0 f ; // float - Konstante int a = 2.0 // gibt a den Wert 2 als Integer
int i ;
i = -2; // negative Konstante im Dezimalsystem
i = 0xDEADBEEF ; // Konstante im Hexadezimalsystem
i = 011; // Konstante im Oktalsystem ( führende Null !!)
i = 'A'; // "character literal" - hier wird automatisch
// der entsprechende numerische Wert für den
// Buchstaben "A" eingefügt
// Siehe 'man 7 ascii' für eine Tabelle.
double d;
d = 2.0; // double - Konstante
d = 2.0 f ; // float - Konstante
int a = 2.0 // gibt a den Wert 2 als Integer
unsigned a = 42;
Operation | direkte Zuweisung | Bedeutung | Ergebnis | Ergebnis-Typ |
---|---|---|---|---|
a = a + 42 |
a += 42 |
Addition | 84 | unsigned |
a = a - 42 |
a -= 42 |
Subtraktion | 0 | unsigned |
a = a * 42 |
a *= 42 |
Multiplikation | 1764 | unsigned |
a = a / 5 |
a /= 5 |
Division | 8 | unsigned |
a = a % 5 |
a %= 5 |
Modulo | 2 | unsigned |
a = a && 0 |
- | logisches UND | 0 | int |
a = a || 0 |
- | logisches ODER | 1 | int |
a = !a |
- | logisches NOT | 0 | int |
a = a << 2 |
a <<= 2 |
Linksshift | 168 | unsigned |
a = a >> 2 |
a >>= 2 |
Rechtsshift | 10 | unsigned |
a = a & 0x3 |
a &= 0x3 |
bitweises UND | 2 | unsigned |
a = a | 0x5 |
a |= 0x5 |
bitweises ODER | 47 | unsigned |
a = a ^ 0xff |
a ^= 0xff |
bitweises XOR | 213 | unsigned |
a = ~a |
- | bitweises NOT | 4294967253 | unsigned |
if (x > 2.4) { ... } else if (x < 0 x123456789) { // else if branch optional ... } else { // else branch optional ... }
if (x > 2.4) {
...
} else if (x < 0 x123456789) { // else if branch optional
...
} else { // else branch optional
...
}
// while while (n-- > 0) { ... if ( x == y ) { break; // beendet schleife } ... } // do...while // code im schleifenkörper wird mindestens 1 mal ausgeführt do { ... if ( x == y ) { continue; // bricht aktuelle iteration der schleife } ... } while (--n > 0) ;
// while
while (n-- > 0) {
...
if ( x == y ) {
break; // beendet schleife
}
...
}
// do...while
// code im schleifenkörper wird mindestens 1 mal ausgeführt
do {
...
if ( x == y ) {
continue; // bricht aktuelle iteration der schleife
}
...
} while (--n > 0) ;
// Variante 0 - standard for (int i = 0; i < 42; i++) { ... } // Variante 1 - init. mehrere Variablen des gleichen Typs for (int i = 0, j = 0; ...) { ... } // Variante 2 - bereits deklarierte Variable int k; for (k = 0; k < 42; k++) { ... } // Variante 3 - Schleife ohne Abbruchbedingung for (;;) { ... } // analog zu "while (1) { ... }"" // Variante 4 - i-- im 2. Teil for (unsigned i = n ; i-- > 0;) { ... }
// Variante 0 - standard
for (int i = 0; i < 42; i++) { ... }
// Variante 1 - init. mehrere Variablen des gleichen Typs
for (int i = 0, j = 0; ...) { ... }
// Variante 2 - bereits deklarierte Variable
int k;
for (k = 0; k < 42; k++) { ... }
// Variante 3 - Schleife ohne Abbruchbedingung
for (;;) { ... } // analog zu "while (1) { ... }""
// Variante 4 - i-- im 2. Teil
for (unsigned i = n ; i-- > 0;) { ... }
switch (x) { case -42: ... break; case 'A': ... /* fall through */ case 'B': ... break; default: ... break; }
switch (x) {
case -42:
...
break;
case 'A':
...
/* fall through */
case 'B':
...
break;
default:
...
break;
}
cpp
#
#define NUMBER 42 // Ersetze NUMBER durch 42 #define MYNUM 2 + 3 // Ersetze MYNUM durch 2 + 3 int a = NUMBER ; // = 42 int b = MYNUM * 2; // = 2 + 3 * 2 = 8 ( nicht (!) 10) #undef MYNUM // Mache Definition ruckgängig
#define NUMBER 42 // Ersetze NUMBER durch 42
#define MYNUM 2 + 3 // Ersetze MYNUM durch 2 + 3
int a = NUMBER ; // = 42
int b = MYNUM * 2; // = 2 + 3 * 2 = 8 ( nicht (!) 10)
#undef MYNUM // Mache Definition ruckgängig
#define MYFLAG 0 #if MYFLAG const char c = ’A ’; #else const char c = ’B ’; #endif #if 0 int x = 42; // auskommentierter Code #endif
#define MYFLAG 0
#if MYFLAG
const char c = ’A ’;
#else
const char c = ’B ’;
#endif
#if 0
int x = 42; // auskommentierter Code
#endif
#include
-Direktiven#include <system_header.h> // Copy - paste Inhalte von system_header.h an diese Stelle #include "local_header.h" // Copy - paste Inhalte von local_header.h an diese Stelle
#include <system_header.h> // Copy - paste Inhalte von system_header.h an diese Stelle
#include "local_header.h" // Copy - paste Inhalte von local_header.h an diese Stelle
<>
wenn C-Standardbibliothek""
wenn Datei im Rahmen des eigenen Projekts// foo.h void func(void); // foo.c #include "foo.h" void func(void) {...} // main.c #include "foo.h" int main(void) { func(); return 0; }
// foo.h
void func(void);
// foo.c
#include "foo.h"
void func(void) {...}
// main.c
#include "foo.h"
int main(void) {
func();
return 0;
}
// foo.h void func(void); // foo.c #include "foo.h" static void helper(void) {...} void func(void) {...} // main.c #include "foo.h" static void helper(void){...} int main(void) { func(); return 0; }
// foo.h
void func(void);
// foo.c
#include "foo.h"
static void helper(void) {...}
void func(void) {...}
// main.c
#include "foo.h"
static void helper(void){...}
int main(void) {
func();
return 0;
}
Problem: helper
wird sowohl von main.c
, als auch von foo.c
definiert, so dass static
notwendig ist
mittels Storage-Class-Specifier extern
(impl.) und static
extern
: Funktionen sind extern für andere C-Dateien sichtbar und können genutzt werdenstatic
: beschränkt Sichtbarkeit nur auf eigene Datei (in der die Funktion deklariert / definiert wird)import
-System, die Standardbibliothek wird über Header benutzt// Systemweite Bibliotheksheader # include < stdio.h > // Input - Output Funktionalität # include < string.h > // Funktionen zur Stringmanipulation # include < stddef.h > // Definiert u.a. size_t (unsigned Typ, max. Größe von Objekten im Speicher). // m.a.W. gibt es kein Speicherobjekt mit einer größeren Größe als size_t // Bereits indirekt durch stdio.h eingebunden. // Lokaler Header des Projekts # include "myheader.h"
// Systemweite Bibliotheksheader
# include < stdio.h > // Input - Output Funktionalität
# include < string.h > // Funktionen zur Stringmanipulation
# include < stddef.h > // Definiert u.a. size_t (unsigned Typ, max. Größe von Objekten im Speicher).
// m.a.W. gibt es kein Speicherobjekt mit einer größeren Größe als size_t
// Bereits indirekt durch stdio.h eingebunden.
// Lokaler Header des Projekts
# include "myheader.h"
stdint.h
und stdbool.h
stdint.h
definiert fixed-width Integer TypenSigned | Unsigned | Größe |
---|---|---|
int8_t |
uint8_t |
8 Bit |
int16_t |
uint16_t |
16 Bit |
int32_t |
uint32_t |
32 Bit |
int64_t |
uint64_t |
64 Bit |
stdbool.h
enthält syntaktischen Zucker für boolsche Werte
bool
für _Bool
true
und false
für 1 und 0printf
// Hello World in C # include < stdio .h > // <-- Wir brauchen die Deklaration von printf int main (void) { // Schreibe "Hello World!" gefolgt von einer Newline printf ("Hello World \n"); return 0; }
// Hello World in C
# include < stdio .h > // <-- Wir brauchen die Deklaration von printf
int main (void) {
// Schreibe "Hello World!" gefolgt von einer Newline
printf ("Hello World \n");
return 0;
}
printf
bietet viele Ausgabemöglichkeitenunsigned a = 0x42; printf(" The value of a is : % u\n", a);
unsigned a = 0x42;
printf(" The value of a is : % u\n", a);
Specifier | Argumenttyp | Ausgabe |
---|---|---|
d | signed int | Dezimaldarstellung |
u | unsigned int | Dezimaldarstellung |
x / X | unsigned int | Hex-Darstellung |
c | signed int | ASCII-Zeichen |
s | const char* |
String |
%ld
für einen long int
printf("%s", "test");
kann es möglicherweise zu keinem Output führen, da ohne Newline die Ausgabe ggf. in den Zwischenspeicher kommtobjdump
objdump PROGNAME -d -M intel | less
-d
: disassemble-M intel
: Intel-Syntax statt AT&Tless
: übersichtlichergcc -O2 -o gaussO2 gauss.c
-O0
: keine Optimierung (default)
-O1
: “Optimize”
-O2
: “Optimize even more”
-O3
: “Optimize yet more”
-Ofast
: -O3
+ float-Optimerungen
-Os
: wie -O2
, aber möglichst kleine Ausgabedatei-Og
: wie -O1
, stattdessen gut debugbarer Codegcc
: -O0 -g debug.c
-O0
: for obvious reasons-g
: generiert alle notwendigen Dateien für Debugging (nur mit Debugging benutzen!)gcc -o debug -O0 -Wall -Wextra -g debug.c
gdb PROGNAME
run / r argv[0] argv[1]...
: führt Programm (mit Argumenten) aus
break / b PROGNAME:LINENUM
/ b LABEL
: setzt Breakpoint an Zeilennummer / Label / Funktion im Code
print / p VARIABLE / $REGISTER
: zeige Variablen- bzw. Registerinhalt an
p (len == $rax)
: bestätigt, dass len
und $rax
entsp. Calling Convention denselben Wert habenp POINTER
: gibt Adresse von Pointer ausx ADDR
: zeigt Speicherinhalt an Addresse
x ARRAY_NAME
: zeigt Addresse und erstes Element eines Arraysx (ARRAY_NAME + 1)
: zeigt 2. Element in Arrayx/LEN ARRAY_NAME
: zeigt alle Elemente des Arraysx/a
: gibt Adresse ausx/s
: gibt String aus (alle Zeichen bis zum Ende des Strings)x/d
: gibt Dezimalzahl ausstep / s
: führt nächste Zeile im Code aus und stoppt erneut Ausführung (step-into)
stepi / si
: führt einzelne Assembler-Instruktion aus
s
und si
bei ASM-Debugging identischinfo break
: zeigt alle Breakpointsdelete
: löscht alle Breakpoints
delete BREAKPOINT_NUM
: löscht bestimmten Breakpointdisable / enable BREAKPOINT_NUM
: temporäres (de)aktivieren eines Breakpointsnext / n
: springt direkt in die nächste Zeile nach Funktionsaufruf (step-over)continue / c
: setzt Programmausführung bis zum nächsten Breakpoint fortfinish
: setzt Ausführung bis zum Verlassen der aktuellen Funktion fortquit
: beendet Debugging und löscht alle Breakpoints~./gdbinit
: zeigt Inhalte aller General-Purpose-Register an(gdb) p argc $5 = 3 (gdb) x/3a argv 0x...: 0x... 0x... 0x...: 0x... (gdb) x/s argv[1] 0x...: "hello"
(gdb) p argc
$5 = 3
(gdb) x/3a argv
0x...: 0x... 0x...
0x...: 0x...
(gdb) x/s argv[1]
0x...: "hello"
# include <stdint.h> uint64_t fib1 ( uint64_t n ) { if(n <= 1) { return n ; // fib (0) = 0 , fib (1) = 1 } return fib1(n - 1) + fib1(n - 2); }
# include <stdint.h>
uint64_t fib1 ( uint64_t n ) {
if(n <= 1) {
return n ; // fib (0) = 0 , fib (1) = 1
}
return fib1(n - 1) + fib1(n - 2);
}
(Komplexe) Laufzeit eines Algorithmus: f(n)
f(n) wächst vergleichbar zu einer “simplen” Funktion K(n)
beste Optimierung: Laufzeitklasse des Algorithmus ist entscheidend
uint64_t fib2 ( uint64_t n ) { if (n == 0) { // base case return 0; } if (n > 93) { // integers greater than 93 cannot be represented using uint64_t return UINT64_MAX ; } uint64_t a = 0; uint64_t b = 1; uint64_t i = 1; for (; i < n ; i++) { uint64_t tmp = b ; b += a ; a = tmp ; } return b ; }
uint64_t fib2 ( uint64_t n ) {
if (n == 0) { // base case
return 0;
}
if (n > 93) { // integers greater than 93 cannot be represented using uint64_t
return UINT64_MAX ;
}
uint64_t a = 0;
uint64_t b = 1;
uint64_t i = 1;
for (; i < n ; i++) {
uint64_t tmp = b ;
b += a ;
a = tmp ;
}
return b ;
}
// All 94 64 - bit fibonacci numbers ( n = 0 ,... ,93) uint64_t lut[] = {0 ,1 ,1 ,2 ,3 ,5 ,8 ,13 ,21 ,34 ,55 ,89 ,144 ,233 ,377, ..., 7540113804746346429 ,12200160415121876738}; uint64_t fib3 (uint64_t n) { if (n > 93) { return UINT64_MAX; } return lut[n]; }
// All 94 64 - bit fibonacci numbers ( n = 0 ,... ,93)
uint64_t lut[] = {0 ,1 ,1 ,2 ,3 ,5 ,8 ,13 ,21 ,34 ,55 ,89 ,144 ,233 ,377, ..., 7540113804746346429 ,12200160415121876738};
uint64_t fib3 (uint64_t n) {
if (n > 93) {
return UINT64_MAX;
}
return lut[n];
}
# include < stdint .h > // LUT for n = {0 ,16 ,32 ,48 ,64 ,80} uint64_t lut0 [] = {0 ,987 ,2178309 ,4807526976 ,10610209857723 , 23416728348467685}; // LUT for n = {1 ,17 ,33 ,49 ,65 ,81} uint64_t lut1 [] = {1 ,1597 ,3524578 ,7778742049 ,17167680177565 , 37889062373143906}; uint64_t fib4 ( uint64_t n ) { // case for numbers exceeding 64-bit limit if ( n > 93) { return UINT64_MAX ; } // calculate index in interval to find first 2 numbers of interval uint64_t index = n / 16; uint64_t a = lut0 [ index ]; uint64_t b = lut1 [ index ]; // get pos. of first fibonacci number in interval index *= 16; if ( index == n ) // if number is already saved, return it return a ; // calculate fibonacci number using standard loop index++; for (; index < n ; index ++) { uint64_t tmp = b ; b += a ; a = tmp ; } return b ; }
# include < stdint .h >
// LUT for n = {0 ,16 ,32 ,48 ,64 ,80}
uint64_t lut0 [] = {0 ,987 ,2178309 ,4807526976 ,10610209857723 , 23416728348467685};
// LUT for n = {1 ,17 ,33 ,49 ,65 ,81}
uint64_t lut1 [] = {1 ,1597 ,3524578 ,7778742049 ,17167680177565 , 37889062373143906};
uint64_t fib4 ( uint64_t n ) {
// case for numbers exceeding 64-bit limit
if ( n > 93) {
return UINT64_MAX ;
}
// calculate index in interval to find first 2 numbers of interval
uint64_t index = n / 16;
uint64_t a = lut0 [ index ];
uint64_t b = lut1 [ index ];
// get pos. of first fibonacci number in interval
index *= 16;
if ( index == n ) // if number is already saved, return it
return a ;
// calculate fibonacci number using standard loop
index++;
for (; index < n ; index ++) {
uint64_t tmp = b ;
b += a ;
a = tmp ;
}
return b ;
}
struct
union
: Zugriff auf gleichen Speicherbereich über unterschiedliche Identifiersizeof
-Operatorsizeof(char) == 1
)size_t size; size = sizeof(char); // 1 size = sizeof(size_t); // 8 size = sizeof(void *); // 8 size_t a = sizeof(size_t); size_t b = sizeof(a); // a == b, da 'a' vom Typ 'size_t' ist
size_t size;
size = sizeof(char); // 1
size = sizeof(size_t); // 8
size = sizeof(void *); // 8
size_t a = sizeof(size_t);
size_t b = sizeof(a);
// a == b, da 'a' vom Typ 'size_t' ist
int
char
&
: nimmt Adresse eines Objekts*
: dereferenziert Pointerint i = 0; int* i_ptr = &i; // declare pointer variable of type int (int*) that points to address of i (&i) int i_new = *i_ptr; // deref. pointer, i_new == i
int i = 0;
int* i_ptr = &i; // declare pointer variable of type int (int*) that points to address of i (&i)
int i_new = *i_ptr; // deref. pointer, i_new == i
int*
s wird 4 darauf addiert// Pointerarithmetik int i = 0; int* i_ptr = &i; // 0x1234 i_ptr++; // 0x1238 i_ptr -= 2; // 0x1230 (undefined behavior, i_ptr points to element outside of array)
// Pointerarithmetik
int i = 0;
int* i_ptr = &i; // 0x1234
i_ptr++; // 0x1238
i_ptr -= 2; // 0x1230 (undefined behavior, i_ptr points to element outside of array)
ptr[0] == *ptr
ptr = &ptr[0]
ptr[3] == *(ptr + 3)
ptr + 3 = &ptr[3]
ptr[index] == index[ptr]
void
-Pointer und implizite Pointer-Castsvoid
-Pointer nicht dereferenzierbarvoid
-Pointers ist undefiniertvoid
-Pointer und Pointer eines anderen Typs ist implizitint* i_ptr = ...; void* v_ptr = i_ptr; // every pointer can become a void-pointer int* i_ptr2 = v_ptr; // a void-pointer can become any pointer int i = *v_ptr; // compiler error!
int* i_ptr = ...;
void* v_ptr = i_ptr; // every pointer can become a void-pointer
int* i_ptr2 = v_ptr; // a void-pointer can become any pointer
int i = *v_ptr; // compiler error!
char*
)
int* i_ptr = ...; char* c_ptr = (char*) i_ptr; // zugriff möglich short* s_ptr = (short*) i_ptr; // zugriff undefined long* l_ptr = (long*) i_ptr; // cast undefined wegen strengeren alignment-anforderungen von long
int* i_ptr = ...;
char* c_ptr = (char*) i_ptr; // zugriff möglich
short* s_ptr = (short*) i_ptr; // zugriff undefined
long* l_ptr = (long*) i_ptr; // cast undefined wegen strengeren alignment-anforderungen von long
arr[0]
oder *arr
)int arr[3]
int arr[3] = {1,2,3}
int arr[] = {1,2,3}
sizeof
int arr[3] = {1,2,3}; size_t arr_len = sizeof(arr) / sizeof(arr[0]); // 12 / 4 = 3
int arr[3] = {1,2,3};
size_t arr_len = sizeof(arr) / sizeof(arr[0]); // 12 / 4 = 3
void func(size_t size){ char buf[size]; }
// pointersyntax void func ( int * arr , size_t arr_length ) { // sizeof(arr) ermittelt NICHT größe des arrays! for ( size_t i = 0; i < arr_length ; i ++) { printf ( " % d \ n " , arr [ i ]) ; } } int main ( void ) { int arr [3] = {1 ,2 ,3}; func ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ; // ... } // arraysyntax // parameter sind immer noch pointer! void func1 ( int arr [ arr_length ] , size_t arr_length ) { for ( size_t i = 0; i < arr_length ; i ++) { printf ( " % d \ n " , arr [ i ]) ; } } void func2 ( int arr [3]) { // sizeof(arr) würde immer noch größe des pointers zurückgeben! for ( size_t i = 0; i < 3; i ++) { printf ( " % d \ n " , arr [ i ]) ; } } int main ( void ) { int arr [3] = {1 ,2 ,3}; func1 ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ; func2 ( arr ) ; // ... }
// pointersyntax
void func ( int * arr , size_t arr_length ) { // sizeof(arr) ermittelt NICHT größe des arrays!
for ( size_t i = 0; i < arr_length ; i ++) {
printf ( " % d \ n " , arr [ i ]) ;
}
}
int main ( void ) {
int arr [3] = {1 ,2 ,3};
func ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ;
// ...
}
// arraysyntax
// parameter sind immer noch pointer!
void func1 ( int arr [ arr_length ] , size_t arr_length ) {
for ( size_t i = 0; i < arr_length ; i ++) {
printf ( " % d \ n " , arr [ i ]) ;
}
}
void func2 ( int arr [3]) { // sizeof(arr) würde immer noch größe des pointers zurückgeben!
for ( size_t i = 0; i < 3; i ++) {
printf ( " % d \ n " , arr [ i ]) ;
}
}
int main ( void ) {
int arr [3] = {1 ,2 ,3};
func1 ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ;
func2 ( arr ) ; // ...
}
char
-Array
\0
als Terminalchar str[] = {'t','u','x','\0'};
char str[] = "tux";
(äquiv. zur vorherigen Variante)char* str = "tux";
(String hier nicht modifizierbar, sollte als const char*
definiert werden!)struct
sstruct Penguin{ int age; char* name; } struct Penguin1 { // size: 8 int id; // 4 unsigned char age; // 1 char color; // 1 // 2 byte padding... // 4 + 1 + 1 + 2 = 8 } struct Penguin2 { // size: 12 unsigned char age; // 1 // 3 byte padding... int id; // 4 char color; // 1 // 3 byte padding... // 1 + 3 + 4 + 1 + 3 = 12 }
struct Penguin{
int age;
char* name;
}
struct Penguin1 { // size: 8
int id; // 4
unsigned char age; // 1
char color; // 1
// 2 byte padding...
// 4 + 1 + 1 + 2 = 8
}
struct Penguin2 { // size: 12
unsigned char age; // 1
// 3 byte padding...
int id; // 4
char color; // 1
// 3 byte padding...
// 1 + 3 + 4 + 1 + 3 = 12
}
penguin.age = 0; // type of penguin is struct Penguin penguin_ptr -> age = 0; // type of penguin_ptr is struct Penguin*
penguin.age = 0; // type of penguin is struct Penguin
penguin_ptr -> age = 0; // type of penguin_ptr is struct Penguin*
struct
s über “Compound Literal” oder indem alle Werte auf null gesetzt werden// compound literal struct Penguin penguin1 = { .age = 0, .name = "Tux" }; struct Penguin penguin2 = { .age = 0 }; // impl. penguin2.name = NULL struct Penguin penguin3 = { 0, "Tux" }; // festgelegte Reihenfolge // NULL struct Penguin penguin4 = { 0 };
// compound literal
struct Penguin penguin1 = { .age = 0, .name = "Tux" };
struct Penguin penguin2 = { .age = 0 }; // impl. penguin2.name = NULL
struct Penguin penguin3 = { 0, "Tux" }; // festgelegte Reihenfolge
// NULL
struct Penguin penguin4 = { 0 };
struct
als Parameterstruct
by Value übergegeben wird und in der Funktion modifiziert wird, wird nur die Kopie modifiziert und nicht das eigentliche struct
-Objektstruct Penguin { char * name ; unsigned age ; }; void print_penguin_name1 ( struct Penguin * penguin ) { printf ( " name : % s \ n " , penguin - > name ) ; } void print_penguin_name2 ( struct Penguin penguin ) { printf ( " name : % s \ n " , penguin . name ) ; } int main ( void ) { struct Penguin penguin = { " tux " , 5 }; print_penguin_name1 (& penguin ) ; print_penguin_name2 ( penguin ) ; }
struct Penguin {
char * name ;
unsigned age ;
};
void print_penguin_name1 ( struct Penguin * penguin ) {
printf ( " name : % s \ n " , penguin - > name ) ;
}
void print_penguin_name2 ( struct Penguin penguin ) {
printf ( " name : % s \ n " , penguin . name ) ;
}
int main ( void ) {
struct Penguin penguin = { " tux " , 5 };
print_penguin_name1 (& penguin ) ;
print_penguin_name2 ( penguin ) ;
}
struct
vs. union
union
erlaubt Zugriff auf Speicherbereich mit unterschiedlichen Datentypen
struct
speichert immer alle Elemente an aufeinanderfolgenden Adressenstruct
s und bei anonymen union
s werden die Member zu Membern der übergeordneten Strukturstruct Penguin { struct { unsigned height ; unsigned age ; unsigned id ; // oops }; char id [256]; // compiler error: attribute id already "defined" };
struct Penguin {
struct {
unsigned height ;
unsigned age ;
unsigned id ; // oops
};
char id [256]; // compiler error: attribute id already "defined"
};
struct
mit union
struct
mit Indikator für Gültigkeit der union
-Elementestruct Dimension { ... }; struct Shape { int shape_kind; // 1 = circle, 2 = rect union { int circle_radius; struct Dimension rect; }; }; struct Shape my_circ = { .shape_kind = 1, { .circle_radius = 10 }};
struct Dimension { ... };
struct Shape {
int shape_kind; // 1 = circle, 2 = rect
union {
int circle_radius;
struct Dimension rect;
};
};
struct Shape my_circ = { .shape_kind = 1, { .circle_radius = 10 }};
union
als Parameterstruct
void f(union Number num){ // Zugriff auf 'a' mit 'num.a' } void g(union Number* num){ // Zugriff auf 'a' mit 'num->a' }
void f(union Number num){
// Zugriff auf 'a' mit 'num.a'
}
void g(union Number* num){
// Zugriff auf 'a' mit 'num->a'
}
unsigned matrix [3][4] = { { 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 } }; for ( size_t i = 0; i < sizeof ( matrix ) / sizeof (* matrix ) ; i ++) { for ( size_t j = 0; j < sizeof ( matrix [ i ]) / sizeof (* matrix [ i ]) ; j ++) { printf ( " % u " , matrix [ i ][ j ]) ; } } printf ( " \ n " ) ; // Auslassen der expliziten Größenangabe der “obersten Ebene”: unsigned matrix [][4] = { { 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 } };
unsigned matrix [3][4] = {
{ 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 }
};
for ( size_t i = 0; i < sizeof ( matrix ) / sizeof (* matrix ) ; i ++) {
for ( size_t j = 0; j < sizeof ( matrix [ i ]) / sizeof (* matrix [ i ]) ; j ++) {
printf ( " % u " , matrix [ i ][ j ]) ;
}
}
printf ( " \ n " ) ;
// Auslassen der expliziten Größenangabe der “obersten Ebene”:
unsigned matrix [][4] = {
{ 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 }
};
.text
: beinhaltet Programmcode (read-only, executable)
rip
zeigt auf den zunächst auszuführenden Befehl innerhalb des Codes.rodata
: beinhaltet globale konstante initialisierte Variablen (read-only)
const int i = 42;
(global).data
: beinhaltet globale initialisierte Variablen (read-write)
int i = 42;
(global).bss
: beinhaltet globale Variablen, die mit 0 initialisiert sind
int i;
(global)void* malloc(size_t size)
und void* calloc(size_t nmemb, size_t size)
aus stdlib.h
reservieren Speicher auf dem Heap und müssen freigegeben werden!
void* alloca(size_t size)
reserviert Speicher auf dem Stack und muss nicht wieder freigegeben werdenNULL
-Pointer als Rückgabewertvoid free(void* ptr)
aus stdlib.h
ptr
von malloc
bzw. calloc
freen!free
soll ptr
nicht mehr für Speicherzugriffe bzw. Speicherverwaltung verwendet werden!// Beispiel: Allokation auf dem Heap char* p = malloc(256 * sizeof(char)); if ( p == NULL ) { // Behandlung von Fehler bei Speicherallokation abort(); } // ... arbeite mit p free(p);
// Beispiel: Allokation auf dem Heap
char* p = malloc(256 * sizeof(char));
if ( p == NULL ) {
// Behandlung von Fehler bei Speicherallokation
abort();
}
// ... arbeite mit p
free(p);
void* realloc(void* ptr, size_t size)
vergrößert / verkleinert bereits reservierter Speicher
realloc(NULL, size)
equiv. zu malloc(size)
NULL
-Pointer als Rückgabewert und alter Speicherbereich wird nicht freigegebenvoid* aligned_alloc(size_t alignment, size_t size)
alignment
muss Zweierpotenz seinsize
muss Vielfaches des alignment
s sein-Wall
und -Wextra
printf
-Debuggingprintf
s sind gebuffert
\n
leert den Bufferfflush(stdout)
leert ihn auchassert()
assert.h
-DNDEBUG
wird festgestellt, dass assert()
als NOP funktioniertassert()
false zurückliefertassert
soll nur in der Testphase verwendet werdenA
hat platz nur für 4 char
s (inkl. \0
)strcpy()
darf jedoch aufgerufen werden, der Code kompiliert noch (obwohl die meisten Compiler den Overflow erkennen und davor warnen werden) und wird ausgeführtB
werden somit überschriebenfopen
gibt bei Fehler NULL
zurück0
und endet bei arrLen - 1
)memcpy(void* dest, const void* src, size_t n)
, die eine explizite Angabe der Datengröße n
fordertgets(char* buf)
buf
, bis EOF
oder '\n'
erkannt wirdscanf()
scanf("%5s", buf)
)scanf("%s", buf)
(ohne Einschränkung) ist weiterhin erlaubtfgets(char* dest, int n, FILE* stream)
verwendet werden
n - 1
oder EOF bzw. \n
Zeichen werden eingelesendest
wird nullterminiert \to Buffer-Overflows durch Nullterminal werden vermiedenstrcpy(char* dest, const char* src)
src
nach dest
, inklusive Nullterminal von src
size(src) > size(dest)
\to Buffer-Overflowstrncpy(char* dest, const char* src, size_t n)
n
bestimmt, wie viele Bytes maximal kopiert werdenn
Bytes von src
existiert!
char d[3]; char* s = {'1','2','3'}
strncpy(d,s,3)
ergibt d = {'1','2','3'}
printf(buf)
, wobei buf
User-Input enthält
buf
aufgrund des Einlesens mit scanf
durch den Nutzer frei wählbar ist, kann dieser dort auch Format Specifier wie %s
, %d
, etc. angebenprintf
wird diese Format Specifier dann wie üblich interpretieren und als Konsequenz evtl. den Inhalt von Registern- und/oder des Stack-Speichers ausgebenmalloc()
malloc()
bei Fehler ist NULL-Pointermalloc()
NULL zurückgibtprintf()
benutzt pro Format Specifier einen Parameter%x
, %s
etc. leaken oder mit %n
schreibenprintf
-Aufrufe und Benutzung eines Formatstrings: printf("Hello %s!\n", buf)
free
vor return
-Statements
malloc
oder calloc
, da man diesen vllt. später noch braucht)if (! strlen ( buf ) ) { printf ( " You didn ’t enter your name !\ n " ) ; return 1; } else if ( strlen ( buf ) > 20) { printf ( " You have a really long name , % s !\ n " , buf ) ; free ( buf ) ; } // buf gets used after being freed -> use after free printf ( " Thank you for introducing yourself , % s !\ n " , buf ) ; // buf gets freed again -> double free free ( buf ) ;
if (! strlen ( buf ) ) {
printf ( " You didn ’t enter your name !\ n " ) ;
return 1;
} else if ( strlen ( buf ) > 20) {
printf ( " You have a really long name , % s !\ n " , buf ) ;
free ( buf ) ;
}
// buf gets used after being freed -> use after free
printf ( " Thank you for introducing yourself , % s !\ n " , buf ) ;
// buf gets freed again -> double free
free ( buf ) ;
fflush(stdin)
-fsanitize=address
für Buffer Overflows und Dangling Pointer-fsanitize=leak
für Memory Leaks-fsanitize=undefined
für Undefined Behaviorint argc
und char** argv
argv
: Kommandozeile, an Leerzeichen aufgetrennt
argc
: Länge des Arrays argv
""
wird nicht getrenntkeine besondere Bedeutung für -...
getopt
getopt
auf eine Option trifft, die nicht im optstring
spezifiziert wurde, wird der character '?'
zurückgegebenstrtol
: Parsen von Zahlen aus Strings
**endptr
zeigt nach Ausführung auf das erste Zeichen, das nicht konvertiert werden konnte**endptr
auf das letzte Zeichen des übergebenen Strings - das Nullbyte das hinter dem String abgelegt wird \to *strtol_err == '\0'
gilt, um zu prüfen, dass das Argument für Option n
eine Ganzzahl ist#include <stdio.h> int main(int argc, char** argv){ for(int i = 0; i < argc; i++){ printf(";%s;\n", argv[i]); } return 0; }
#include <stdio.h>
int main(int argc, char** argv){
for(int i = 0; i < argc; i++){
printf(";%s;\n", argv[i]);
}
return 0;
}
$ ./myprog.o -o filename -g ;./myprog.o; ;-o; ;filename; ;-g; $ ./myprog.o -n "Hallo Welt" ;./myprog.o; ;-n; ;Hallo Welt;
$ ./myprog.o -o filename -g
;./myprog.o;
;-o;
;filename;
;-g;
$ ./myprog.o -n "Hallo Welt"
;./myprog.o;
;-n;
;Hallo Welt;
rbx, rbp, r12 - r15, rsp
rax, rcx, rdx, rsi, rdi, r8 - r11
myfun: push rbx mov rbx, [rdi] ... pop rbx ret
myfun:
push rbx
mov rbx, [rdi]
...
pop rbx
ret
rsp % 16 == 0
, bzw. die letzten 4 bit der Adresse sind 0)call
legt weitere 8 Byte auf den Stack \to um 8 Byte verschoben (rsp % 16 == 8
)sub
oder push
)myfun: push rbx mov rbx, [rdi] ... call do_sth add rax, rbx pop rbx ret
myfun:
push rbx
mov rbx, [rdi]
...
call do_sth
add rax, rbx
pop rbx
ret
struct
s im Speicher angeordnet?
struct
s ist das seines Felds mit dem größten Alignmentstruct
s ist ein Vielfaches davon \to Padding am Endestruct
s als Funktionsparameter übergeben?
int
-Parameter behandelt//2 64-Bit Integer (16 Byte) void func(struct { uint64_t a; uint64_t b; } param); // a -> rdi, b -> rsi // 2 32-Bit Integer (8 Byte) void func(struct { uint32_t a; uint32_t b; } param); // a -> rdi[31:0], b -> rdi[63:32] // mehrere zusammengefasste Felder (12 Byte) void func(struct { uint16_t a; uint16_t b; uint8_t c; } param); // a -> rdi[15:0], b -> rdi[31:16], c -> rdi[39:32] // struct größer als 16 Byte (24 Byte) void func(struct { uint64_t a; uint64_t b; uint64_t c; } param); // a -> [rsp], b -> [rsp + 8], c -> [rsp + 16]
//2 64-Bit Integer (16 Byte)
void func(struct { uint64_t a; uint64_t b; } param);
// a -> rdi, b -> rsi
// 2 32-Bit Integer (8 Byte)
void func(struct { uint32_t a; uint32_t b; } param);
// a -> rdi[31:0], b -> rdi[63:32]
// mehrere zusammengefasste Felder (12 Byte)
void func(struct { uint16_t a; uint16_t b; uint8_t c; } param);
// a -> rdi[15:0], b -> rdi[31:16], c -> rdi[39:32]
// struct größer als 16 Byte (24 Byte)
void func(struct { uint64_t a; uint64_t b; uint64_t c; } param);
// a -> [rsp], b -> [rsp + 8], c -> [rsp + 16]
rax
für ersten 64 Bit, rdx
für die zweiten 64 Bitrdi
übergebenrax
zurückstruct ComputeRes { uint64_t a, b, c; }; struct ComputeRes compute(int param); // verhält sich wie: struct ComputeRes* compute(struct ComputeRes* retval, int param);
struct ComputeRes { uint64_t a, b, c; };
struct ComputeRes compute(int param);
// verhält sich wie:
struct ComputeRes* compute(struct ComputeRes* retval, int param);
rdi, rsi, rdx, rcx, r8, r9, Stack
rax, rdx (bei 128-bit)
rbx, rbp, rsp, r12 - r15
rax, rcx, rdx, rsi, rdi, r8 - r11
0000000000000000.0000000000000000
(Vorkommastellen, dann Nachkommastellen)00000000001101.11010000000000
110.100 * 1110.11 = 1011111.11100
(3.3 * 4.2 = 7.5)S | EEEEEEEE | MMMMMMMMMMMMMMMMMMMMMMM
S
: 0 wenn positiv, 1 wenn negativE
: um wie viele Stellen shiften wir?M
: Kommazahlfloat
und double
Größe | Dezimalziffern | Abs. Min | Abs. Max | |
---|---|---|---|---|
float |
32 | \approx 7 | \approx 1.18 \cdot 10^{-38} | \approx 3.4 \cdot 10^{38} |
double |
64 | \approx 15 | \approx 10^{-308} | \approx 10^{308} |
float
:
double
:
Beispiel: 37.75
100101.11
1.0010111
0 10000100 00101110000000000000000
Beispiel: 0 10000100 01010000000000000000000
1000000.00f + 0.01f = 1000000.00f
1000000.1f − 1000000.0f = 0.125f != .1f
, weil 1000000.1f
tatsächlich als 1000000.125
dargestellt wird(x + y) + z != x + (y + z)
(x * y) * z != x * (y * z)
x * (y + z) != (x * y) + (x * z)
-ffast-math
(-Ofast
) in GCC ignoriert diese zwecks Geschwindigkeitfalse
, außer \neqhalf
S | EEEEE | MMMMMMMMMM
(1 / 5 / 10)bfloat
S | EEEEEEEE | MMMMMMM
(1 / 8 / 7)xmm0
bis xmm15
xmm
-Registern verwendbar für Floating-Point-Berechnungenpxor dst, src
genutzt werden (wobei dst == src
).rodata
) mit movss dst, src
(move single precision float) geladen werden
movss xmm0, [rip + .Lconstx]
movd / movq
möglich
ss
für Scalar Single, sd
für Scalar Double (ss nicht mit sd anwendbar)addss dst, src
src
: Register und Speicher erlaubtaddss
verwendetsubss dst, src
divss dst, src
div
nicht implizit gefolgert, sondern werden explizit angegebenmulss dst, src
ucomiss op1, op2
: skalarer Vergleich zweier Floating-Point Werte
jcc
cmpFloat : ucomiss xmm1 , xmm0 jp _Lunordered ; xmm0 or xmm1 NaN jb _Llesser ; xmm1 < xmm0 ja _Lgreater ; xmm1 > xmm0 je _Lequal ; xmm1 == xmm0
cmpFloat :
ucomiss xmm1 , xmm0
jp _Lunordered ; xmm0 or xmm1 NaN
jb _Llesser ; xmm1 < xmm0
ja _Lgreater ; xmm1 > xmm0
je _Lequal ; xmm1 == xmm0
jp
oder jnp
behandelt#include <stdio.h> extern float func(float x); int main(int argc, char **argv){ float res = func(2.0); printf("Result: %f\n", res); return 0; }
#include <stdio.h>
extern float func(float x);
int main(int argc, char **argv){
float res = func(2.0);
printf("Result: %f\n", res);
return 0;
}
.intel_syntax noprefix .global func .text func: mov r8, 1 cvtsi2ss xmm1, r8 divss xmm1, xmm0 movss xmm0, xmm1 ret
.intel_syntax noprefix
.global func
.text
func:
mov r8, 1
cvtsi2ss xmm1, r8
divss xmm1, xmm0
movss xmm0, xmm1
ret
xmm0
xmm0 -> xmm7
(weitere auf Stack)float fn(int, float, char*, float); // xmm0 edi xmm0 rsi xmm1
float fn(int, float, char*, float);
// xmm0 edi xmm0 rsi xmm1
ex: mov rdi, 1 movss xmm0, [rip + .Lconstx] mov rs1, 2 movss xmm1, [rip + .Lconsty] call fn ...
ex:
mov rdi, 1
movss xmm0, [rip + .Lconstx]
mov rs1, 2
movss xmm1, [rip + .Lconsty]
call fn
...
ADDPD
/ PADDD
)PADDD xmm1, xmm2/m128
(ADD Packed Integer DWORD)
PADDB xmm1, xmm2/m128
(ADD Packed Integer BYTE)
PADDQ xmm1, xmm2/m128
(ADD Packed Integer QWORD)
PADDD
(jeder Kasten ist 32-bit groß \to es können 4 Werte zur selben Zeit addiert werden)PADDQ
(jeder Kasten ist 64-bit groß \to es können 2 Werte zur selben Zeit addiert werden); void add_one(int x[4]) add_one: mov eax, 1 movd xmm0, eax pshufd xmm0, xmm0, 0x00 movdqu xmm1, [rdi] paddd xmm1, xmm0 movdqu [rdi], xmm1 ret
; void add_one(int x[4])
add_one:
mov eax, 1
movd xmm0, eax
pshufd xmm0, xmm0, 0x00
movdqu xmm1, [rdi]
paddd xmm1, xmm0
movdqu [rdi], xmm1
ret
MOVD
kopiert DWORD aus EAX in XMM0PSHUFD
kopiert den niedrigsten DWORD in XMM0 mittels der Maske 0x00 in die drei höherwertigen DWORD in XMM0
MOVDQU
lädt 4 Integer aus dem Speicher ab RDI in XMM1PADDD
addiert 4 Integer aus XMM0 auf XMM1MOVDQU
schreibt das Resultat in den Speicher (4 Integer aus XMM1 in Speicher ab RDI)ADDSS xmm1, xmm2/m32
(ADD Scalar Single-Precision Floating-Point Values)
ADDPS xmm1, xmm2/m128
(ADD Packed Single-Precision Floating-Point Values)
ADDPD xmm1, xmm2/m128
(ADD Packed Double-Precision Floating-Point Values)
; void add_self(float *, size_t) ; assume that rdi & 3 == 0 add_self: movups xmm0, [rdi] addps xmm0, xmm0 movups [rdi], xmm0 add rdi, 16 sub rsi, 4 jnz add_self ret
; void add_self(float *, size_t)
; assume that rdi & 3 == 0
add_self:
movups xmm0, [rdi]
addps xmm0, xmm0
movups [rdi], xmm0
add rdi, 16
sub rsi, 4
jnz add_self
ret
MOVUPS
lädt 4 Floats ab Speicheradresse RDI in das Register XMM0ADDPS
addert 4 Floats in XMM0 paarweise auf sich selbstMOVUPS
schreibt 4 Floats vom Register XMM0 an Speicheradresse RDIMOV
-Instruktionen für XMM-Register__attribute__((aligned(16)))
int x[12] __attribute__((aligned (16)));
MOVAPS xmm/m128 xmm/m128
(Move Aligned Packed Single-Precision)
MOVDQA
gibtMOVUPS xmm/m128 xmm/m128
(Move Unaligned Packed Single-Precision)
MOVAPS
, auch auf aligned Speicher
MOVAPS
__m128
für float-Datentypen
__m128
definiert sind
addps xmm, xmm
\to C: __m128 _mm_add_ps (__m128 a, __m128 b)
mulss xmm, xmm
\to C: __m128 _mm_mul_ss (__m128 a, __m128 b)
mov*s
__m128 _mm_load_ps (float const* mem_addr)
__m128 x = _mm_load_ss(&b)
void _mm_store_ps (float* mem_addr, __m128 a)
#include <immintrin.h> void saxpy(long n, float alpha, float *x, float *y){ __m128 valpha = _mm_load1_ps(&alpha); // simd for(size_t i = 0; i < (n - (n % 4)); i += 4) { __m128 vx = _mm_loadu_ps(x + i); __m128 vy = _mm_loadu_ps(y + i); vy = _mm_add_ps(_mm_mul_ps(valpha, vx), vy); _mm_storeu_ps(y + i, vy); } // remaining elements for(size_t i = (n - (n % 4)); i < n; i++) { y[i] = alpha * x[i] + y[i]; } }
#include <immintrin.h>
void saxpy(long n, float alpha, float *x, float *y){
__m128 valpha = _mm_load1_ps(&alpha);
// simd
for(size_t i = 0; i < (n - (n % 4)); i += 4) {
__m128 vx = _mm_loadu_ps(x + i);
__m128 vy = _mm_loadu_ps(y + i);
vy = _mm_add_ps(_mm_mul_ps(valpha, vx), vy);
_mm_storeu_ps(y + i, vy);
}
// remaining elements
for(size_t i = (n - (n % 4)); i < n; i++) {
y[i] = alpha * x[i] + y[i];
}
}
__m128d
(Double) und __m128i
(Integer)
__m256
: AVX__m64
: Legacyaddpd xmm, xmm
\to C: __m128d _mm_add_pd (__m128d a, __m128d b)
paddb xmm, xmm
\to C: __m128i _mm_add_epi8 (__m128i a, __m128i b)
-O3
-march
spezifiziert, welcher Erweiterungssatz verwendet werden soll
-march=native
: alle Erweiterungssätze unterstützen die lokale Architektur-fopt-info-vec(-missed)
: prüfe, ob / welche Vektorisierungen erfolgreich waren (und warum auch nicht)objdump
zu analysierenOptimierungsstufe | Beschreibung |
---|---|
-O0 |
keine Optimierungen |
-O1 |
Optimierungen mit wenig Compile-Zeit |
-Og |
O1 mit Fokus auf debugbaren Code |
-O2 |
alle Optimierungen ohne Space-Speed-Tradeoff |
-Os |
O2 mit Fokus auf minimaler Code-Größe |
-O3 |
alle Optimierungen |
-Ofast |
O3 mit Float-Optimierungen (disregard strict standard compliance) |
-O1
kann debugging schwerer werden, da Statements verschoben oder Variablen wegoptimiert wurden-O2
kann das Kompilieren länger dauern-O3
kann die größe der komplierten Executable wachsenint x = a * b * 24; int y = a * b * c; // optimized int tmp = a * b; int x = tmp * 24; int y = tmp * c;
int x = a * b * 24;
int y = a * b * c;
// optimized
int tmp = a * b;
int x = tmp * 24;
int y = tmp * c;
for(int i = 0; i < 6; i++){ arr[i] = 2 * arr[i]; } // optimized: -floop-unroll-and-jam, ab -O3 for(int i = 0; i < 6; i+= 2){ arr[i] = 2 * arr[i]; arr[i + 1] = 2 * arr[i + 1]; }
for(int i = 0; i < 6; i++){
arr[i] = 2 * arr[i];
}
// optimized: -floop-unroll-and-jam, ab -O3
for(int i = 0; i < 6; i+= 2){
arr[i] = 2 * arr[i];
arr[i + 1] = 2 * arr[i + 1];
}
for(int i = 0; i < 6; i++){ arr[i] = 2 * arr[i]; } for(int i = 0; i < 6; i++){ arr2[i] = arr2[i] + 24; } // optimized: -floop-unroll-and-jam for(int i = 0; i < 6; i++){ arr[i] = 2 * arr[i]; arr2[i] = arr2[i] + 24; }
for(int i = 0; i < 6; i++){
arr[i] = 2 * arr[i];
}
for(int i = 0; i < 6; i++){
arr2[i] = arr2[i] + 24;
}
// optimized: -floop-unroll-and-jam
for(int i = 0; i < 6; i++){
arr[i] = 2 * arr[i];
arr2[i] = arr2[i] + 24;
}
x == 0
, kann n jeden Wert haben; daher ist die optimierte Variante korrekt
int n; for(int i = 0; i < x; i++){ n = sizeof(arr) / sizeof(int); arr[i] = 2 * arr[i]; } // optimized: -fmove-loop-invariants, ab -O1 int n = sizeof(arr) / sizeof(int); for(int i = 0; i < x; i++){ arr[i] = 2 * arr[i]; }
int n;
for(int i = 0; i < x; i++){
n = sizeof(arr) / sizeof(int);
arr[i] = 2 * arr[i];
}
// optimized: -fmove-loop-invariants, ab -O1
int n = sizeof(arr) / sizeof(int);
for(int i = 0; i < x; i++){
arr[i] = 2 * arr[i];
}
int sum = 0; for(int i = 0; i < 6; i++){ for(int j = 0; j < 9; j++){ sumt += arr[j][i]; } } // optimized: -floop-interchange, ab -O3 int sum = 0; for(int j = 0; i < 9; j++){ for(int i = 0; j < 6; i++){ sumt += arr[j][i]; } }
int sum = 0;
for(int i = 0; i < 6; i++){
for(int j = 0; j < 9; j++){
sumt += arr[j][i];
}
}
// optimized: -floop-interchange, ab -O3
int sum = 0;
for(int j = 0; i < 9; j++){
for(int i = 0; j < 6; i++){
sumt += arr[j][i];
}
}
call
speichert Rücksprungadresse, Code liegt an anderem Ort in Speicher)ìnline
hat nur bedingt etwas zu tunstatic int square(int x){ return x*x; } for(int i = 0; i < n; i++){ arr[i] = square(i); } // optimized: -finline-functions-called-once, ab -O1 bzw. -finline-functions, ab -O2 for(int i = 0; i < n; i++){ arr[i] = i * i; }
static int square(int x){
return x*x;
}
for(int i = 0; i < n; i++){
arr[i] = square(i);
}
// optimized: -finline-functions-called-once, ab -O1 bzw. -finline-functions, ab -O2
for(int i = 0; i < n; i++){
arr[i] = i * i;
}
call + ret
) durch jmp
int fac(int k, unsigned n){ if(n <= 0) return k; return fac(k * n, n - 1); } // optimized: -foptimize-sibling-calls, ab -O2 int fac(int k, unsigned n){ fac: if(n <= 0) return k; k *=n; n--; goto fac; }
int fac(int k, unsigned n){
if(n <= 0) return k;
return fac(k * n, n - 1);
}
// optimized: -foptimize-sibling-calls, ab -O2
int fac(int k, unsigned n){
fac:
if(n <= 0) return k;
k *=n;
n--;
goto fac;
}
static
-Funktionen möglichstatic
-Funktionen ohne externe Nutzung möglichlea
libc
stellt häufig benutzte Funktionen hochoptimiert bereit, wobei die beste Funktion zur Laufzeit mittels IFUNC_SELECTOR
s ausgewählt wirdbuiltin
s an (nicht Teil der Standardbibliothek)
__builtin_clz(unsigned x)
: gibt Anzahl der führenden 0 eines unsigned ints zurück
bsr
implementierbar__builtin_expect(long exp, long c)
exp
wird wahrscheinlich zu c
auswerten__attribute__((always_inline)) void addTwo(uint8_t* element){ *element += 2; } __attribute__((noinline)) void addTwo(uint8_t* element){ *element += 2; }
__attribute__((always_inline))
void addTwo(uint8_t* element){
*element += 2;
}
__attribute__((noinline))
void addTwo(uint8_t* element){
*element += 2;
}
const
: Ausgabe nur durch Eingabe bestimmt
const
-Funktion darf nur andere const
-Funktionen aufrufenvoid
-Rückgabewert sinnlos)__attribute__((const)) extern uint32_t mulPi(uint32_t n); // n * pi
__attribute__((const))
extern uint32_t mulPi(uint32_t n); // n * pi
pure
: ähnlich zu, aber wenig restriktiver als const
pure
-Funktionen dürfen pure
- und const
-Funktionen aufrufen__attribute__ (( pure ) ) int my_memcmp ( const void * ptr1 , const void * ptr2 , size_t n ) { while (! n - -) if (* ptr1 ++ != * ptr2 ++) return * ptr2 - * ptr1 ; return 0; }
__attribute__ (( pure ) )
int my_memcmp ( const void * ptr1 , const void * ptr2 , size_t n ) {
while (! n - -)
if (* ptr1 ++ != * ptr2 ++)
return * ptr2 - * ptr1 ;
return 0;
}
hot
: besonders oft aufgerufene Funktionen
cold
: besonders selten aufgerufene Funktionen
{0,1,...,12800}
wäre unsigned short
besser als unsigned int
{0.00,0.25,...,100.00}
wäre float
besser als double
int, short
etc. ist implementation-defined, so dass die Verwendung von fixed-width Integern sinnvoll ist (z.B. uint8_t
, int16_t
etc.)struct
sstruct PenguinBad { // alignment: 8 (char*) char type; // offset: 0 char *name; // offset: 8 uint8_t age; // offset: 16 } // size (mult of alignment): 24 struct PenguinBad { // alignment: 8 (char*) char type; // offset: 0 uint8_t age; // offset: 1 char *name; // offset: 8 } // size (mult of alignment): 16
struct PenguinBad { // alignment: 8 (char*)
char type; // offset: 0
char *name; // offset: 8
uint8_t age; // offset: 16
} // size (mult of alignment): 24
struct PenguinBad { // alignment: 8 (char*)
char type; // offset: 0
uint8_t age; // offset: 1
char *name; // offset: 8
} // size (mult of alignment): 16
char* / unsigned char*
)unsigned int*
kann Alias von int*
sein, aber unsigned int*
kann nicht Alias von double*
sein)restrict
void foo(unsigned* a, int* b){ ... } void foo2(unsigned* restrict a, int* restrict b){ ... }
void foo(unsigned* a, int* b){
...
}
void foo2(unsigned* restrict a, int* restrict b){
...
}
// arr und sum zeigen nicht auf gleichen speicher // compiler kann das aber nicht wissen (char* kann alles aliasen) void count_a(const char *arr, int *sum){ while(*arr){ *sum += *arr++ == 'a'; } } // fixed void count_a(const char* restrict arr, int *sum){ while(*arr){ *sum += *arr++ == 'a'; } }
// arr und sum zeigen nicht auf gleichen speicher
// compiler kann das aber nicht wissen (char* kann alles aliasen)
void count_a(const char *arr, int *sum){
while(*arr){
*sum += *arr++ == 'a';
}
}
// fixed
void count_a(const char* restrict arr, int *sum){
while(*arr){
*sum += *arr++ == 'a';
}
}
// arr und sum können keine gültigen aliase sein // diese zeigen also nicht auf gleiche speicherbereiche // optimierungen erst ab -o2 oder mit -fstrict-aliasing void count_a_short(const short arr[4], int *sum){ for(size_t i = 0; i < 4; i++){ *sum += arr[i] == 'a'; } } // optimierung: // sum muss nicht bei jeder iteration in den speicher geschrieben werden
// arr und sum können keine gültigen aliase sein
// diese zeigen also nicht auf gleiche speicherbereiche
// optimierungen erst ab -o2 oder mit -fstrict-aliasing
void count_a_short(const short arr[4], int *sum){
for(size_t i = 0; i < 4; i++){
*sum += arr[i] == 'a';
}
}
// optimierung:
// sum muss nicht bei jeder iteration in den speicher geschrieben werden
; if(a[i] != b[i]) a[i] = 0; mov ecx, dword ptr [rsi] cmp dword ptr [rdi], ecx je .Lskip mov byte ptr [rdi] 0 .Lskip: ...
; if(a[i] != b[i]) a[i] = 0;
mov ecx, dword ptr [rsi]
cmp dword ptr [rdi], ecx
je .Lskip
mov byte ptr [rdi] 0
.Lskip:
...
Jcc
überprüft werdena == b
, a > b
etc.)PCMPEQB xmm1, xmm2/m128
(Compare Packed Data for Equal)
0xfff...
wenn wahr, 0x000...
wenn falsch)PCMPEQW xmm1, xmm2/m128
: 8 x 16-bitPCMPEQD xmm1, xmm2/m128
: 4 x 32-bitPCMPEQQ xmm1, xmm2/m128
: 2 x 64-bitPCMPGTB xmm1, xmm2/m128
(Compare Packed Signed Integers for Greater Than)
PCMPGTW xmm1, xmm2/m128
: 8 x 16-bitPCMPGTD xmm1, xmm2/m128
: 4 x 32-bitPCMPGTQ xmm1, xmm2/m128
: 2 x 64-bit; if (a[i] != b[i]) ; a[i] = 0; ; xmm0 = a [i] movdqa xmm0, xmmword ptr [rdi] ; xmm1 = b[i] movdqa xmm1, xmmword ptr [rsi] pcmpeqd xmm1, xmm0 ; create bit mask pand xmm0, xmm1 ; set a[i] to 0 with logical AND movdqa xmmword ptr [rdi] , xmm0 ; replace a[i] in memory
; if (a[i] != b[i])
; a[i] = 0;
; xmm0 = a [i]
movdqa xmm0, xmmword ptr [rdi]
; xmm1 = b[i]
movdqa xmm1, xmmword ptr [rsi]
pcmpeqd xmm1, xmm0 ; create bit mask
pand xmm0, xmm1 ; set a[i] to 0 with logical AND
movdqa xmmword ptr [rdi] , xmm0 ; replace a[i] in memory
uint8_t
-Arrays durch 2 teilen
rax
laden, Division durch 2 mit Bitshift (shr rax, 1
)01111111 | 01111111 | ...
) in r8
, da das niedrigste Bit von einem Element ins nächste geschoben wird (and rax, r8
)mov rax, [rdi] shr rax, 1 mov r8, 0x7F7F7F7F7F7F7F7F and rax, r8
mov rax, [rdi]
shr rax, 1
mov r8, 0x7F7F7F7F7F7F7F7F
and rax, r8
a[i] = foo(i)
)ymm0
bis ymm15
xmm
-Register für KompatibilitätDrei-Operanden-Format (OP dest, src1, src2
)
a = b + c
\to mehr FlexibilitätPräfix V
VADDPS xmm/ymm, xmm/ymm, xmm/m128/ymm/m256
: Vektoraddition von Gleitkommazahlen
ADDPS
würde der Wert beibehaltenVMOVSD xmm, xmm, xmm
: merged 2 x 64-bit Gleitkommazahlen in ein xmm-Zielregister
VBROADCASTSS xmm/ymm, xmm/m32
PSHUFD
in SSEVPSLLVD xmm/ymm, xmm/ymm, xmm/m128/ymm/m256
VPSRLVD xmm/ymm, xmm/ymm, xmm/m128/ymm/m256
VPSRAVD xmm/ymm, xmm/ymm, xmm/m128/ymm/m256
MOVAPD
)was passiert, wenn man im Code häufig zwischen SSE und AVX wechselt?
VZEROALL
(nullt alle ymm-Register und markiert diese als ungenutzt) und VZEROUPPER
(nullt die oberen 128-bit aller ymm-Register) sollten vor jedem SSE/AVX-Wechsel ausgeführt werdenunterschiedliche Prozessorfrequenzen für verschiedene Instruktionsklassen
Fazit: VEX-Instruktionen nicht mit nicht-VEX-Befehlen mischen (um Performanzverschlechterung zu vermeiden)
time cmd
(cmd
kann kompiliertes Programm oder Befehl sein), z.B. time ./matr
oder time ls -a
real 0m.024s <- Abstand zwischen call und finish user 0m.016s <- CPU-Zeit im User-Mode sys 0m.016s <- CPU-Zeit im Kernel-Mode
real 0m.024s <- Abstand zwischen call und finish
user 0m.016s <- CPU-Zeit im User-Mode
sys 0m.016s <- CPU-Zeit im Kernel-Mode
time
ungeeignet für Performancemessungend
- Messpunkt start
= Dauertime.h
liefert int clock_gettime(clockid_t clk_id, struct timespec *tp)
clk_id
: bestimmt die Uhr, die verwendet wird; clockid_t CLOCK_MONOTONIC
(basiert auf in der Realität vergehenden Zeit, durch NTP-Syncs und adjustTime()
beeinflusst)*tp
: struct timespec{time_t tv_sec; long tv_nsec;}
tv_sec
: aktuelle Zeit in Sekundentv_nsec
: aktuelle Zeit in Nanosekundendouble sec = tv_sec + 1e-9 * tv_nsec;
#include <time.h> ... struct timespec start; clock_gettime(CLOCK_MONOTONIC, &start) ; for(int i = 0; i < iterations ; ++i) foo(); struct timespec end; clock_gettime(CLOCK_MONOTONIC, &end); double time = end.tv_sec - start.tv_sec + 1e-9 * (end.tv_nsec - start.tv_nsec); double avg_time = time / iterations;
#include <time.h>
...
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start) ;
for(int i = 0; i < iterations ; ++i)
foo();
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
double time = end.tv_sec - start.tv_sec + 1e-9 * (end.tv_nsec - start.tv_nsec);
double avg_time = time / iterations;
CLOCK_MONOTONIC
perf
profiling: Analyse der Laufzeit (+ Geschwindigkeit) eines Programms, um ineffiziente Bereiche aufzudecken
Tracing Profiler: fügen Instruktionen zum Programmcode hinzu (entweder im Source Code, in ASM oder zur Laufzeit)
Sampling Profiler (perf
): Programmcode wird nicht verändert
time ./prog
: Messung der Zeit
perf record ./prog
: sammelt Daten und schreibt sie in perf.data
perf report
: zeigt protokollierte Daten an
perf
systemweit aufgerufen wird)[Privilege]
(k
- Kernel, .
- Nutzermodus) + Name des Symbols / der Methodeperf list
: zeigt Events an, die protokolliert werden können
perf record -e cache-misses ./prog
: zeigt in Annotate x
auch die Anzahl an Cache-Misses anSummary by Flavius Schmidt, ge83pux, 2023.
https://home.in.tum.de/~scfl