//
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, r9rax 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 stdoutret 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_Bools 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 intvoid
* 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 werdenint foo ( void ) ; // <-- RICHTIG : akzeptiert keine Parameter
int bar () ; // <-- FALSCH : kann mit beliebigen Parametern
// definiert / aufgerufen werdenreturn 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 Variableconst int a; a = 4; // COMPILER-FEHLER
const int a;
a = 4; // COMPILER-FEHLERconst 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 Datenconst 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 Datenvoid 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 Integerint 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 Integerunsigned 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.hstdint.h definiert fixed-width Integer Typen| Signed | 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 _Booltrue 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 intprintf("%s", "test"); kann es möglicherweise zu keinem Output führen, da ohne Newline die Ausgabe ggf. in den Zwischenspeicher kommtobjdumpobjdump PROGNAME -d -M intel | less
-d: disassemble-M intel: Intel-Syntax statt AT&Tless: übersichtlicher
gcc -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 Code

gcc: -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.cgdb 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 ;
}
structunion: 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' istint
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 == iint*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 longarr[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 = 3int 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 Terminal
char 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!)structsstruct 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*structs ü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. unionunion erlaubt Zugriff auf Speicherbereich mit unterschiedlichen Datentypen
struct speichert immer alle Elemente an aufeinanderfolgenden Adressenstructs und bei anonymen unions 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 unionstruct 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 Parameterstructvoid 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 alignments sein-Wall und -Wextraprintf-Debuggingprintfs 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 werden
A hat platz nur für 4 chars (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 srcsize(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, rsprax, rcx, rdx, rsi, rdi, r8 - r11myfun: push rbx mov rbx, [rdi] ... pop rbx ret
myfun:
push rbx
mov rbx, [rdi]
...
pop rbx
retrsp % 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
structs im Speicher angeordnet?
structs ist das seines Felds mit dem größten Alignmentstructs ist ein Vielfaches davon \to Padding am Endestructs 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, Stackrax, 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.111.00101110 10000100 00101110000000000000000Beispiel: 0 10000100 01010000000000000000000
1000000.00f + 0.01f = 1000000.00f1000000.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
jcccmpFloat :
ucomiss xmm1 , xmm0
jp _Lunordered ; xmm0 or xmm1 NaN
jb _Llesser ; xmm1 < xmm0
ja _Lgreater ; xmm1 > xmm0
je _Lequal ; xmm1 == xmm0cmpFloat :
ucomiss xmm1 , xmm0
jp _Lunordered ; xmm0 or xmm1 NaN
jb _Llesser ; xmm1 < xmm0
ja _Lgreater ; xmm1 > xmm0
je _Lequal ; xmm1 == xmm0jp 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
retxmm0xmm0 -> 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 xmm1ex:
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
retMOVD 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
retMOVUPS 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 analysieren| Optimierungsstufe | 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 jmpint 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öglichlealibc stellt häufig benutzte Funktionen hochoptimiert bereit, wobei die beste Funktion zur Laufzeit mittels IFUNC_SELECTORs ausgewählt wirdbuiltins 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 doubleint, short etc. ist implementation-defined, so dass die Verwendung von fixed-width Integern sinnvoll ist (z.B. uint8_t, int16_t etc.)structsstruct 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): 16struct 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): 16char* / 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 memoryuint8_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, r8a[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 -areal 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-Modetime 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_MONOTONICperfprofiling: 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