Post on 19-Mar-2021
transcript
11
Software ubiquitärer SystemeAnwendungsentwicklung in C/C++
Olaf SpinczykArbeitsgruppe Eingebettete Systemsoftware
Lehrstuhl für Informatik 12TU Dortmund Olaf.Spinczyk@tu-dortmund.dehttp://ess.cs.uni-dortmund.de/~os/
http://ess.cs.tu-dortmund.de/DE/Teaching/SS2010/SuS/
06.1 – Anwendungsentwicklung in C/C++ 22
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
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
06.1 – Anwendungsentwicklung in C/C++ 44
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
CC++
AssemblerJava.Net
SystemCIEC 61131
4GL SprachenDSL
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
06.1 – Anwendungsentwicklung in C/C++ 55
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
Objektplazierung (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.
06.1 – Anwendungsentwicklung in C/C++ 66
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.
06.1 – Anwendungsentwicklung in C/C++ 77
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 Objekte 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.
06.1 – Anwendungsentwicklung in C/C++ 88
Inhalt● Motivation
● Sicheres Programmieren in C und C++● MISRA C● Embedded C++● Cyclone
● Ressourcenverbrauch von C++ gegenüber C● Laufzeit-Polymorphie● Parametrische Polymorphie
● Zusammenfassung
06.1 – Anwendungsentwicklung in C/C++ 99
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
06.1 – Anwendungsentwicklung in C/C++ 1010
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.
06.1 – Anwendungsentwicklung in C/C++ 1111
Embedded C++ (EC++)● Teilmenge von C++ für eingebettete Systeme
● Festgelegt 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++)
06.1 – Anwendungsentwicklung in C/C++ 1212
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 Coll.)
● Exceptions, Namespaces, Subtyping, u.v.m.
06.1 – Anwendungsentwicklung in C/C++ 1313
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.
06.1 – Anwendungsentwicklung in C/C++ 1414
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.
06.1 – Anwendungsentwicklung in C/C++ 1515
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);}
06.1 – Anwendungsentwicklung in C/C++ 1616
Cyclone: Kosten● Die Sicherheit hat ihren Preis
● Im Vergleich zu anderen sicheren Sprachen ist es jedoch effizient
06.1 – Anwendungsentwicklung in C/C++ 1717
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.
06.1 – Anwendungsentwicklung in C/C++ 1818
Inhalt● Motivation
● Sicheres Programmieren in C und C++● MISRA C● Embedded C++● Cyclone
● Ressourcenverbrauch von C++ gegenüber C● Laufzeit-Polymorphie● Parametrische Polymorphie
● Zusammenfassung
06.1 – Anwendungsentwicklung in C/C++ 1919
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
06.1 – Anwendungsentwicklung in C/C++ 2020
(Einfach-)Vererbung● eine abgeleitete Klasse erbt von einer Basisklasse
● geerbt werden Attribute, Methoden, ...
● statt einer Instanz der Basisklasse kannimmer 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
Base
b1: int
b2: int
Derived
d: int
f: void ()
06.1 – Anwendungsentwicklung in C/C++ 2121
(Einfach-)Vererbung – Ressourcen (1)
● die Attribute der Basisklasse liegen im Speicher am Anfang des Objekts
● keine Zeigeranpassung bei Typumwandlung von Base* in Derived* oder Derived* in 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
06.1 – Anwendungsentwicklung in C/C++ 2222
(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
06.1 – Anwendungsentwicklung in C/C++ 2323
Mehrfachvererbung
● eine abgeleitete Klasse erbt von mehreren Basisklassen
● eine auf dem „Einfachvererbungspfad“
● 1-N auf dem „Mehrfachvererbungspfad“
● Vererbungshierarchie ist keine Baumstruktur mehr
● mehrfaches Erben von der selben Klasse möglich!
Multi
m: int
fm: int ()
Single
s: int
fs: int ()
Derived
f: void ()
06.1 – Anwendungsentwicklung in C/C++ 2424
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
06.1 – Anwendungsentwicklung in C/C++ 2525
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
06.1 – Anwendungsentwicklung in C/C++ 2626
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?
Derived
f: void ()
Left
l: int
Right
r: int
Common
c: int
06.1 – Anwendungsentwicklung in C/C++ 2727
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 KlassenObjekt-Layout [3]
Derived Instanz
this (Derived)this (Left)
this (Common)
∆CDerived
VtableDerived
VtableDerived-
Rightint d
int l
int r
∆CDerived
∆CRightint c
06.1 – Anwendungsentwicklung in C/C++ 2828
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!● inbesondere, 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;}
06.1 – Anwendungsentwicklung in C/C++ 2929
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
Base
b: void ()
bv: void ()
Derived
bv: void ()
void Base::b () { bv ();} ?
int main () { Base *p = ...; p->bv ();} ?
06.1 – Anwendungsentwicklung in C/C++ 3030
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
VtableBaseBase Instanz
this (Base)
Base::bv()
Derived::bv()
06.1 – Anwendungsentwicklung in C/C++ 3131
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 ();}
_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
_ZN4Base2bvEv: pushl %ebp movl %esp, %ebp popl %ebp ret
_ZN4Base2bvEv: pushl %ebp movl %esp, %ebp popl %ebp ret
06.1 – Anwendungsentwicklung in C/C++ 3232
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
06.1 – Anwendungsentwicklung in C/C++ 3333
● Häufig werden die selben 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?
06.1 – Anwendungsentwicklung in C/C++ 3434
● 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 den selben Typ haben,
damit T „deduziert“ werden kann- int i = max(2,3); // ok
- int k = max(4,4.2); // Fehler
Ein erstes Funktions-Template
max.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
06.1 – Anwendungsentwicklung in C/C++ 3535
Ü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}
06.1 – Anwendungsentwicklung in C/C++ 3636
● 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);}
06.1 – Anwendungsentwicklung in C/C++ 3737
● 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 Konstructor}
#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 Konstructor}
06.1 – Anwendungsentwicklung in C/C++ 3838
● neben Typen können auch konstante Ausdrücke als Template-Parameter benutzt werden
Nicht-Typ Template-Parameter
Vector2.hVector2.h
template <typename T, int DIM>class Vector2 { T data[DIM]; // in jedem Objekt steckt ein Arraypublic: // Konstruktor, Copy-Konstruktor(!), ... // Zugriffsfunktionen z.B. mit Indexprüfung: void set (int index, const T& obj); T get (int index) const;};
template <typename T, int DIM>class Vector2 { T data[DIM]; // in jedem Objekt steckt ein Arraypublic: // Konstruktor, Copy-Konstruktor(!), ... // Zugriffsfunktionen z.B. mit Indexprüfung: void set (int index, const T& obj); T get (int index) const;};
● aber Achtung:
Vector<int> v1(10)Vector<int> v2(20); // v1 und v2 haben den selben TypVector2<int,10> v2_1; // v2_1 und v2_2 habenVector2<int,20> v2_2; // unterschiedliche Typen!
Vector<int> v1(10)Vector<int> v2(20); // v1 und v2 haben den selben TypVector2<int,10> v2_1; // v2_1 und v2_2 habenVector2<int,20> v2_2; // unterschiedliche Typen!
06.1 – Anwendungsentwicklung in C/C++ 3939
● Templates, die Templates benutzen sollen, können auch Templates als Parameter bekommen
Template Template-Parameter
DataCollector.hDataCollector.h
template <template <typename> class Container>class DataCollector { Container<double> collected_data; // Container wird benutztpublic: // ...};
template <template <typename> class Container>class DataCollector { Container<double> collected_data; // Container wird benutztpublic: // ...};
● so wird’s benutzt:
DataCollector<Vector> dc; // alles OK
DataCollector<Vector2> dc2; // geht nicht! // (Vector2 erwartet 2 Parameter)
DataCollector<Vector> dc; // alles OK
DataCollector<Vector2> dc2; // geht nicht! // (Vector2 erwartet 2 Parameter)
Parameter ist ein Template mit einem ParameterParameter ist ein Template mit einem Parameter
06.1 – Anwendungsentwicklung in C/C++ 4040
● ähnlich wie bei Default-Argumenten von Funktionen können auch Template-Parameter ein Default haben● wie bei Funktionen müssen dann nachfolgende Parameter auch ein
Default haben
Default Template-Parameter
DataCollector2.hDataCollector2.h
template <template <typename,int> class Container=Vector2, int DIM=32>class DataCollector2 { Container<double, DIM> collected_data; // Container und DIMpublic: // ...};
template <template <typename,int> class Container=Vector2, int DIM=32>class DataCollector2 { Container<double, DIM> collected_data; // Container und DIMpublic: // ...};
● so wird’s benutzt:DataCollector2<> c1; // auf <> kann man nicht // verzichtenDataCollector2<Vector2> c2; // DIM ist 32 per DefaultDataCollector2<FooBar, 10> c3; // Default nicht genutzt
DataCollector2<> c1; // auf <> kann man nicht // verzichtenDataCollector2<Vector2> c2; // DIM ist 32 per DefaultDataCollector2<FooBar, 10> c3; // Default nicht genutzt
zwei Parameter mit Defaultszwei Parameter mit Defaults
06.1 – Anwendungsentwicklung in C/C++ 4141
● mit Hilfe der Template Spezialisierung können unterschiedliche Template Implementierungen in Abhängigkeit von den Parametern gewählt werden
● Explizite Template Spezialisierung● für einen bestimmten Parametersatz, z.B. Vector<bool>
● Partielle Template Spezialisierung● für eine Parametermenge, z.B. Vector<const T*> mit beliebigem T● geht nicht bei Funktions-Templates
● wenn sowohl eine explizite als auch eine partielle Spezialisierung passen, wird die explizite gewählt
● passt keine Spezialisierung, wird das primäre Template genommen
● Template-Spezialisierung bewirkt eine Fallunterscheidung● wichtige Grundlage für Template-Metaprogrammierung
Template Spezialisierung
06.1 – Anwendungsentwicklung in C/C++ 4242
● kann z.B. zur Optimierung benutzt werden:
Explizite Template Spezialisierung
● mit der Spezialisierung für bool wird für Vector<bool> auf dem Heap nur ein Achtel des Speicherplatzes angefordert
Vector.hVector.h
template <>class Vector<bool> { unsigned *data; // bool wird als Bit gespeichert int dim;public: // Konstruktor Vector(int d) { data = new unsigned[1 + (d-1) / (sizeof(unsigned)*8)]; } // Copy-Konstruktor(!), Zugriffsfunktionen, ...};
template <>class Vector<bool> { unsigned *data; // bool wird als Bit gespeichert int dim;public: // Konstruktor Vector(int d) { data = new unsigned[1 + (d-1) / (sizeof(unsigned)*8)]; } // Copy-Konstruktor(!), Zugriffsfunktionen, ...};
explizite Spezialisierung für boolexplizite Spezialisierung für bool
06.1 – Anwendungsentwicklung in C/C++ 4343
● kann z.B. die gleiche Optimierung für Vector2 realisieren:
Partielle Template Spezialisierung
Vector2.hVector2.h
template <int DIM>class Vector2<bool, DIM> { typedef unsigned storage_t; static const unsigned BITS = sizeof(storage_t) * 8; storage_t data[1 + (DIM - 1) / BITS];public: // Konstruktor, Copy-Konstruktor(!), ... // Zugriffsfunktionen z.B. mit Indexprüfung: void set (int index, bool obj) { if (obj) data[index / BITS] |= (1 << (index % BITS)); else data[index / BITS] &= ~(1 << (index % BITS)); } // ...};
template <int DIM>class Vector2<bool, DIM> { typedef unsigned storage_t; static const unsigned BITS = sizeof(storage_t) * 8; storage_t data[1 + (DIM - 1) / BITS];public: // Konstruktor, Copy-Konstruktor(!), ... // Zugriffsfunktionen z.B. mit Indexprüfung: void set (int index, bool obj) { if (obj) data[index / BITS] |= (1 << (index % BITS)); else data[index / BITS] &= ~(1 << (index % BITS)); } // ...};
partielle Spezialisierung für bool und beliebige DIMensionpartielle Spezialisierung für bool und beliebige DIMension
06.1 – Anwendungsentwicklung in C/C++ 4444
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
identis
ch!
06.1 – Anwendungsentwicklung in C/C++ 4545
Inhalt● Motivation
● Sicheres Programmieren in C und C++● MISRA C● Embedded C++● Cyclone
● Ressourcenverbrauch von C++ gegenüber C● Laufzeit-Polymorphie● Parametrische Polymorphie
● Zusammenfassung
06.1 – Anwendungsentwicklung in C/C++ 4646
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 eingeb. Produktlinien
06.1 – Anwendungsentwicklung in C/C++ 4747
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