Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

41

29.06.2012, 22:53

Laut http://www.c-plusplus.de/forum/p1956793 handelt es sich genau bei diesen Code um den aus dem besagten Buch. Wenn du wert drauf legst, kann ich natürlich nach dem WE mich mal in die UNI einklinken und das entsprechende Buch runterladen, um die Aussage aus dem Post zu verifizieren, aber ich bezweifle stark dass da ein falscher Code gepostet wurde.
Nebenbei zerchst du Beispiele hervor bei denen explizit erwähnt wird, dass es für general propose Hardware eher weniger relevant sind und argumentierst beim nächsten Beispiel, dass es sich ja nur auf Kontroller beziehen würde. Dabei geht es bei dem Thema eigentlich nur um das Kompilat und nicht um die Zielhardware, oder woher nimmst du die Information, dass das "Problem" nur auf kleine Mikrocontroller beschränkt sein sollte?
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

42

29.06.2012, 23:23

Ich habe mir auch gerade mal diesen Thread reingezogen.

Ich möchte ein einfaches und sehr praxisnahes Beispiel bringen (das dich, S. P. Gardebiter, unmittelbar betrifft), wo man mit C++ sowohl Arbeit spart als auch effizienteren Code erhält.

Ausgangssituation:
Eine Liste mit Zeigern auf Objekte. Jedes Objekt hat einen Z-Wert, und nach diesem sollen sie sortiert werden.
Damit's schön einfach bleibt, ist die Liste ein Array.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
struct Object // kommt dir das Beispiel bekannt vor? ;)
{
    float x, y;
    int z;
    float rotation;
    float width, height;
    Texture* texture;
};

Object* myObjects[1000];


Der C-Weg:
Du benutzt die qsort()-Funktion der Standardbibliothek. Dazu musst du eine Funktion schreiben, die zwei Objekte vergleicht:

C-/C++-Quelltext

1
2
3
4
5
6
int compareObjects(const void* objA, const void* objB)
{
    return (*((const Object**)objA))->z - (*((const Object**)objB))->z;
}

qsort(myObjects, 1000, sizeof(Object*), compareObjects);

Man beachte, dass die Funktion void-Zeiger erhält, die ich erst wieder in meine Objektzeiger casten muss. Unschön und fehleranfällig, weil ich gar nicht mitbekomme, wenn ich in den falschen Typ caste (erst dann, wenn es kracht)!

Der C++-Weg:
Es gibt verschiedene Varianten. Auch hier gibt's eine Funktion, die sich std::sort nennt. Sie ist aber etwas anders als qsort, wie du gleich sehen wirst.
Auch hier müssen wir eine Vergleichsfunktion schreiben, nur ist sie diesmal in einer Klasse.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
class ObjectComparer
{
public:
    bool operator ()(const Object* objA, const Object* objB)
    {
        return objA->z < objB->z;
    }
};

std::sort(myObjects, myObjects + numObjects, ObjectComparer());

Beachte, dass die C++-Funktion direkt Object-Zeiger als Parameter erhält. Das ist sinnvoll und gut, weil ich ja auch Object-Zeiger sortieren will. Wenn die Funktion andere Parameter erwarten würde, bekäme ich schon zur Compile-Zeit einen Fehler.

Nun ist es so, dass die C++-Version schneller ist als die C-Version.
Obendrein bekommt man noch Typensicherheit dazu. Die C-Funktion "compareObjects" hat als Parameter nur void-Zeiger. Die Information über den Typ, der dahinter steckt, ist verloren. Mit den void-Zeigern kann man machen, was man will - vor allem kann man sie auch in den falschen Datentyp casten (ist mir sogar passiert, als ich das Beispiel geschrieben habe - danke an Dot :-)). In der C++-Version kann das nicht passieren, denn dann gibt's direkt wie oben gesagt einen Compiler-Fehler.

Warum ist die C++-Version schneller? Der Compiler weiß, weil std::sort ein Template ist, schon zur Compile-Zeit, wie die Objekte verglichen werden. Wenn er gut optimiert, ist der Code identisch mit dem, den man per Hand schreiben würde, wenn man ein Sortierverfahren für genau diesen Objekt-Datentyp programmiert. Der C-Compiler weiß dies nicht und muss für jeden Vergleich die "compareObjects"-Funktion aufrufen. Er kann die Vergleichsfunktion im Gegensatz zum C++-Compiler nicht inlinen. [danke Spiele Programmierer für die Tippfehler-Korrektur]

Was wäre die Alternative, um auch in C diese hohe Geschwindigkeit zu erreichen? Man müsste für jeden Datentyp, den man sortieren will, einen eigenen Sortier-Code schreiben, in dem man die Vergleichsfunktion direkt inline implementiert. Das ist viel Arbeit und nur sehr schwer wartbar. Wenn du z.B. eine Änderung am Sortierverfahren machen willst, müsstest du zig verschiedene "Instanzen" dieses Algorithmus anpassen.

Hiermit hast du ein Beispiel, wo C++:
- die Erzeugung schneller ausführbaren Maschinencodes erlaubt,
- Fehler bei der Programmierung vermeidet (durch die Typensicherheit)
- einfacher zu wartenden und sauberen Code ermöglicht.

Und genau so ist es praktisch in allen Fällen.
Es gibt wirklich keinen Grund, warum man heute noch C benutzen sollte - vor allem, wenn man schnellen Code will!

43

05.07.2012, 10:46

@D.S.: Dein Comparer sollte eine Referenz, keinen Zeiger auf ein Objekt nehmen, da std::sort immer Iteratoren auf Elemente dereferenziert.
Wenn es eine sinnvolle default-Sortierung gibt, wäre der Code mit einem überladenen operator < sogar noch einfacher.

@S.P. G: struct und class sind im Übrigen exakt dasselbe, bis auf Unterschiede in der Sichtbarkeit.

dot

Supermoderator

  • »dot« ist der Autor dieses Themas

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

44

05.07.2012, 11:02

@D.S.: Dein Comparer sollte eine Referenz, keinen Zeiger auf ein Objekt nehmen, da std::sort immer Iteratoren auf Elemente dereferenziert.
Wenn es eine sinnvolle default-Sortierung gibt, wäre der Code mit einem überladenen operator < sogar noch einfacher.

Der Code stimmt schon so; er sortiert ein Object* Array, der Iterator ist ein Object**... ;)

Mit dem operator < hast du natürlich recht. Der sort Aufruf ließe sich auch mit einer Lambda Expression sehr elegant schreiben:

C-/C++-Quelltext

1
std::sort(myObjects, myObjects + numObjects, [](const Object* objA, const Object* objB) { return objA->z < objB->z; });

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (05.07.2012, 11:10)


45

05.07.2012, 11:30

Ah, ein Array aus Zeigern. Okay, das habe ich übersehen. Stimmt dann natürlich.
Um den operator < dann nutzen zu können (man will schließlich die Werte hinter den Zeigern und nicht die Zeiger selbst vergleichen) wäre dann eben sowas wie boost::indirect_iterator erforderlich.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Paprikachu« (05.07.2012, 11:37)


drakon

Supermoderator

Beiträge: 6 513

Wohnort: Schweiz

Beruf: Entrepreneur

  • Private Nachricht senden

46

07.07.2012, 23:19

Ohne den ganzen Thread gelesen zu haben:
Habe kürzlich den Artikel hier gelesen. Könnte vielleicht interessant für die Diskussion sein (wollte den sowieso dot auf facebook senden ging da aber gerade nicht ^^).

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

47

08.07.2012, 00:52

Ich überflog den Artikel und alle Punkte die darin stehen lassen sich eigentlich auch mit C++ erreichen, wobei die Annahme der "exceptionlosigkeit" doch recht gewagt ist weil z.B. eine div by zero gibt es ja nicht nur auf highlevel Ebene.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

rnlf

Frischling

Beiträge: 85

Beruf: Softwareingenieur Raumfahrt

  • Private Nachricht senden

48

09.07.2012, 08:01

Ich weiß nicht, ob das zwischenzeitig mal gesagt wurde und ich bin auch zu bequem eure Diskussion komplett durchzuarbeiten, aber üblicherweise wird nicht auf 4-Byte aligned sondern auf ganzzahlige Vielfache des verwendeten Datentyps. Bytes werden von jedem mir bekannten Compiler auf jedem mir bekannten Prozessor auf ein Byte aligned. Wäre auch Unsinn, einen int64 auf 4 Bytes zu alignen. Da gewinnt man nix bei.

Legend

Alter Hase

Beiträge: 731

Beruf: Softwareentwickler

  • Private Nachricht senden

49

09.07.2012, 10:58

Ein Beispiel warum man auch einen Datentyp mit 8 Bytes auf 4 Bytes alignen kann:

Quellcode

1
2
3
4
struct Beispiel {
     char einbyte;
     __int64 beginntnunbestimmtnichtirgendwoimnirvana;
};


Schlimmer noch wäre es, wenn man die SSE-Instrics benutzt und ein __m128 dort stehen hat.
Der muss sogar auf 16 Bytes aligned sein.
"Wir müssen uns auf unsere Kernkompetenzen konzentrieren!" - "Juhu, wir machen eine Farm auf!"

Netzwerkbibliothek von mir, C#, LGPL: https://sourceforge.net/projects/statetransmitt/

dot

Supermoderator

  • »dot« ist der Autor dieses Themas

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

50

09.07.2012, 13:38

Ich weiß nicht, ob das zwischenzeitig mal gesagt wurde und ich bin auch zu bequem eure Diskussion komplett durchzuarbeiten, aber üblicherweise wird nicht auf 4-Byte aligned sondern auf ganzzahlige Vielfache des verwendeten Datentyps. Bytes werden von jedem mir bekannten Compiler auf jedem mir bekannten Prozessor auf ein Byte aligned. Wäre auch Unsinn, einen int64 auf 4 Bytes zu alignen. Da gewinnt man nix bei.

Richtig, so genau sind wir hier aber auf die technischen Details nicht eingegangen. Es ging nur darum, dass Alignment prinzipiell notwendig und zu beachten ist ;)

Ein Beispiel warum man auch einen Datentyp mit 8 Bytes auf 4 Bytes alignen kann:

[...]

Schlimmer noch wäre es, wenn man die SSE-Instrics benutzt und ein __m128 dort stehen hat.
Der muss sogar auf 16 Bytes aligned sein.

Und exakt das macht der Compiler auch, wenn du nicht explizit was dagegen unternimmst, da struct Member per default 8 Byte aligned werden. Vor deinen __int64 kommen 7 Byte padding, wenn du einen __m128 dort hättest, wärens sogar 15 und das struct damit 32 Byte groß...

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (09.07.2012, 13:44)


Werbeanzeige