Warum machen Betriebssysteme Low-Level-Sachen in C und C ++? Warum nicht einfach C ++?


20

Auf der Wikipedia-Seite für Windows heißt es, dass Windows für den Bootloader und den Task-Switcher in Assembly und für Kernel-Routinen in C und C ++ geschrieben ist.

IIRC können Sie C ++ - Funktionen aus einem extern "C"'d-Block aufrufen . Ich kann C für die Kernelfunktionen verwenden, damit reine C-Apps sie (wie printfund so) verwenden können. Wenn sie jedoch nur in einen extern "C "Block eingeschlossen werden können, warum dann Code in C?


10
Haben Sie die verschiedenen "Warum C verwenden, wenn es C ++ gibt?" Fragen hier? Dies ist nicht unbedingt ein Duplikat von ihnen, aber sie sind verwandt.

1
msgstr "" "Sie können C ++ - Funktionen von einem externen" C "- Block als C ++ aufrufen." Meinen Sie, Sie können C - Funktionen aufrufen ...?
Code-Guru

@ Code-Guru nein, denn der einzige Unterschied zwischen exportierten C- und C ++ - Funktionen besteht in der Namensdekoration und in C ++ in der Hinzufügung der thisVariablen
Cole Johnson

2
Werfen Sie eine Ausnahme in einem ISR und sehen Sie, was passiert
James

Noch eine C vs. C ++ Frage.
Machado

Antworten:


31

Es ist vor allem aus historischen Gründen. Einige Teile des Windows-Kernels wurden ursprünglich in C geschrieben, da C ++ vor über drei Jahrzehnten, als Windows 1.0 veröffentlicht wurde , kaum veröffentlicht wurde. Jetzt werden diese C-Bibliotheken "für immer" dort bleiben, weil Microsoft die Abwärtskompatibilität zu einem Verkaufsargument gemacht hat und das Umschreiben einer bug-kompatiblen Version der C-Teile in C ++ eine Menge Aufwand erfordert, ohne dass ein effektiver Nutzen entsteht.


+1, ich denke, das ist die realistischste Antwort (abgesehen von der Tatsache, dass es einige Windows-Kernel-Entwickler geben könnte, die C ++ einfach nicht mögen oder C ++ - Compilern für diese Dinge auf niedriger Ebene nicht vertrauen). Schauen Sie sich zum Beispiel hier stackoverflow.com/questions/520068/… an , warum der Linux-Kernel in C geschrieben ist.
Doc Brown

@ back2dos - Obwohl der C-Code nicht weggeworfen wird, bedeutet dies nicht, dass er verwendet oder nicht aktualisiert wird. Ich garantiere Ihnen, dass es mindestens eine Methode gibt, die etwas tut, das ursprünglich in einer C-Bibliothek geschrieben und enthalten war, die als C ++ - Bibliothek in Windows 8
Ramhound

8
Es fällt mir schwer zu glauben, dass in einer aktuellen Windows-Version Windows 1.0-Code enthalten ist. Windows ME war die letzte Windows-Version, die nicht auf der Windows NT-Codebasis basierte. Und auch das wurde weitgehend durch den neuen RT-Kernel ersetzt (der meiner Meinung nach in Bezug auf die Rückwärtskompatibilität nicht viel verspricht).
TMN

@TMN: Das Argument ist höchstwahrscheinlich richtig entweder - ich bin ziemlich sicher , dass auf dem Weg von Win 1,0 bis jetzt gibt es war viele Bibliotheken in C geschrieben , die nach wie vor Teil der aktuellen Win8 Code - Basis sind, und niemand bei MS sieht jede Nutzen Sie, um sie in C ++ umzuschreiben.
Doc Brown

1
Es gibt sowieso kaum einen Code, der mit dem Windows-Kernel kommuniziert. Grundsätzlich nur Fahrer. Anwendungen kommunizieren normalerweise mit der Win32-API oder möglicherweise mit der POSIX-API.
MSalters

24

Wie die meisten Leute betont haben, sind Gründe bei weitem historisch, aber es gibt noch etwas, das niemand erwähnt, und ich glaube, es ist der Grund, warum die Leute immer noch C-Code für Low-Level schreiben.

C ist eine kleine Sprache in dem Sinne, dass die Spezifikation (relativ) kurz ist. C ++ ist riesig und das ist eine Untertreibung. Dies mag für den Programmierer nicht so wichtig sein (obwohl ich denke, dass dies der Fall ist), aber es ist äußerst wichtig, wenn Sie eine formale Überprüfung durchführen möchten . Darüber hinaus gibt es etablierte Tools für die C-Code-Analyse, die helfen können, Fehler zu vermeiden.

Und dies ist sehr wichtig bei eingebetteter Software, bei der die Kosten eines bereitgestellten Fehlers im Vergleich zum Rest der Branche extrem hoch sind (vergleiche das Web, bei dem Sie einen Patch sofort auf alle Benutzer anwenden können). Ganz zu schweigen von unternehmenskritischer Software und medizinischen Dingen.

Es gab Versuche, C von seiner dominierenden Position in der Low-Level-Programmierung durch Sprachen zu verdrängen, die diesbezüglich noch besser sind, wie BitC, aber bisher waren sie nicht erfolgreich.


4
+1 Für die Erwähnung unternehmenskritischer Software in reinem C (z. B. Flugsoftware). In der medizinischen Software wird jedoch auch C ++ verwendet (umfangreiche Tests werden anstelle einer formalen Verifizierung verwendet, was in C ++ extrem schwierig wäre).
Giorgio

1
Außerdem hat C eine ziemlich erfolgreiche sichere Teilmenge von MISRA-C. Es gibt Äquivalente für C ++, aber sie sind (noch) kein Industriestandard. Der Trend in der sicherheitskritischen Programmierung ist, dass Bibliotheken und Compiler auch gezwungen sein werden, eine sichere Teilmenge wie MISRA zu verwenden. Das Neuschreiben einer MISRA-C ++ kompatiblen Version der gesamten C ++ - Standardbibliothek wird höchstwahrscheinlich ein Albtraum sein.

2
@Lundin MISRA-Richtlinien sind keine sichere Teilmenge - Sie haben immer noch unformatierte Zeiger und die meisten anderen Funktionen, die C unsicher machen - sie konzentrieren sich hauptsächlich darauf, implementierungsspezifisches Verhalten nicht zu verwenden oder zu dokumentieren.
Pete Kirkham

@PeteKirkham MISRA-C erkennt alle klassischen Zeigerfehler und erzwingt statische Analysen. Industrielle Sicherheitsstandards (IEC 61508 et al.) Genehmigen offenbar MISRA-C als sichere Untergruppe von C. Es gibt nicht viele andere nützliche Alternativen für unternehmenskritische Software, es sei denn, Sie entscheiden sich für SPARK Ada, eine Sprache, die nur wenige kennen und die nur über ein begrenztes Tool verfügt Unterstützung.

2
Dies sollte als die beste Antwort ausgewählt worden sein, da es korrekt ist. Andere, die C nur aus historischen Gründen vorschlagen, sind an sich hysterisch.
Rob

15
  1. Die C-Laufzeit ist viel kleiner.
  2. Die Übersetzung von C ++ in Konstrukte niedrigerer Ebene ist weniger transparent als in C. (Siehe Referenzen und vtables für zwei kurze Beispiele)
  3. C hat normalerweise einen stabilen ABI. C ++ funktioniert normalerweise nicht. Dies bedeutet, dass die Systemaufrufschnittstelle im Minimum C-Stil sein sollte. Wenn Sie dynamische Module benötigen, ist eine konsistente ABI von großem Nutzen.

+1 für (2) und (3). Ich bin nicht überzeugt von (1).
Thomas Eding

7
@Thomas Eding: Die C ++ - Laufzeit besteht aus der C-Laufzeit sowie C ++ - Funktionen wie Ausnahmen, RTTI und einem weiteren Speicherzuweiser. YMMV, ob das viel größer ist. (Und dann ist da noch die Standardbibliothek ...)
wnoise

Ah, ich habe Ausnahmen und neue / Löschpools vergessen. RTTI benutze ich allerdings nie: D
Thomas Eding

11

Die Gründe sind nicht technisch. Ein wenig Montage ist unvermeidlich, aber sie sind nicht gezwungen , die gelegentliche C zu verwenden, sie wollen zu. Mein Unternehmen verwendet einen eigenen proprietären Kernel, der fast ausschließlich in C ++ geschrieben ist, aber wir müssen keine C-Schnittstelle zum Kernel wie die meisten anderen unterstützen, da unser eingebetteter Kernel mit unseren C ++ - Anwendungen monolithisch kompiliert wird. Wenn Sie eine C-Schnittstelle haben, ist es oft einfacher, den Schnittstellencode in C zu schreiben, obwohl dies möglich ist zu verwenden extern "C"es in C ++ zu schreiben.

Sogar wir haben ein bisschen C-Dateien, hauptsächlich aufgrund von Code von Drittanbietern. Low-Level-Code von Drittanbietern wird fast immer in C bereitgestellt, da es viel einfacher ist, C-Code in eine C ++ - App zu integrieren, als umgekehrt.


6

Kernel-Entwickler sind oft die Art von Leuten, die sich glücklicher fühlen, wenn aus der Quelle sofort ersichtlich ist, was der Code tatsächlich tut.

C ++ hat viel mehr Funktionen, die verbergen, was der Code bewirkt, als dass er durch einfachen C-Code verdeckt wird: Überladungen, virtuelle Methoden, Vorlagen, Verweise, Throws ... C ++ hat auch wesentlich mehr Syntax, die Sie beherrschen müssen, um das C ++ überhaupt zu verstehen Code mit ihm.

Ich denke, Power of C ++ ist ein sehr mächtiges Werkzeug, um Bibliotheken und Frameworks zu erstellen, die dann die Anwendungsentwicklung zum Kinderspiel machen. Sehr oft ist der C ++ - Anwendungsentwickler in den mit Vorlagen gefüllten Innereien einer Bibliothek völlig verloren, selbst wenn er sehr kompetent darin ist, Anwendungen mit dieser Bibliothek zu erstellen. Das Schreiben einer C ++ - Bibliothek ist eine sehr herausfordernde Programmieraufgabe, die nur durchgeführt wird, um ein hervorragendes Framework zum Nutzen der Anwendungsentwickler bereitzustellen. C ++ - Bibliotheken sind intern nicht einfach, sie sind (oder können es sein ...) aus Sicht der Anwendungsprogrammierer nur leistungsstark und dennoch einfach.

Die Kernel-API kann jedoch keine C ++ - API sein, sondern muss eine sprachunabhängige API sein, sodass die meisten nützlichen Dinge in C ++ an dieser Schnittstelle nicht direkt verwendet werden können. Darüber hinaus ist der Kernel nicht wirklich in "Bibliotheks" - und "Anwendungs" -Teile unterteilt, die unabhängig voneinander entwickelt wurden, wobei logischerweise mehr Aufwand für eine Bibliothek erforderlich ist, um die Erstellung einer Vielzahl von Anwendungen zu vereinfachen.

Außerdem sind Sicherheit und Stabilität in einem Kernel wichtiger, und virtuelle Methoden sind viel dynamischer und daher viel schwieriger zu isolieren und zu überprüfen als einfache Rückrufe oder andere C-ähnliche Mechanismen.

Kurz gesagt, obwohl Sie natürlich jedes C-Programm einschließlich eines Kernels als C ++ schreiben können, wird der größte Teil der Leistungsfähigkeit von C ++ im Kernel nicht gut genutzt. Und viele würden argumentieren, dass Programmiertools Sie daran hindern sollten, Dinge zu tun, die Sie nicht tun sollten. C ++ würde das nicht.


+1. Als Kernel-Entwickler lautet meine "Faustregel", dass die Sprache, die Sie verwenden, mehr schadet als nützt, wenn Sie nicht abschätzen können, dass die Cache-Zeilen leicht berührt werden.
Brendan

Erläuterung: Bei Kerneln muss davon ausgegangen werden, dass die CPU die meiste Zeit im Benutzerbereich verbringt und der Kernelcode sporadisch verwendet wird (z. B. wenn der Benutzerbereich die Kernel-API aufruft oder ein Interrupt auftritt). was bedeutet, dass Sie "kalten Cache" annehmen müssen. Bei modernen CPUs (bei denen der RAM im Verhältnis zur CPU-Geschwindigkeit langsam ist) sind Cache- und TLB-Ausfälle teuer. In Verbindung mit der Erwartung eines "kalten Caches" wird die Metrik "Cache-Zeilen berührt" zu einem äußerst wichtigen Indikator für die Leistung und / oder Skalierbarkeit.
Brendan

5

Bjarne Stroustrup in einem Interview im Juli 1999 :

Keine dieser Sprachen war radikal anders oder dramatisch besser als andere zeitgenössische Sprachen. Sie waren jedoch gut genug und die Nutznießer von Glück und sozialen Faktoren


2
Willkommen David. Wenn Sie zitieren oder zitieren, ist es eine gute Idee, eine Referenz anzugeben (hinzugefügt!)
Andrew

3

C ist eine sehr einfache Sprache. Es ist einen Schritt vom Assembler entfernt. Wenn Sie den Chipsatz kennen, auf den Sie abzielen, können Sie C mit ein wenig Wissen manuell in ASM "kompilieren". Diese Art von "Metall-nahe" Sprache ist der Schlüssel für ein hohes Maß an Optimierung (für Leistung, Speichereffizienz usw.). Da es jedoch so nah am Metal ist, bekommt man mit dieser Sprache nicht viel umsonst. Es ist eine prozedurale, nicht objektorientierte Sprache, und daher erfordert die Arbeit mit solchen Konstrukten eine Menge Code, um mehrwertige Konstrukte im Speicher zu erstellen und zu konsumieren.

C ++ ist "C one better" und fügt eine Reihe benutzerfreundlicher Funktionen wie dynamische Speicherzuweisung, integriertes Struktur-Marshalling, eine große Bibliothek mit vordefiniertem Code usw. auf Kosten einiger hinzu Effizienzverluste (noch viel besser) als verwaltete Laufzeitumgebungen). Für den durchschnittlichen Codierer überwiegen die Vorteile bei weitem die Nachteile in Bereichen der Codebasis, in denen keine anal-remanente Steuerung der Speicherzuweisung usw. erforderlich ist.

Die Kombination der beiden ist eine ziemlich traditionelle; Mit C schreiben Sie die leistungskritischsten, speichereffizientesten Bereiche der Codebasis, mit denen Sie über Methodenaufrufe aus C ++ - Code, der eleganter organisiert und entworfen werden kann als der überaus leistungsfähige Code, abstrakter arbeiten können Sehr optimierter C-Code.


2
In Bezug auf die Effizienz wiederhole ich Folgendes: (1) Die C ++ - Leute werden Ihnen sagen, dass das Unsinn ist. (2) Ich sage, ich sehe keinen Grund, warum dies der Fall sein sollte, und möchte konkrete Beispiele. Keine Beispiele dafür, wie einfach es ist, weniger effizienten Code zu schreiben, sondern Beispiele dafür, wie effizient C zu sein, übermäßige Hässlichkeit erfordert.

3
Definiere "hässlich"; Ich programmiere meinen Lebensunterhalt in C #, und wenn ich C ++ - Codebeispiele in StackOverflow sehe, sehe ich, wie viel Cruft im alltäglichen Gebrauch der Sprache als normal angesehen wird. Als ich vor langer Zeit in C ++ codierte, sah ich oft C-Code und duckte mich. Handpacken von Strukturtypen, Ausführen von Zeigerberechnungen für manuelle Sprünge, eingebettetes ASM ... yuck. Einige beklagen den Verlust von Grundkenntnissen unter Java / .NET-Programmierern. Ich halte es für einen enormen Segen für die Produktivität.
KeithS

1
Du hast meine Frage nicht beantwortet;) Mit "hässlich" meine ich "hässlich nach C ++ - Standards der Gurus". Mit anderen Worten, Beispiele, bei denen man "modernes C ++" nicht verwenden und nicht so effizient sein kann wie C.

2
Das mag wahr sein (ich weiß es ehrlich gesagt nicht). Für die Kernel-Entwicklung (und einige andere Bereiche) sprechen wir jedoch nicht über durchschnittliche Programmierer.

3
Die Leute vergessen, dass C -> C ++ ein Kontinuum ist. Zu oft vergleichen diese Argumente gutes, modernes C ++ mit C. Sie können ein C-Programm nehmen und es in relativ kurzer Zeit mit einem C ++ - Compiler kompilieren lassen, und es wird genauso schnell ausgeführt. Wenn eine moderne C ++ - Funktion "zu langsam" ist, verwenden Sie sie nicht. Das kann sogar Dinge wie beinhalten iostream. "Zu langsam" ist niemals eine gute Ausrede, um C über C ++ zu verwenden.
Gort the Robot

1

Es kann sein, dass Sie mit C den größten Teil Ihrer Zeit verbringen, um über das vorliegende Problem nachzudenken und die Lösung zu codieren. In C ++ müssen Sie sich am Ende mit C ++ und seinen unzähligen Features, Funktionen und der obskuren Syntax auseinandersetzen.

Auch in vielen C ++ - Shops wird Ihr Code von der "Modepolizei" überwacht, die von den neuesten Designmustern oder den neuesten unverständlichen Aussagen des großen Gottes Stroustrup fasziniert ist. Hübscher Code hat einen höheren Stellenwert als Arbeitscode. Die Verwendung des neuesten Satzes von Boost-Vorlagen wird mehr bewundert als die Suche nach einer funktionierenden Lösung für das Unternehmen.

Ich habe die Erfahrung gemacht, dass bei all den cleveren Funktionen und der OO-Reinheit von C ++ das Codieren in einfachem C die Aufgabe schneller und effektiver erledigt.


0

Es ist möglich, dass die C-Teile nicht gut auf den C ++ - Compiler portierbar sind, der für die C ++ - Teile verwendet wird. Möglicherweise ist der C-Code mit dem C-Compiler in einer Weise befreundet, die mit dem C ++ - Compiler nicht kompatibel ist.

Wenn Sie einen hochwertigen C ++ - Compiler haben, gibt es fast keinen Grund, C und C ++ in einem Projekt zu mischen. Fast.

Der eine Grund wäre, dass Ihr Projekt C-Code mit anderen Projekten teilt, der Code nicht als C ++ kompiliert wird und Sie keine C ++ - Verzweigung dieses Codes verwalten möchten.


-1

Ich denke, Sie haben es rückwärts - der extern "C"Block stellt sicher, dass die C-Aufrufkonventionen für alle Funktionen innerhalb des Blocks verwendet werden. Sie können also reine C-Funktionen aus C ++ aufrufen, nicht C ++ - Funktionen aus C. Ungeachtet dessen stelle ich mir vor, dass der Grund, warum sowohl C als auch C ++ verwendet werden, darin besteht, dass viele Bibliotheken auf niedriger Ebene mit C geschrieben werden und es einfacher ist, etwas zu verwenden, das existiert bereits (und ist vermutlich debuggt und optimiert) als das Schreiben Ihrer eigenen. OTOH, C ++ bietet viele nette Funktionen auf hohem Niveau, mit denen die Leute lieber arbeiten würden, sodass sie diese für den Rest verwenden.


-2

Es gibt verschiedene Ebenen eingebetteter Plattformen, auf denen C als Programmiersprache verwendet wird (natürlich können Sie die Assemblersprache jederzeit verwenden).

Für 'Level' spreche ich von der internen SRAM- und ROM-Ressourcenebene für ein System.

Diese Plattformen sind manchmal ressourcenbeschränkt (z. B. haben einige 8051-Plattformen nur 128 Byte User-SRAM).

Es ist sinnlos, eine dynamische Speicherzuweisung mit einer so geringen Menge an RAM zu unterstützen. (neu / löschen) oder auch Malloc in C.

Eine der wichtigsten Verbesserungen von C auf C ++ ist das objektorientierte Paradigma. C ++ eignet sich für Software mit größerem Speicherbedarf

Dies gilt jedoch nicht für eingebettete Firmware mit einer Größenbeschränkung von bis zu 32 KB. (zB in einer 16-Bit-MCU-Plattform)

Es ist kein C ++ - Compiler erforderlich, der im Allgemeinen komplizierter ist als der C-Compiler. (Zumindest SDK-Anbieter werden sich nicht darum kümmern).

In der Tat kann ich kaum einen C ++ - Compiler über eine 32-Bit-ARM7-Plattform finden.

Die Komplexität ist es einfach nicht wert

In einigen 8051 (8 Bit): 1MB ROM, 128B RAM

TI MSP430 (16 Bit): 32 KB ROM, 4 KB RAM

ST Microelectronics ARM 32-Bit-Cortex ™ -M3-CPU-Kern (STM32F103T4): 16 oder 32 KByte Flash-Speicher 6 oder 10 KByte SRAM


2
Dies ist nichts Neues für die anderen Antworten, die bereits hier gepostet wurden.
Martijn Pieters

Ein 32-Bit-C ++ - Compiler für ARM? Wenn Sie möchten, können Sie LLVM nach einigen Änderungen selbst aus der Quelle kompilieren, um einen C ++ - Compiler für iOS zu erhalten.
Cole Johnson

-4

Ich sehe ein paar mögliche Gründe:

  • C ist im Vergleich zu C ++ etwas effizienter.
  • Einige Bibliotheken, die sie benutzen, sind in C geschrieben.
  • Sie verwenden einige Teile des Linux-Kernels, der in C geschrieben ist.

Bearbeitet: Wie sich herausstellt, ist das dritte Argument nicht wahr (siehe Kommentare).


5
(1) Die C ++ Leute würden anfangen, sich zu unterscheiden. Und objektiv sehe ich keinen Grund dafür. (2) C ++ kann C-Code problemlos aufrufen (das ist der springende Punkt bei der Abwärtskompatibilität und extern "C").

1
Wenn MS einige Teile des Linux-Kernels verwendet und der Linux-Kernel GPL ist, würde das nicht bedeuten, dass Windows auch GPL sein müsste?
TomJ

1
@TomJ tatsächlich war das der Grund, warum sie gezwungen waren, auch ein paar tausend Zeilen an Linux zu spenden. + Warum unterstützen sie Ihrer Meinung nach die Linux-Entwicklung?
Pijusn

2
@Pius Sein Punkt ist (und er hat Recht, AFAIK), wenn der Code der GPL mit dem Windows-Kernel verknüpft wäre, müsste der gesamte Kernel mit der GPL versehen werden (vorausgesetzt, es gibt keine separate Vereinbarung mit den Copyright-Inhabern).

@delnan, über die Details nicht sicher. Ich bin kein Anwalt und kann mich daher nicht dazu äußern. Aber es gab vor ein paar Jahren einen kleinen Skandal. Ich bin mir nicht sicher, aber ich glaube, ich war vielleicht das Netzwerkmodul, um das es in all den verrückten Dingen ging.
Pijusn

-4

Weil C wohl eine bessere Sprache ist als C ++. Und weil ein Teil des Codes geschrieben wurde, bevor C ++ populär wurde und die Leute keinen Grund hatten, ihn zu ersetzen.

Und weil C ++ viele Funktionen hat, die Ihren Code beschädigen können, wenn Sie bei der Verwendung in einem Kernel nicht vorsichtig sind.


C ++ erwartet dynamische Bibliotheken? Und was ist dynamisches Gedächtnis?

4
-1 für [stuff] that C++ expects. Warum verwendet C ++ mehr Heap als C? Warum benötigt C ++ mehr DLLs als C? Einfach nicht wahr
Thomas Eding

1
Korrektur: Sie verlieren die in C ++ geschriebenen Bibliotheken. Wenn Sie also C ++ oder C verwenden, kehren Sie zu Quadrat 1 zurück. Warum beschränken Sie sich auf die C-Funktionalität, wenn Sie Vorlagen, Klassen, Destruktoren, const und alle möglichen anderen Funktionen verwenden können?
Thomas Eding

6
Was? Vorlagen, Ausnahmen, Objekte (einschließlich Konstruktoren, Destruktoren, überladene Operatoren, jetzt Verschiebungskonstruktion und - praktisch? - alles andere) usw. funktionieren einwandfrei, unabhängig davon, woher der Speicher stammt (Stapelspeicher, statischer Speicher, Ihr benutzerdefinierter Pool oder was auch immer). . Die Standardcontainer verwenden standardmäßig die Heap-Zuweisung, aber ihre Zuweiser können angepasst werden, und die Kernel-Mitarbeiter schreiben ohnehin ihre eigenen Sammlungen und ihre eigene Speicherverwaltung. -1

2
Zu Ihrer Information: Ich habe meine Ablehnung entfernt, weil ich nicht denke, dass Ihre Antwort jetzt aktiv schädlich ist, aber ich halte sie auch nicht für besonders nützlich oder aufschlussreich.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.