Software ubiquitärer Systeme (SuS)
Anwendungsentwicklung in C/C++
https://ess.cs.tu-dortmund.de/DE/Teaching/SS2018/SuS/
Olaf Spinczyk
[email protected]://ess.cs.tu-dortmund.de/~os
AG Eingebettete SystemsoftwareInformatik 12, TU Dortmund
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 2
Inhalt● Motivation● Sicheres Programmieren in C und C++
– MISRA C– Embedded C++– Cyclone
● Ressourcenverbrauch von C++ gegenüber C– Laufzeit-Polymorphie– Parametrische Polymorphie
● Zusammenfassung
HardwareHardware
BetriebssystemBetriebssystem
MiddlewareMiddleware
DatenhaltungDatenhaltung
Anwendung/ProgrammierungAnwendung/Programmierung
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 3
Inhalt● Motivation● Sicheres Programmieren in C und C++
– MISRA C– Embedded C++– Cyclone
● Ressourcenverbrauch von C++ gegenüber C– Laufzeit-Polymorphie– Parametrische Polymorphie
● Zusammenfassung
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 4
Verbreitung von C und C++● Statistik über den Einsatz von Programmiersprachen bei der
Entwicklung eingebetteter Systeme– Daten beruhen auf einer Umfrage bei 74 Unternehmen der Branche
C
C++
Assembler
Java
.Net
SystemC
IEC 61131
4GL-Sprachen
DSL
sonstige
0 10 20 30 40 50 60 70
5858
5858
4343
4242
3232
1818
1818
1212
77
88
Einsatz in %
Quelle: Computer Zeitung Nr. 23, Juni 2009, S. 16
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 5
Warum C und C++? (1)● Transparency and Control [1]● Beispiele:
– Datenstrukturen wie Arrays von Structs werden einfach linear im Speicher abgelegt. Zugriffe sind durch Cache-Lokalität sehr schnell.
● In Java bestimmen der Compiler und das Laufzeitsystem die Objektplatzierung (keine Transparenz).
– C-Datentypen werden direkt auf die Hardwaredatentypen abgebildet● In Java wird von der realen Hardware abstrahiert.
– C-Programme verwalten den Heap-Speicher manuell.Dies gibt dem Programmierer mehr Kontrolle über die Performance.
● Java arbeitet mit einem Garbage Collector. Die Strategie bleibt dem Laufzeitsystem überlassen.
● C/C++-Programme …– lassen sich manuell optimieren. Man sieht ihnen die verursachten Kosten
direkt an.– sind in der Regel effizienter als zum Beispiel Java-Programme.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 6
Warum C und C++?● Vergleich beim „Great Programming Language Shootout“
– siehe http://shootout.alioth.debian.org/
– C-Code hier bis zu einem Faktor von 36 schneller als Java („mandelbrot“-Benchmark) und bis zu 23 mal kleiner.
● Mit JIT-Compiler ist Java deutlich schneller (als mit Interpreter),aber auch deutlich größer.
– C und C++ liegen dicht beieinander. C-Code war etwas langsamer, dafür aber kleiner als der äquivalente C++-Code.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 7
Und warum lieber nicht?● Undefiniertes Verhalten, z.B.
– Zugriffe über die Grenzen eines Arrays hinweg– Lesen einer uninitialisierten Variablen– Dereferenzieren eines Zeigers, der auf kein gültiges, zum Typ des Zeigers
passendes Objekt verweist● Manuelle Heap-Speicher-Verwaltung, z.B.
– Memory Leaks– Ungültige Zeiger– Mehrfachfreigabe
● Portabilität, z.B.– Wertebereich von Objekten variiert je nach Compiler/Zielplattform
➔ Stabilitäts- und Sicherheitsprobleme, z.B.– Buffer Overflows können genutzt werden, um die Kontrolle über privilegierte
Programme zu übernehmen.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 8
Inhalt● Motivation● Sicheres Programmieren in C und C++
– MISRA C– Embedded C++– Cyclone
● Ressourcenverbrauch von C++ gegenüber C– Laufzeit-Polymorphie– Parametrische Polymorphie
● Zusammenfassung
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 9
MISRA-C [2] (1)(Motor Industry Software Reliability Association)
● Über 100 Programmierregeln der Automobilindustrie– Vermeidung von undefiniertem Verhalten– Einhaltung des Standards (ISO C)– Guter Programmierstil zur Vermeidung von Fehlern und Missverständnissen
● Werkzeugunterstützung zur Überprüfung der Einhaltung– „MISRA-Checker“– Statische Code-Analyse
● Weite Verbreitung in der Industrie– Compiler-Hersteller integrieren Checker– Integratoren verlangen die Einhaltung der Regeln von Zulieferern
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 10
MISRA-C (2)● Beispiel:
● Problem: Es können trotzdem diverse Fehler auftreten– Memory Leaks– Zugriffe über Array-Grenzen hinaus– …
➔ Nur der Stil wird verbessert, nicht die Sprache
Rule 34 (required)The operands of a logical && or || shall be primary expressions.
Invalid: if ( x == 0 && ishigh )Valid: if ( ( x == 0 ) && ishigh )
Primary expressions are constants, a single identifier such as ishigh, or a parenthesized expression. Parentheses are important for readability and ensuring that the behavior is what the programmer intends.
Rule 34 (required)The operands of a logical && or || shall be primary expressions.
Invalid: if ( x == 0 && ishigh )Valid: if ( ( x == 0 ) && ishigh )
Primary expressions are constants, a single identifier such as ishigh, or a parenthesized expression. Parentheses are important for readability and ensuring that the behavior is what the programmer intends.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 11
Embedded C++ (EC++)● Teilmenge von C++ für eingebettete Systeme
– Festgelegt seit 1996 von einem japanischen Industriekonsortium,u.a. Hitachi, NEC, Fujitsu, Toshiba
● Weglassen wurden …– kostenbehaftete C++-Sprachmerkmale
● RTTI● Exceptions
– komplexe Merkmale, die somit Fehlerquellen sind● Mehrfachvererbung (teils auch Kostengründe)● Templates● mutable-Qualifizierer
– Merkmale, die man einfach für unnötig hielt● Namespaces, New-style Casts
● Einige C++-Compiler erlauben Einschränkung auf EC++– oder auch nur EC++ (kein C++)
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 12
Cyclone: Ein sicheres C [1]● C-Spracherweiterung, die undefiniertes Verhalten vermeiden hilft
– Ein erweitertes Typsystem und statische Analyse– Laufzeitabsicherungen
● Konkrete Maßnahmen– Spezielle sichere Zeigertypen (Fat, Thin und Bounded)– Sicherer Umgang mit NULL– Erzwungene Initialisierung– Sichere Unions– Region-based Type System– Verschiedene Strategien zur Heap-Verwaltung (inkl. Garbage Collection)– Exceptions, Namespaces, Subtyping, u.v.m.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 13
Cyclone: Beispiele (1)● Fat Pointers
– Enthalten implizit den erlaubten Adressbereich– Dereferenzierungen außerhalb des Bereich führen zu Exception
#include <stdio.h>int main(int argc, char *@fat *@fat argv) { argc--; argv++; /* skip command name */ while (argc > 0) { printf(" %s", *argv); argc--; argv++; } printf("\n"); return 0;}
Das Speicher-Layout der Arrays ist nicht anders als bei C. Die fetten Zeiger enthalten die Information über die Dimension. Zeigerarithmetik ist erlaubt, aber kontrolliert.
Das Speicher-Layout der Arrays ist nicht anders als bei C. Die fetten Zeiger enthalten die Information über die Dimension. Zeigerarithmetik ist erlaubt, aber kontrolliert.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 14
Cyclone: Beispiele (2)● Thin Pointers
– Verweisen auf ein einzelnes Objekt– Es finden keine Laufzeitüberprüfungen statt– Zeigerarithmetik ist aber komplett verboten
● Garantierte Initialisierung– Realisiert durch statische Kontrollflussanalyse
int x = 3;int *y = &x;
Form *f;switch (event->eType) {case frmOpenEvent: f = FrmGetActiveForm(); ...case ctlSelectEvent: i = FrmGetObjectIndex(f, field); ...}
Fehler! 'f' ist hiernicht initialisiert.Fehler! 'f' ist hiernicht initialisiert.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 15
Cyclone: Beispiele (3)● Tagged Unions
– Optionale Erweiterung von Unions– Das Union-Objekt merkt sich seinen aktuellen Typ– Lesezugriffe werden kontrolliert– Der Typ kann auch explizit abgefragt werden
@tagged union U { int i; int *p; };void pr(union U x) { if (tagcheck(x.i)) printf("int(%d)", x.i); else printf("ptr(%d)", *x.p);}
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 16
Cyclone: Kosten● Die Sicherheit hat ihren Preis
– Im Vergleich zu anderen sicheren Sprachen ist es jedoch effizient
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 17
Fazit: Sichere/bessere C/C++ Dialekte● MISRA C und Embedded C++ reduzieren die
Fehlerwahrscheinlichkeit durch die Beschränkung auf eine Teilmenge von C bzw. C++.– Der Compiler oder separate Werkzeuge können die Einhaltung der
Regeln zur Übersetzungszeit prüfen– Trotzdem bleiben viele zentrale Probleme bestehen
● Unsichere Zeiger, unkontrollierte Arrays, manuelle Heap-Verwaltung, …● Cyclone geht deutlich weiter
– Sichere Zeiger, Unions, Heap-Verwaltung, …– Cyclone-Programme sind typsicher und laufen ohne undefiniertes
Verhalten durch.
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 18
Inhalt● Motivation● Sicheres Programmieren in C und C++
– MISRA C– Embedded C++– Cyclone
● Ressourcenverbrauch von C++ gegenüber C– Laufzeit-Polymorphie– Parametrische Polymorphie
● Zusammenfassung
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 19
Was macht Objektorientierung aus?
● wesentliches Alleinstellungsmerkmal: Vererbung● im Fall von C++ sind zu untersuchen:
– Einfach/Mehrfach-Vererbung– virtuelles Vererben– dynamisches Binden
Klassifizierung nach P. Wegner [3]:
object-oriented = data abstraction+ abstract data types+ type inheritance
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 20
(Einfach-)Vererbung● eine abgeleitete Klasse erbt von einer Basisklasse
– geerbt werden Attribute, Methoden, ...● statt einer Instanz der Basisklasse kann
immer auch eine Instanz der abgeleitetenKlasse verwendet werden– gilt nicht umgekehrt!– möglichst kompatibles Objekt-Layout– Liskov'sches Substitutionsprinzip [4]
● Methoden können hinzugefügt oderüberdefiniert werden
Baseb1: intb2: int
Derivedd: intf: void ()
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 21
(Einfach-)Vererbung – Ressourcen (1)
● die Attribute der Basisklasse liegen im Speicher am Anfang des Objekts
● keine Zeigeranpassung bei Typumwandlung Base* → Derived* oder Derived* → Base* nötig
struct Base { int b1, b2;};
struct Derived : Base { int d; void f ();};
struct Base { int b1, b2;};
struct Derived : Base { int d; void f ();};
C++-Code der Klassen Objekt-Layout [5]
Base-Instanz
Derived-Instanz
this this
int b1int b2
int d
int b1int b2
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 22
(Einfach-)Vererbung – Ressourcen (2)
● Methoden erhalten den this-Pointerals unsichtbaren ersten Parameter
● Zugriff auf eigene Attribute und Basisklassenattribute kosten gleich viel.
● Kein Overhead durch (Einfach-)Vererbung.
Derived derived;
int main () { derived.f ();}
void Derived::f () { b1 = 1; b2 = 2; d = 3;}
Derived derived;
int main () { derived.f ();}
void Derived::f () { b1 = 1; b2 = 2; d = 3;}
.bssderived: .zero 12
.bssderived: .zero 12
_ZN7Derived1fEv: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl $1, (%eax) movl $2, 4(%eax) movl $3, 8(%eax) popl %ebp ret
_ZN7Derived1fEv: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl $1, (%eax) movl $2, 4(%eax) movl $3, 8(%eax) popl %ebp ret
main: pushl %ebp movl %esp, %ebp pushl $derived call _ZN7Derived1fEv xorl %eax, %eax leave ret
main: pushl %ebp movl %esp, %ebp pushl $derived call _ZN7Derived1fEv xorl %eax, %eax leave ret
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 23
Mehrfachvererbung
● eine abgeleitete Klasse erbt von mehreren Basisklassen– eine auf dem „Einfachvererbungspfad“– N-1 auf dem „Mehrfachvererbungspfad“
● Vererbungshierarchie ist keine Baumstruktur mehr● mehrfaches Erben von derselben Klasse möglich!
Multim: intfm: int ()
Singles: intfs: int ()
Derivedf: void ()
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 24
Mehrfachvererbung – Ressourcen (1)
● die Attribute der Basisklassen liegen nacheinander im Speicher am Anfang des Objekts
● bei der Typumwandlung von Derived* in einen Zeiger auf eine Klasse im Mehrfachvererbungspfad muss ein Offset addiert werden
struct Single { int s; int fs();};
struct Multi { int m; int fm();};
struct Derived : Single, Multi /*, ...*/ { int f ();};
struct Single { int s; int fs();};
struct Multi { int m; int fm();};
struct Derived : Single, Multi /*, ...*/ { int f ();};
C++-Code der KlassenObjekt-Layout [3]
Derived-Instanz
this (Derived)this (Single)
this (Multi)
∆S
......
int s
int m
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 25
Mehrfachvererbung – Ressourcen (2)
● Beim Aufruf einer Methodeder Klasse im Einfach-vererbungspfad kann derthis-Pointer einfachdurchgereicht werden
● Beim Zugriff auf Multi muss this angepasst werden (+ 4)● Bei inline-Methoden tritt das Problem nicht auf● Geringer Overhead bei Mehrfachvererbung
void Derived::f () { fs (); fm ();}
void Derived::f () { fs (); fm ();}
_ZN7Derived1fEv: pushl %ebp movl %esp, %ebp pushl %ebx movl 8(%ebp), %ebx pushl %ebx call _ZN6Single2fsEv addl $4, %ebx pushl %ebx call _ZN5Multi2fmEv popl %eax movl -4(%ebp), %ebx popl %edx leave ret
_ZN7Derived1fEv: pushl %ebp movl %esp, %ebp pushl %ebx movl 8(%ebp), %ebx pushl %ebx call _ZN6Single2fsEv addl $4, %ebx pushl %ebx call _ZN5Multi2fmEv popl %eax movl -4(%ebp), %ebx popl %edx leave ret
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 26
Virtuelle Vererbung● durch virtuelle Vererbung wird
vermieden, dass eine mehrfachgeerbte Basis mehr als einmalinstanziiert wird.
● Speicherplatz im Objekt wirdeingespart
● Mehrdeutigkeiten bei der Namens-auflösung werden vermieden
● Wo werden die Instanzen der virtuellen Basisklasse Common abgelegt?
Derivedf: void ()
Leftl: int
Rightr: int
Commonc: int
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 27
Virtuelle Vererbung – Ressourcen (1)
● die Attribute virtueller Basisklassen liegen am Ende● der Objekttyp-spezifische Offset macht die Typkonvertierung
kompliziert– (mindestens) eine virtuelle Funktionstabelle wird benötigt!
struct Common { int c; };
struct Left : virtual Common { int l;};
struct Right : virtual Common { int r;};
struct Derived : Left, Right { int d; void f ();};
struct Common { int c; };
struct Left : virtual Common { int l;};
struct Right : virtual Common { int r;};
struct Derived : Left, Right { int d; void f ();};
C++-Code der Klassen Objekt-Layout [3]
Derived-Instanz
this (Derived)this (Left)
this (Common)
∆CDerived
VtableDerived
VtableDerived-
Rightint d
int l
int r
∆CDerived
∆CRightint c
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 28
Virtuelle Vererbung – Ressourcen (2)
● Der Zugriff auf ein Attributeiner virtuellen Basisklasseist erheblich komplizierter
● Dazu kommen noch (in diesem Beispiel):– 90 Byte für Tabellen– Konstruktor-Code zum Initialisieren der Vtable-Zeiger
● Deutlicher Overhead bei virtueller Vererbung!– insbesondere, wenn die beteiligten Klassen sonst keine Vtable
benötigen würden
_ZN7Derived1fEv: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl (%eax), %edx movl -12(%edx), %edx movl $1, (%edx,%eax) movl $2, 4(%eax) movl $3, 12(%eax) movl $4, 16(%eax) popl %ebp ret
_ZN7Derived1fEv: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl (%eax), %edx movl -12(%edx), %edx movl $1, (%edx,%eax) movl $2, 4(%eax) movl $3, 12(%eax) movl $4, 16(%eax) popl %ebp ret
void Derived::f () { c = 1; l = 2; r = 3; d = 4;}
void Derived::f () { c = 1; l = 2; r = 3; d = 4;}
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 29
Dynamisches Binden● dynamisches Binden
erfolgt bei virtuellenFunktionen– C++-Schlüsselwort virtual
● die Zielfunktion einesAufrufs wird dabei zurLaufzeit ermittelt– wäre bv() nicht virtuell, würde in den Beispielen immer Base::bv()
ausgeführt werden
– ob Base::bv() oder Derived::bv() ausgeführt wird, hängt vom Objekttyp (nicht vom Zeigertyp) ab
– da Base::b() sowohl auf Base- als auch Derived-Objekten ausgeführt werden kann, muss der Objekttyp ermittelt werden
– da nicht immer zur Übersetzungszeit bestimmt werden kann, worauf p in main() zeigt, muss auch hier der Typ ermittelt werden
Baseb: void ()bv: void ()
Derivedbv: void ()
void Base::b () { bv ();} ?
int main () { Base *p = ...; p->bv ();} ?
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 30
Dynamisches Binden – Ressourcen (1)
● die klassenspezifischen virtuellen Funktions-tabellen enthalten Zeiger auf den passenden Code
● der Konstruktor muss den Vtable-Zeiger eintragen!– ggf. sogar mehrfach überschreiben!
struct Base { void b (); virtual void bv () {}};
struct Derived : Base { void bv () {} // virtuell};
struct Base { void b (); virtual void bv () {}};
struct Derived : Base { void bv () {} // virtuell};
C++-Code der KlassenObjekt-Layout [3]
Derived-Instanz
this (Derived)this (Base)
VtableDerived
VtableBase
Base-Instanz
this (Base)
Base::bv()
Derived::bv()
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 31
Dynamisches Binden – Ressourcen (2)
● Virtuelle Funktionsaufrufe wie inBase::b() und main() bedingen eine Indirektion– kein Inlining solcher Aufrufe möglich!– selbst leere virtuelle Funktionen müssen angelegt werden
● Dynamisches Binden kostet deutlich mehr als statisches!
main: pushl %ebp movl %esp, %ebp pushl $4 call _Znwj movl $_ZTV7Derived+8, (%eax) movl %eax, (%esp) call *_ZTV7Derived+8 xorl %eax, %eax leave ret
main: pushl %ebp movl %esp, %ebp pushl $4 call _Znwj movl $_ZTV7Derived+8, (%eax) movl %eax, (%esp) call *_ZTV7Derived+8 xorl %eax, %eax leave ret
int main () { Base *p = new Derived; p->bv();}
int main () { Base *p = new Derived; p->bv();}
void Base::b () { bv ();}
void Base::b () { bv ();}
_ZN4Base2bvEv: pushl %ebp movl %esp, %ebp popl %ebp ret
_ZN4Base2bvEv: pushl %ebp movl %esp, %ebp popl %ebp ret
_ZN4Base1bEv: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl (%eax), %edx pushl %eax call *(%edx) popl %eax leave ret
_ZN4Base1bEv: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl (%eax), %edx pushl %eax call *(%edx) popl %eax leave ret
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 32
Kosten von Objektorientierung in C++● Einfachvererbung
– praktisch keine zusätzlichen Kosten● Mehrfachvererbung
– Zeigerkonvertierung Mehrfachvererbungspfad, geringer Aufwand● Virtuelle Vererbung
– Vtable-Speicher, Objektinitialisierung, indirekter Zugriff, Aufwand!● Dynamisches Binden
– Vtable-Speicher, Objektinitialisierung, indirekter Aufruf, Aufwand!● Faustregel: In ressourcenbeschränkten Domänen das virtual-
Schlüsselwort nur verwenden, wenn es wirklich nötig ist
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 33
Inhalt● Motivation● Sicheres Programmieren in C und C++
– MISRA C– Embedded C++– Cyclone
● Ressourcenverbrauch von C++ gegenüber C– Laufzeit-Polymorphie– Parametrische Polymorphie
● Zusammenfassung
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 34
● Häufig werden dieselben Algorithmen für verschiedene Datentypen benötigt, z.B. quicksort() für int, float, u.s.w. oder Listen von int-, Foo- oder Bar-Objekten.
● Wie kann der Programmierer damit umgehen, z.B. in C oder Java < 5?– Mehrfachimplementierung des Algorithmus
● Probleme bei der Wartung, Wiederholung von Fehlern, Mühe!– Gemeinsame Basis
● fehlende Typsicherheit bzw. Typüberprüfung erst zur Laufzeit– Präprozessoren (z.B. C-Makros)
● blinde Textersetzung, Scopes und Typen werden ignoriert● Templates sind ein standardisierter [2] C++-Mechanismus,
der alle diese Probleme vermeidet!
Warum Templates?
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 35
● als Template-Parameter kann im Prinzip jeder Typ verwendet werden (keine gemeinsame Basis nötig!)
● Einschränkungen definiert das Template implizit, hier:– T benötigt operator < (const T&) const
– die Aufrufparameter von max() müssen denselben Typ haben, damit T „deduziert“ werden kann
● int i = max(2, 3); // OK● int k = max(4, 4.2); // Fehler
Ein erstes Funktions-Templatemax.hmax.h
template <typename T>inline const T& max (const T &a, const T &b) { return a < b ? b : a;}
template <typename T>inline const T& max (const T &a, const T &b) { return a < b ? b : a;}
Template-Parameter (altern. <class T>)Template-Parameter (altern. <class T>)
T kann wie ein normaler Typverwendet werdenT kann wie ein normaler Typverwendet werden
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 36
Überladen von Funktions-Templatesmax.hmax.h
template <typename T>inline const T& max (const T &a, const T &b) {...}
template <typename T>inline const T& max (const T &a, const T &b, const T &c) {...}
inline const int& max (const int &a, const int &b) {...}
template <typename T>inline const T& max (const T &a, const T &b) {...}
template <typename T>inline const T& max (const T &a, const T &b, const T &c) {...}
inline const int& max (const int &a, const int &b) {...}
main.ccmain.cc
#include "max.h"int main() { max(1,2,3); // Template mit 3 Argumenten max(1.0,2.0); // max<double> per Deduktion max('X','Y'); // max<char> per Deduktion max(1,2); // nicht-Template Variante bevorzugt max<>(1,2); // max<int> per Deduktion max<double>(1,2); // max<double> ohne Deduktion max('X',3.14); // nicht-Template Variante für 2 ints}
#include "max.h"int main() { max(1,2,3); // Template mit 3 Argumenten max(1.0,2.0); // max<double> per Deduktion max('X','Y'); // max<char> per Deduktion max(1,2); // nicht-Template Variante bevorzugt max<>(1,2); // max<int> per Deduktion max<double>(1,2); // max<double> ohne Deduktion max('X',3.14); // nicht-Template Variante für 2 ints}
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 37
● Klassen-Templates sind perfekt für Container-Klassen– darum gibt es auch die Standard Template Library (STL)
Ein erstes Klassen-TemplateVector.hVector.h
template <typename T>class Vector { T *data; int dim;public: // Konstruktor, Copy-Konstruktor(!), ... // Zugriffsfunktionen z.B. mit Indexprüfung: void set (int index, const T& obj); T get (int index) const;};
template <typename T>class Vector { T *data; int dim;public: // Konstruktor, Copy-Konstruktor(!), ... // Zugriffsfunktionen z.B. mit Indexprüfung: void set (int index, const T& obj); T get (int index) const;};
main.ccmain.cc
#include "Vector.h"int main() { Vector<float> a_vector(3); a_vector.set (0, 2.71);}
#include "Vector.h"int main() { Vector<float> a_vector(3); a_vector.set (0, 2.71);}
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 38
● Wenn Methoden von Klassen-Templates nicht im Klassenrumpf definiert werden, müssen sie ähnlich wie ein Funktions-Template formuliert werden:
Methoden von Klassen-Templates
vector.hvector.h
#include <assert.h>template <typename T>void Vector<T>::set (int index, const T& obj) { assert (index < dim); // wird nur in der Debug-Variante // geprüft data[index] = obj; // erfordert operator = in T}
template <typename T>T Vector<T>::get (int index) { assert (index < dim); // wird nur in der Debug-Variante // geprüft return data[index]; // erfordert Copy-Konstruktor}
#include <assert.h>template <typename T>void Vector<T>::set (int index, const T& obj) { assert (index < dim); // wird nur in der Debug-Variante // geprüft data[index] = obj; // erfordert operator = in T}
template <typename T>T Vector<T>::get (int index) { assert (index < dim); // wird nur in der Debug-Variante // geprüft return data[index]; // erfordert Copy-Konstruktor}
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 39
Ressourcenverbrauch von Templates● Bei Inline-Funktionen effizient wie Makros, aber typsicher
● Gefahr des Code Bloat– Auswirkung auf die Codegröße und weniger auf die Performance
testmax.cctestmax.cc
template <typename T>inline const T& max (const T &a, const T &b) { return a < b ? b : a;}
int f() { return max (3, max (1, 2)); }
template <typename T>inline const T& max (const T &a, const T &b) { return a < b ? b : a;}
int f() { return max (3, max (1, 2)); }
_Z1fv: push %ebp mov $0x3,%eax mov %esp,%ebp pop %ebp ret
_Z1fv: push %ebp mov $0x3,%eax mov %esp,%ebp pop %ebp ret
<int const& max<int>(int const&, int const &)>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 8b 4d 08 mov 0x8(%ebp),%ecx 6: 8b 55 0c mov 0xc(%ebp),%edx 9: 8b 01 mov (%ecx),%eax b: 3b 02 cmp (%edx),%eax d: 7c 02 jl 11 f: 89 ca mov %ecx,%edx 11:89 d0 mov %edx,%eax 13:5d pop %ebp 14:c3 ret
<int const& max<int>(int const&, int const &)>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 8b 4d 08 mov 0x8(%ebp),%ecx 6: 8b 55 0c mov 0xc(%ebp),%edx 9: 8b 01 mov (%ecx),%eax b: 3b 02 cmp (%edx),%eax d: 7c 02 jl 11 f: 89 ca mov %ecx,%edx 11:89 d0 mov %edx,%eax 13:5d pop %ebp 14:c3 ret
<long const& max<long>(long const&, long const &)>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 8b 4d 08 mov 0x8(%ebp),%ecx 6: 8b 55 0c mov 0xc(%ebp),%edx 9: 8b 01 mov (%ecx),%eax b: 3b 02 cmp (%edx),%eax d: 7c 02 jl 11 f: 89 ca mov %ecx,%edx 11:89 d0 mov %edx,%eax 13:5d pop %ebp 14:c3 ret
<long const& max<long>(long const&, long const &)>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 8b 4d 08 mov 0x8(%ebp),%ecx 6: 8b 55 0c mov 0xc(%ebp),%edx 9: 8b 01 mov (%ecx),%eax b: 3b 02 cmp (%edx),%eax d: 7c 02 jl 11 f: 89 ca mov %ecx,%edx 11:89 d0 mov %edx,%eax 13:5d pop %ebp 14:c3 ret
identisch!
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 40
Inhalt● Motivation● Sicheres Programmieren in C und C++
– MISRA C– Embedded C++– Cyclone
● Ressourcenverbrauch von C++ gegenüber C– Laufzeit-Polymorphie– Parametrische Polymorphie
● Zusammenfassung
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 41
Zusammenfassung● C und C++ sind nicht typsicher
– Quelle für diverse Fehler und Sicherheitslücken– Teilmengen wie MISRA C und Embedded C++ helfen nur bedingt
● Dafür bieten C und C++ …– (Kosten-)Transparenz
● Man „sieht“ wofür die Ressourcen verbraucht werden● Das Systemverhalten ist vorhersagbar (z.B. kein Garbage Collector)
– Kontrolle über den Ressourcenverbrauch● Man kann den Ressourcenverbrauch beeinflussen (tuning)
… und sind daher dominierend in eingebetteten Systemen● C++ erweitert C primär um zwei neue Paradigmen
– Objektorientierung● Vorsicht Kostenfalle
– Generische Programmierung● Komplex, aber keine Laufzeitkosten → geeignet für eingebettete Produktlinien
03.07.2018 SuS: 06.1 Anwendungsentwicklung in C/C++ 42
Literatur[1] T. Jim, J. G. Morrisett, D. Grossman, M. W. Hicks, J. Cheney, and Y.
Wang. Cyclone: A Safe Dialect of C. In Proceedings of the General Track of the Annual Conference on USENIX Annual Technical Conference (C. S. Ellis, Ed.). USENIX Association, Berkeley, CA, pages 275-288, June 2002.
[2] Guidelines for the Use of the C Language in Critical Systems, ISBN 0 9524156 2 3 (paperback), ISBN 0 9524156 4 X (PDF), October 2004.
[3] P. Wegner. Classification in Object-Oriented Systems, ACM, SIGPLAN Notices, 21(10):173-182, 1986.
[4] B. Liskov. Data Abstraction and Hierarchy, ACM, SIGPLAN Notices, 23(5), 1988.
[5] C++ ABI Summary, http://www.codesourcery.com/cxx-abi