Hallo zusammen, ich weiß, ich bin etwas spät dran, aber möchte Euch vor Abgabeschluss noch nach etwas Feedback zu meinem Artikel fragen: http://www.mikrocontroller.net/articles/Plattformunabh%C3%A4ngige_Programmierung_in_C Johann L. hat mir auf der Diskussionsseite schon einige Punkte geschrieben (Vielen Dank dafür!), die ich größtenteils eingearbeitet habe. Aber vielleicht gibt es ja noch weitere Verbesserungsvorschläge oder Aspekte, die noch erwähnt werden sollten. Insbesondere ist mir wichtig, dass im Artikel keine groben Falschaussagen stehen. Ich habe zwar nach bestem Gewissen recherchiert, bin aber natürlich nicht unfehlbar. Ich würde mich daher sehr freuen, wenn die C-Experten mal einen Blick drauf werfen könnten. Aber auch für allgemeines Feedback von jedem Interessierten wäre ich sehr dankbar: Ist alles verständlich formuliert, ist etwas zu kurz oder zu ausführlich? Vermisst Ihr irgendwo Erklärungen oder Links? Ans Ende kommt noch ein kurzes Kapitel, wie man plattformabhängigen und plattformunabhängigen Code voneinander trennen kann und eine Zusammenfassung mit den wichtigsten Empfehlungen. Das schreibe ich morgen ... Ansonsten ist es inhaltlich aus meiner Sicht jetzt ziemlich vollständig. Also, vielen Dank schon mal fürs Lesen. :)
Gast
#3074815
Fabian O. schrieb: > Also, vielen Dank schon mal fürs Lesen. :) Hallo, ich sehe das ganze eher als Nachschlagewerk und muß gestehen daß ich es nur überflogen habe. Das ist wahrscheinlich einer der umfangreichsten Artikel im Wettbewerb. Was ich noch vermisse sind so die typischen Fallstricke von C wie z.B. das rechts-shiften von signed Größen. Aber möglicherweise ist das ja im Kapitel Kapselung dann enthalten. Gruß Anja
Gast
#3074851
was ich nicht schön finde sind diese Codefragmente als Beispiele - okay man kann sich denken was man dazu basteln muß um ein lauffähiges Programm zu bekommen und das ist ja wohl mittlerweile gängige Praxis in der Erklärung, ich persönlich hasse es trotzdem. Du solltest wenigstens ein lauffähiges Beispiel bringen, sonst wird es für Anfänger schwierig. Compilierungsvorgang für unterschiedliche Platformen wäre auch noch eine nette Ergänzung. Ergänzen könnte man auch noch einige andere Sachen, aber das würde den Rahmen sprengen - insgesamt ganz okay.
Gast
#3074886
noch was: ich weiß nicht wer Deine Zielgruppe ist - der erfahrene Programmierer, der Anfänger, beide? Wenn Du Fachtermini einführst, sollten die immer kurz erklärt werden, was Du nur teilweise machst (z.B. Literale war okay). Was ist ein Cast, eine Union, etc.? Die Unterschiede zwischen den Standards und den Möglichkeiten von C99 hast Du gut erklärt, dafür ein Lob ;-)
Danke für Eure Antworten. Anja schrieb: > ich sehe das ganze eher als Nachschlagewerk > und muß gestehen daß ich es nur überflogen habe. > Das ist wahrscheinlich einer der umfangreichsten Artikel im Wettbewerb. Ja, war aber eigentlich gar nicht mein Ziel ... Bin selber erstaunt, wie viel da zusammengekommen ist. Aber wenn man mal anfängt, diese ganzen Sachen aufzuschreiben, fallen einem eben doch eine Menge Dinge ein, die man erwähnen sollte. Deshalb kommt noch die Zusammenfassung ans Ende, dass man das wichtigste noch mal als kompakten Überblick/Erinnerung hat. > Was ich noch vermisse sind so die typischen Fallstricke von C wie z.B. > das rechts-shiften von signed Größen. Stimmt, dem Thema signed/unsigned könnte man noch ein paar Sätze spendieren. Allerdings soll der Artikel auch nicht jeden möglichen Programmierfehler, der zu undefiniertem Verhalten führt, auflisten (i = i++ etc.), sondern nur die Dinge, die plattformspezifische Gründe haben. Sonst wird das noch viel viel länger ... ;) Passwort vergessen schrieb: > noch was: ich weiß nicht wer Deine Zielgruppe ist - der erfahrene > Programmierer, der Anfänger, beide? Ich glaube das ist der Punkt: Der Artikel ist kein C-Tutorial für Anfänger, auch wenn es ganz am Anfang vielleicht so klingt (Erklärung der Integer-Typen). Er richtet sich an Programmierer, die schon C können, aber sich noch nicht mit plattformspezifischen Eigenheiten und Portierung von Code beschäftigt haben, sondern bisher immer zufrieden waren, wenn der Code auf ihrem Mikrocontroller funktioniert hat. Deshalb setze ich voraus, dass der Leser grundsätzlich weiß, wie Shifts, Casts, Zeiger, Unions usw. funktionieren. Gleiches gilt für die Beispiele: Ich setze voraus, dass der Leser schon den Kenntnisstand hat, sich vorzustellen, wie sie in einem realen Programm aussehen. Werde das am besten am Anfang des Artikels noch klarer erwähnen. Ist vielleicht der Titel "Plattformunabhängige Programmierung in C" missverständlich? Also à la "Programmierung in C (unabhängig von einer Plattform erklärt)"? Denn das ist nicht gemeint, sondern es geht um plattformunabhängigen Code, nicht wie man C programmiert. Hmmm ... hat jemand einen besseren Vorschlag für den Titel? Auf jeden Fall Danke, dass Du das angesprochen hast. :)
Gast
#3075415
Fabian O. schrieb: > etwas Feedback zu meinem Artikel... Nun ja, dein Ziel ist m.E. ein bissel zu hoch gesteckt: "Ziel ist es, Softwaremodule ohne Änderungen auf möglichst jeder Hardwareplattform einsetzen zu können, vom 8-Bit-Mikrocontroller bis hin zum 64-Bit-PC." Wir reden hier von Mikrocontroller-Anwendungen, ja? Die sind eigentlich fast immer so hardwarenah, daß die simple formale Übersetzbarkeit von Quellen eher in den Hintergrund tritt. Viel wichtiger ist es, die zu einem Projekt gehörigen Quellteile thematisch und inhaltlich so zu gruppieren und mit sinnvollen Schnittstellen zu versehen, daß sich daraus eine Weiterverwendbarkeit diverser Teile ergibt. Leider sieht man auch in diesem Forum Unmengen von Quellcode, der das ziemliche Gegenteil darstellt, obwohl deren Autoren allen "modernen" Schnickschnack a la uint16_t usw. benutzt haben und nun glauben, ihr Code sei damit bestens portabel. Noch ein Wort zu C99 und zu den Unsäglichkeiten, die damit aufgekommen sind, insbesondere zu den Integer-Verrenkungen: An der eigentlichen Sprache ist damals überhaupt nix geändert worden. Die Compiler sehen nach wie vor nix anderes als char, int, long und evtl. short sowie eventuellen Präfixen. All die "neuen" Typen wie uint16_t und so sind keine echten neuen Typen, sondern lediglich Header-Akrobatik und damit genauso gut oder schlecht wie Typen a la U8, S16, U32 oder sonstwelche Eigendefinitionen, die sich jeder selbst nach eigenem Geschmack anlegen kann. Das sollte man mal wirklich klarstellen. Was die portable Benutzung von char, int, short, long und int64 aka long long betrifft, so hast du die bereits implizit beschrieben. Man muß deine Tabelle lediglich mal als Logiker betrachten. Huch? Ja. Also die da stehende Aussage lautet z.B. "der C Standard garantiert lediglich folgende Mindestbreiten.." z.B. für int 16 Bit. Das heißt im logischen Umkehrschluß und etwas anders formuliert so: "Ein int ist garantiert 16 Bit breit und man darf sich keinesfalls darauf verlassen, daß der int auch nur ein einziges lumpiges Bit größer ist als 16 Bit. Für den Programmierer ist somit der int also exakt 16 Bit breit." Das gilt auch dann, wenn auf manchen Maschinen in der CPU automatisch breiter gerechnet wird, wie das z.B. bei ARM-Maschinen der Fall ist. Was in diesem Zusammenhang viel wichtiger ist, ist der Umstand, daß manche Maschinen ausgerichtete ("aligned") Daten benötigen und andere nicht. Das schafft beim Portieren von Strukturen viel größere Probleme als das alberne Herumgehacke auf den Integer-Bezeichnungen. Ähnlich sieht es mit Little und Big Endian Maschinen aus. Das sind alles viel delikatere Portierungsprobleme. Dein "Einer der fundamentalsten Unterschiede zwischen.." muß man also etwas relativieren. Ebenso muß man den Passus über Pointerdifferenzen relativieren: Die schiere Bitanzahl ist ja nicht unwichtig, aber gerade bei Mikrocontrollern haben wir sehr oft ganz andere Probleme damit, daß es Adreßraum-Inkompatibilitäten gibt. Bei vielen Maschinen muß man unterscheiden zwischen Pointern, die auf R/W-Daten zeigen und solchen, die auf R/O Daten zeigen, Stichwort Harvard versus v.Neumann. Da können 2 Pointer numerisch völlig gleich sein und sachlich doch völlig unterschiedlich. Zum Schluß noch ein Wort zum Casten: " Klarheit kann man, wie vorher, über einen Cast schaffen:... " Nun, im Prinzip kann man es so tun, aber die meisten Leute casten falsch mangels Vorstellung der inneren Logik - und es sieht das Herumgecaste auch unästhetisch und unleserlich aus. Beispiel: MyLong = (long)(MyByte<<8) | MyotherByte; --->ätschibäh! Man sollte m.E. eher empfehlen, möglichst ohne casts auszukommen und die Optimierung dem Compiler zu überlassen: MyLong = MyByte; MyLong = (MyLong<<8) | MyotherByte; Funktioniert immer, braucht keine casts und gibt bei ordentlichem Compiler nen guten Code. so, das waren erstmal meine Anmerkungen dazu. W.S.
Gast
#3075754
Guter Artikel! Ich würde noch auf den Aufbau der Software in Abstraktionsebenen eingehen, mit Verweis auf das OSI-Modell der Netzwerkschichten als gutem Beispiel.
W.S. schrieb: > Für den Programmierer ist somit der int also exakt 16 Bit breit." Das wär nun aber völlig daneben, da es sehr wohl grösser sein kann. Ob etwas in einer Datenstruktur nun 2 oder 4 Bytes beansprucht ist nicht immer völlig unwichtig. Deine gediegene aber etwas einsame Aversion gegen int16_t&Co ist an anderer Stelle schon eingehend zur Sprache gekommen.
Bronco schrieb: > Ich würde noch auf den Aufbau der Software in Abstraktionsebenen > eingehen, mit Verweis auf das OSI-Modell der Netzwerkschichten als gutem > Beispiel. Wobei ich das eher als separaten Artikel sehe, da sich der Artikel auf Programmierung in C konzentriert, die Vorteile von Programmierung in Abstraktionsebenen aber - in schönster Abstraktion - völlig unabhängig von der Programmiersprache gelten.
Bei dem Tital "Plattformunabhängige Programmierung in C" wäre zunächst mal zu erläutern, was unter einer "Plattform" zu verstehen ist. Der Artikel geht auf unterschiedliche Sprachversionen (C90, C99, C11, ...) ein, aber unter einer "Plattform" wird wohl mehr verstenden, nämlich auch implementation-defined Behavior", d.h. Dinge, die im C-Standard zwar erwähnt sind, aber deren Definition an die Implementation (Compiler, Linker, LibC, Host-OS, ...) delegiert werden. Dazu gehören zB die Anzahl signifikanter Zeichen in Identifiern und Dateinamen, sizeof (int), etc. Im GCC-Manual gibt es einen eigenen Abschnitt zu "implementation defined" und was GCC definiert. I.W. delegiert GCC die Definition weiter: An die LibC, an die Binutils, an eine (E)ABI. Anstatt direkt in medias res zu gehen, wäre ein Überblick zweckmäßig so daß man sich nicht direkt in den Details verliert. Das Integer-Thema ist recht ausgewachsen, vielleicht würde ihm eine Straffung guttun so daß man nicht den Überblick verliert... Beim Thema "Plattformunabhängigkeit" wäre auch ein Hinweis zu den Teilen eines Programms angebracht, die nicht portierbar sind. Auch hier wäre ein kurzer Überblick sinnvoll, wie man diese nicht-portierbaren Teile möglichst praktikabel rausfaktorisiert. Wie erkennt man zB die Plattform? Das Device? Die Architektur? Das Host-OS? Wie erkennt man die Sprachversion in der Quelle? Wie sollte man zB ISR-Implementierungen "Kapseln" oder SFR-Zugriffe. Dies alles ist inheränt nicht-portierbar, d.h. die Kunst bei der Implementierung besteht in einer sauberen Trennung des portierbaren Teils und des nichtportierbaren Teils. Hier sind auch Aspekte der Effizient zumindest zu erwähnen. Die Frage ist auch, wie praxisrelevant eine komplette Portierbarkeit zwischen einem 64-Bit und einem 8-Bit System überhaupt ist. Zwar gibt es zurzeit sowohl kleine 8-Bit µC als auch bereits homogene und heterogene Multicores (Freescale/ST PowerPC VLE, Infineon Aurix, ...) aber die Notwendigkeit einer Portierung in maximal vorstellbarer Bandbreite dürfte eher akademisch sein... volatile + Bitfelder: Das ist fürchterlich, weil es nicht spezifiziert ist und auch kaum spezifiziert werden kann. Der C-Standard macht keine Aussage (weil er den Instruktionssatz nicht kennt) und der Compiler ist relativ frei, wie er das umsetzt. Zb zwei volatile Bitfelder nebeneinander: Wenn ein Bitfeld zu ändern ist und es keine Bitinstruktion gibt, müssen zwangsläufig andere Felder mitgelesen/mitgeschrieben werden. Ergo: Funger weg von volatile + Bitfelder, selbst wenn es um Devive-spezifische Header geht wie sie Atmel z.B. for XMEGA bereitstellt. Das kann man zur Kenntnis nehmen, aber in dem Artikel würde ich nicht groß auf diese Schlangengrube eingehen.
Johann L. schrieb: > Die Frage ist auch, wie praxisrelevant eine komplette Portierbarkeit > zwischen einem 64-Bit und einem 8-Bit System überhaupt ist. Da die Portierbarkeit zwischen 8-, 16- und 32-Bit Architekturen ein ziemlich aktuelles Thema ist, kommt es auf eine Vervollständigung durch 64-Bitter nun auch nicht mehr an. Vom Adressraum abgesehen sind die dank dominierendem LP64/LLP64 auch nur falsche 32-Bitter, die besser mit 64-Bit Typen umgehen können als echte 32-Bitter.
Vielen Dank für die Kommentare. :) Ich habe noch das Kapitel über die Aufteilung von Code geschrieben. Ist durch den vielen Quellcode etwas lang, aber es ist ja das letzte Kapitel. Wen es nicht interessiert, braucht also nicht mehr weiterlesen. Oder soll ich versuchen, es zu kürzen? Ansonsten werde ich schaun, dass ich morgen noch eure Vorschläge einbaue hinsichtlich Überblick am Anfang geben etc. Falls ihr noch weitere Anregungen habt, es sind ja noch 24 Stunden Zeit ... :D
Fabian O. schrieb: > Ich habe noch das Kapitel über die Aufteilung von Code geschrieben. Schau dir die Codebeispiele noch mal an, und zwar die Reihenfolge der #include-Statements. Beispiel: rfm70.h wird vor stdint.h eingebunden, enthält aber eine Deklaration mit einem stdint-Datentyp. Grüße Stefan PS: Ansonsten macht der Artikel einen guten Eindruck.
Gast
#3076411
Es gibt keine "Fließkommazahlen", sondern nur "Gleitkommazahlen", auch wenn man der Mist oft liest... Zur Info: float = gleiten, schwimmen, schweben, ... flow = fließen, strömen, ... Wer "Fließkommanzahl" verwendet, übersetzt auch "bekommen" mit "become"...
Würde das gern als PDF haben, wenn das möglich ist.
Gast
#3076534
Hallo Fabian, es macht auf jeden Fall Sinn, eine Abstraktionsebene für die Platformunabhängigkeit einzuziehen. Auf professioneller Ebene gibt es bereichts einige Ansätze dazu. Bei den ARM-Prozessoren ist es die CSMIS: http://www.arm.com/products/processors/cortex-m/cortex-microcontroller-software-interface-standard.php Im Automotivebereich in Auotosar ist es die MCAL: http://www.all-electronics.de/texte/anzeigen/24834/Erster-MCAL-gemaess-Autosar-Release-30-31 eine Diskussion zu diesem Thema habe ich hier im MC-Netz auch schon einmal angefangen: Beitrag "BIOS für Mikrocontroller" Die Antworten hierzu waren aber eher ernüchternd ... Gruß, chris
Ok, noch ein letztes Feedback :-) Meterlangen Beispielcode finde ich persönlich nicht so hilfreich, der ist eher dazu angetan sich im Urwald zu verlieren und vom Hölzchen aufs Stöckchen zu kommen... Ironischerweise ist der Code nicht gut portabel, für eine Arraylänge wird ein uint8_t verwendet, was aber ein size_t sein sollte. Und es werden C++ Kommentare verwendet, die es erst in C99 gibt. Daß bestimmte Compiler Konstrukte zum Packen (packed) oder zum Alignment (aligned, assume_aligned, alignedaccess, ...) haben, kann durchaus Erwähnung finden, darin baden würd ich aber nicht. Eher angebracht sind hier ein paar Worte zu Serialisierung / Deserialisierung und wie diese abgebildet werden konnen. Über die binärdarstellung der entsprechenden Objekte zu gehen ist nur eine Möglichkeit; (De)serislisierung per ASCII ist eine weitere. Anstatt sich weiter in Codeschnippeln zu verlieren, wäre eine Abrundung des Artikels ein Blick auf die Portierbarkeit des von den Tools erzeugten Binärcodes. Auf Host-Rechnern ist es zB unerlässlich, daß Code unterschiedlicher COmpilerhersteller (Microsoft, GCC, Intel, HP, ...) interoperabel ist, d.h. man kann zB mit GCC eine Anwendung erstellen, die zusammen mit DLLs von Microsoft operiert. Hierzu muss natürlich das ABI passen, etwa Objektformat, Calling Convention, Linkage (C++ Demangling), etc. Beim Thema "pached" von oben geht es genau um diese Binärkompatibilität, es geht nicht um "Quellkompatibilität". Unter den AVR-Compilern ist es zB nicht so, daß man einfach Objekte (*.o) oder Bibliotheken (*.a) unterschiedlicher Hersteller mixen könnte. Selbst innerhalb der gleichen Tools können sich Inkompatibilitäten ergeben, etwa beim Wechsel von avr-gcc 4.6 zu avr-gcc 4.7. Ein Hinweise und Anmerkungen dazu, um zumindest ein Problembewusstsein dafür zu schaffen, fände ich angebracht. So geht es an vielen anderen Stellen des Artikela auch eher darum, die viele Fallstricke übersichtlich und in nicht-ermüdender Weise darzulegen, als sich im Klein-Klein zu verlieren.
Gast
#3077309
>Ironischerweise ist der Code nicht gut portabel, für eine Arraylänge >wird ein uint8_t verwendet, was aber ein size_t sein sollte Warum sollte man uint8_t nicht verwenden können?
Gast
#3077470
Habe mir den Text flüchtig angesehen und dabei folgendes gesehen: Die interne Repräsentation von Fließkommazahlen, d.h. wie die einzelnen Bits bzw. Bytes im Speicher angeordnet sind, ist allerdings plattformabhängig! Die Typen float und double sind deshalb nicht für den Datenaustausch zwischen verschiedenen Geräten, sondern nur für interne Berechnungen geeignet. Wenn Fließkommazahlen über einen Datenbus bzw. Netzwerk übertragen werden sollen, empfiehlt sich die Wandlung in Festkommazahlen oder Strings. Ich war bisher der Meinung, dass C-Compiler das IEEE_format verwenden und keinesfalls die bits irgendwie unterschiedlich verwenden. Dafür wurde doch das IEEE-Format geschaffen. Höchstens die endien-ness kann verschieden sein. Unterschiedliche Formate von NAN oder INFINITY löst man aber nicht mit Wandlung in Festkommazahlen, auch hat eine Wandlung in Strings erhebliche Laufzeit- und Performance-Nachteile. Eine Darstellung was wirklich unterschiedlich sein könnte, wäre sehr hilfreich.
chris schrieb: >>Ironischerweise ist der Code nicht gut portabel, für eine Arraylänge >>wird ein uint8_t verwendet, was aber ein size_t sein sollte > > Warum sollte man uint8_t nicht verwenden können? In dem Zusammenhang kann man es schon verwenden, und meine Kritik geht eher in Richtung defensive Programmierund als Portabilität. Wenn man nämlich den Puffer vergrößert fliegt einem alles um die Ohren :-)
@Fritz Wikipedia sagt dazu: > Die Anordnung der Bits einer single zeigt die nachfolgende Abbildung. Die > bei einer Rechenanlage konkrete Anordnung der Bits im Speicher kann von > diesem Bild abweichen und hängt von der jeweiligen Bytereihenfolge > (little/big endian) und weiteren Rechnereigenheiten ab. http://de.wikipedia.org/wiki/IEEE_754#Zahlenformate_und_andere_Festlegungen_des_IEEE-754-Standards Im C-Standard ist es auf jeden Fall nicht definiert, da stehen nur die geforderten Mindestwertebereiche (die auch nicht unbedingt jeder Compiler einhält, siehe avr-gcc). Mir fällt keine "legale" und effiziente Möglichkeit ein, an die korrekte Bitrepräsentation gemäß IEEE zu kommen. Es garantiert einem ja niemand, dass die gleiche Bytereihenfolge wie bei Integern verwendet wird, man also einen float in uint32_t casten kann und dann byteweise verschicken. Oder steht das im IEEE-Standard?
Gast
#3077575
Super, endlich wird mal etwas zur Integer Promotion geschrieben. Danke! Etwas härter hätte ich den Text bei den "union"s formuliert. In den einen member etwas hineinzuschreiben und aus dem anderen wieder herauszulesen ist grober Unfug (auch wenn das in der Vergangenheit manche Programmier "cool" fanden). Tatsächlich genügt schon der Wechsel zu einem Prozessor mit einer anderen byte-Anordnung um ein "unerwartetes" Ergebnis zu bekommen. Oliver
Fabian O. schrieb: > @Fritz > Wikipedia sagt dazu: > >> Die Anordnung der Bits einer single zeigt die nachfolgende Abbildung. Die >> bei einer Rechenanlage konkrete Anordnung der Bits im Speicher kann von >> diesem Bild abweichen und hängt von der jeweiligen Bytereihenfolge >> (little/big endian) und weiteren Rechnereigenheiten ab. > > Im C-Standard ist es auf jeden Fall nicht definiert, da stehen nur die > geforderten Mindestwertebereiche (die auch nicht unbedingt jeder > Compiler einhält, siehe avr-gcc). > > Mir fällt keine "legale" und effiziente Möglichkeit ein, an die korrekte > Bitrepräsentation gemäß IEEE zu kommen. Es garantiert einem ja niemand, > dass die gleiche Bytereihenfolge wie bei Integern verwendet wird, man > also einen float in uint32_t casten kann und dann byteweise verschicken. > Oder steht das im IEEE-Standard? Nein, ein Cast geht auf keinen Fall, weil das den Wert verändert. Der einzige Standard-konfirme Weg an die Bits eines Fließkomma-Wertes zu kommen ist memcpy. Hilfreich beim (De)serialisieren sind ldexp, frexp, fpclassify etc. Je nach Compiler gibt's auch Makros, die Typlayouts beschreiben. Im GCC etwa
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
die aber auch nicht portabel sind, bzw. nur innerhalb verschiedener GCC-Inkarnationen. GCC kennt übrigens mindestens 4 Arten von Endianess: • Byte-Endianess: Innerhalb eines Word • Word-Endianess: zB innerhalb eines 64-Bit Werts • Bit-Endianess: Bitfelder • float-Endianess Daneben ist die Endianess / Alignment in unterschiedlichen Speicherklassen zu unterscheiden wie: Stack, Register, static Storage, etc. IdR stimmen diese überein, können sich aber durchaus unterscheiden, zB wenn ein Wert auf den Stack gelegt wird (avr-gcc wegen byteweisem Push und weil der Stapel nach unten hin wächst). Für den Artikel geht das aber IMO zu sehr ins Detail; Quintessenz ist daß aufgrund der Unterschiede eine 100% plattformübergreifende Lösung kaum praktikabel ist. Zweckmäßiger erscheint es da, die Abhängigkeit explizit zu faktorisieren, z.B.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
Allerdings gibt's auch da Probleme, etwa weil sich LLVM als GCC ausweist, d.h. __GNUC__ definiert... Aber selbst wenn es nicht um Datenaustausch geht bleiben noch genügend arithmetische Probleme mit float et al. Da man keine == verwenden soll — etwa als Abbruchbedingung für iterative Algorithmen — stellt sich die Frage, wie dies unabhängig von der Plattform und der Genauigkeit bzw. dem Wertebereich zu formulieren ist. Übrigens hat man Portierungsprobleme nicht nur im "reinen" C-Code, sondern auch im Präprozessor. Hier können zB C99-Makros wie UINT32_C verwendet werden, die laut Standard in #if verwendet werden können — auch wenn manche libc-Implementierungen wie die AVR-LibC dem Standard nicht folgen indem sie die Makros als Cast bereitstellen, was natürlich nicht in #if verwendbar ist (Bug #36571).
Noch eins: Wegen eines nicht-vorhandenen C99-Compilers Teile in C++ zu schreiben ist übel. - C++ ist keine Obermenge von C - C++ han andere Linkage, z.B Name-Mangling Besser: Schnickschnack wie Mixen von Deklaration und Befehlen vermeiden oder auf C90 rückportieren. Jede solche Mixtur kann durch Umstellung in gültiges C90 umgeschrieben werden.
Hmm, Geschmackssache. Mich würde es ziemlich hart treffen, wenn ich Schleifenzähler nicht mehr in for-Schleifen deklarieren dürfte. Mag Schnickschnack sein, aber es macht den Code um einiges besser lesbarer, genau wie Kommentare mit zwei Schrägstrichen. Da achte ich lieber drauf, dass mein C-Code auch C++-konform ist und passe ihn ggf. entsprechend an. Zugegebenermaßen habe ich bisher nicht darauf geachtet, weils doch recht unwahrscheinlich ist, dass mein AVR-Code mal unter Windows laufen soll. Aber die meisten Inkompatibilitäten zwischen C und C++ sehen mir recht harmlos aus bzw. widersprechen sowie ordentlich strukturiertem Code (z.B. Funktionen ohne Prototyp aufrufen). Ich habe mal ein "zur Not" in den Satz eingefügt. Sofern der Code sich ohne große Änderungen in C++ kompilieren lässt, erscheint es mir jedenfalls als das kleineres Übel, den C++-Compiler zu verwenden, anstatt tausende Zeilen Quelltext auf "schlechter lesbar" umzuschreiben ...
Gast
#3077894
Johann L. schrieb: >> Mir fällt keine "legale" und effiziente Möglichkeit ein, an die korrekte >> Bitrepräsentation gemäß IEEE zu kommen. Es garantiert einem ja niemand, >> dass die gleiche Bytereihenfolge wie bei Integern verwendet wird, man >> also einen float in uint32_t casten kann und dann byteweise verschicken. >> Oder steht das im IEEE-Standard? > > Nein, ein Cast geht auf keinen Fall, weil das den Wert verändert. Hallo nocheinmal. Vielleicht bin ich zu blauäugig oder pragmatisch. Verstehe die diversen Warnungen nicht wirklich. Habe mit einem STM32F4.. also mit FPU gerade eine Datenübertragung über ein UART programmiert, wobei ich das IEEE_format auf 6 Bytes aufteile und im PC wieder zusammensetze. Am PC in ein 32-bit integer und danach auf float gecastet. Im STM laut IEEE per Asssembler zerlegt und byteweise versandt. Ließe sich aber auch äquivalent in C durchführen. Natürlich geht ein direktes casten int -> float nicht, aber sehrwohl ein casting über pointer: float fl; int32 data32; fl = *((float*)&data32);
Ja, so hatte ichs auch gemeint, war schlecht formuliert. Aber das bricht die Strict-Aliasing-Regel, steht auch im Artikel. Der Compiler darf Dir solche Konstrukte in C99 ggf. wegoptimieren. Wirklich legal ist es nur per memcpy(). Aber auch dann hast Du zwar die Bits des float, aber in relativ "willkürlicher" Reihenfolge. Das heißt, es hängt von den erwähnten Word/Byte/Bit-Reihenfolge der Plattform ab, wo welches Bit steht. Das kann prinzipiell auf jedem Prozessor anders aussehen. Das heißt natürlich nicht, dass diese Vorgehensweise nie funktioniert. Wenn die Floats bei beiden Prozessoren mit der gleichen Bitrepräsentation im Speicher stehen, dann klappt es schon, keine Frage. Nur kannst Du Dich eben nicht drauf verlassen, dass sich der Code auch auf anderen Plattformen so verhält. -> Er ist nicht portabel. Das ist ja genau der Punkt bei dem Artikel: Keines der Codebeispiele darin ist ein Fehler in dem Sinne, dass das nie funktioniert. Auf manchen Plattformen läuft das wunderbar. Nur wenn man dann mal den Compiler, Prozessor oder Betriebssystem wechselt, geht plötzlich nichts mehr und die Fehlersuche beginnt ...
Auch nicht korrekt wegen Aliasing-Rules von C denn Integer und Floating sind nicht kompatibel. D.h. es kann davon ausgegangen werden, daß die Änderung eines Types keine Änderung eines nicht-kompatiblen Types bewirkt. Es bedeutet zwar nicht, daß dies zu problemen führen muß aber es macht eben den Unterschied zwischen "robust bzw. standardkonfirm" und "Hack, der in diesem Kontext funktioniert".
Gast
#3077961
Fabian O. schrieb: > Aber auch dann hast Du zwar die Bits des float, aber in relativ > "willkürlicher" Reihenfolge. Das heißt, es hängt von den erwähnten > Word/Byte/Bit-Reihenfolge der Plattform ab, wo welches Bit steht. Das > kann prinzipiell auf jedem Prozessor anders aussehen. Also Word/Byte-Reihenfolge verstehe ich ja, dass sie je nach Plattform verschieden sein kann. Aber Bit-Reihenfolge im Byte ist meiner Meinung nach wohl in allen Plattformen gleich. Sich da nicht daraf verlassen zu wollen ist etwas übertrieben. 4-bit uC`s gibts wohl nicht mehr wo das möglicherweise relevant wäre.
Die Bitreihenfolge von Integern ist an sich transparent, weil man in C nur auf ganze Bytes zugreifen kann. Die sehen immer so aus, wie man es erwartet (links msb, rechts lsb), selbst wenn da ein 4- oder 1-Bit-Prozessor hinterstecken würde. Ausnahme sind nur Bitfelder. Nur garantiert einem niemand, dass Fließkommazahlen die gleiche Bitreihenfolge wie Integer verwenden (zumindest afaik, bitte korrigieren falls falsch). Sie könnten theoretisch also auch genau umgedreht sein, wenn die FPU des Prozessors des so brauchen sollte. Oder auch in Big-Endian statt Little-Endian im Speicher stehen, obwohl Integer auf dem Prozessor Little-Endian sind. In der Praxis wohl unwahrscheinlich, aber es wäre gemäß C-Standard. Denn einen float hast Du legal nur in Rechenoperationen zu verwenden. Du kannst ihn mit einem Cast zurück in einen Integer konvertieren (also den ganzzahligen Wert bekommen), dann kümmert sich der Compiler um die nötigen Schritte, also die entsprechenden Maschinenbefehle auszuführen, dass Du das erwartetete Ergebnis bekommst. Aber wie er das im Speicher vollbringt, ist seine Sache. Auch mit memcpy bekommst Du nur Bits, die zu dem float gehören, aber keine garantierte Semantik dazu. Es sei denn, der IEEE-Standard würde explizit vorschreiben, dass bei Interpretation der Bits als Integer gemäß der Integer-Bytereihenfolge der Plattform die richtige Float-Bitrepräsentation rauskommen muss. Dann könnte man sich bei einem IEEE-Standard-konformen Prozessor wirklich drauf verlassen. Ich weiß aber nicht, ob das da so drinnen steht.
Gast
#3078038
Fabian O. schrieb: > Nur garantiert einem niemand, dass Fließkommazahlen die gleiche > Bitreihenfolge wie Integer verwenden (zumindest afaik, bitte korrigieren > falls falsch). Sie könnten theoretisch also auch genau umgedreht sein, > wenn die FPU des Prozessors des so brauchen sollte. Wenn man so wie ich eher von der Hardware kommt, ist diese Annahme, dass das tatsächlich in Silizium ausgeführt ist, ziemlich idiotisch. Jegliche Konvertierngen zwischen float und int wird nur zusätzlich erschweren. Die 23 bit Mantisse sind eben ein 24-bit int (inkl. implizite führende 1) und das nicht an das 32-bit int format anzupassen bzw. zumindestens in den Bytes die bitreihenfolge gleich zu lassen, kann ich mir nicht vorstellen. Da jetzt kompatibilitätsproblem heraufzubeschwören halte ich für übertrieben. Rein theoretisch mag das ja stimmen. Von Titel her erwarte ich mir wie man es wirklich plattformunabhängig machen kann. Die Empfehlung: Wenn Fließkommazahlen über einen Datenbus bzw. Netzwerk übertragen werden sollen, empfiehlt sich die Wandlung in Festkommazahlen oder Strings. ist halt nur sehr allgemein gehalten und geht nicht auf auch da mögliche Proble ein: Manche Dezimalzahlen lassen sich im float format nicht exakt darstellen, daher kann es bei Umwandlung in Strings zu Rundungsproblemen kommen. NAN und INFINTY ... müssen auch richtig übertragen werden. Also wie mache ich es plattformübergreifend?
Fritz schrieb: > Manche Dezimalzahlen lassen sich im float format nicht exakt darstellen, > daher kann es bei Umwandlung in Strings zu Rundungsproblemen kommen. Dieses Umwandlungsproblem entsteht doch bei der Umwandlung von Dezimal nach Float, nicht auf dem Rückweg. Wenn das ein Problem ist, darfst Du erst gar keinen Float verwenden. In einem String kannst Du jede beliebige Float-Zahl (und auch jede Dezimalzahl) darstellen. > NAN und INFINTY ... müssen auch richtig übertragen werden. Müssen sie das wirklich? Dann übertrag "NAN" und "INFINITY" als String. Oder denk Dir ein anderes Protokoll aus. Alternativ kannst Du Exponent und Mantisse getrennt als Integer übertragen. Johann hat ja oben die entsprechenden Funktionen genannt, mit denen man daran kommt. Das geht ohne Genauigkeitsverlust. In der Praxis fällt mir aber ehrlich gesagt keine Anwendung ein, bei der man Fließkomma mit maximaler Genauigkeit zwischen eingebetteten Systemen übertragen muss. In der Regel hat man doch relativ klare Wertebereiche, in denen sich z.B. Messwerte abspielen. Die skaliert man auf einen passend großen Integer und gut ists. Oder Du lebst eben damit, dass der Code für Deine Spezialanwendung nicht portabel ist. Aber wie gesagt, woher kommen denn die Floats? Die tauchen ja nicht auf natürliche Weise auf, sondern entstehen erst durch Berechnungen. Woher kommen die Eingabewerte? Sind die so exakt, dass man auf kein Mantissenbit verzichten kann?
Fritz schrieb: > Wenn man so wie ich eher von der Hardware kommt, ist diese Annahme, dass > das tatsächlich in Silizium ausgeführt ist, ziemlich idiotisch. "Although the ubiquitous x86 of today use little-endian storage for all types of data (integer, floating point, BCD), there have been a few historical machines where floating point numbers were represented in big-endian form while integers were represented in little-endian form.[3] There are old ARM processors that have half little-endian, half big-endian floating point representation." http://en.wikipedia.org/wiki/Endianness#Floating-point_and_endianness > Also wie mache ich es plattformübergreifend? Als ASCII-Darstellung mit einer Zweierpotenz statt 10 als Basis hat man Umrechnungseffekte von und zum üblichen Format vom Hals. Aber wenn C - was ich annehme - keine binäre Fliesskommadarstellung definiert, dann geht das theoretisch überhaupt nicht völlig verlustfrei, weil dann auch eine interne dezimale Fliesskommadarstellung und -rechnung zulässig wäre - sowas gibts in Hardware wirklich, auch heutzutage!
Danke für den Link, hab ihn in den Artikel mit aufgenommen. Also ist es schon so, wie ich angenommen habe: Auch der IEEE-Standard sagt nicht, wie die Bytes intern zu speichern sind. Der C-Standard sowieso nicht, der spezifiziert wirklich nur dezimale Mindest-Wertebereiche.
Gast
#3078144
Erstaunlich, was die Leute alles portabel haben wollen. Dabei sind die Anwendungen Welten voneinander weg. Lasst mich mal aufzaehlen, was man auf'm PC sicher nicht braucht, auf einem Controller aber eben schon. - interrupts, Echtzeit konzepte, Ringbuffer, FIR & IIR filter, PID, Special function register, analog & digital IO, Tastengeschichten, Bootloader, ... Dinge, die man aufm PC hat, aber auf einem Controller nicht braucht. - Dynamisches Memory, Klassen- & Klassen bibliotheken, Betriebssystem der Ueberlapp zwischen Controller und PC ist IMO relativ bescheiden. Zu bescheiden , um von Portabilitaet zu reden, resp zu verlangen.
Es geht ja nicht nur um Portierbarkeit PC <-> µC-Hänfling sondern auch Portierbarkeit zwischen Boliden oder unterschiedlichen µCs. Und wenn man sich High-End µCs anschaut, wo bereits fett ausgestattete Multicore-Maschinen (SIMD, Vektor-Instruktionen, HW-float, ...) den Alltag erreicht haben (Automotive), ist es zu den PC-Boliden nicht mehr sooo weit. Ausserdem geht es in dem Artikel darum, mochglichst viele Facetten der Portierbarkeit von C Programmen zu beleuchten, hab ich zumindest so verstanden. Der ganze Artikel wäre doch für die Katz, wenn bestimmte Aspekte willkürlich übergangen würden.
Gast
#3078201
Fabian O. schrieb: >> NAN und INFINTY ... müssen auch richtig übertragen werden. > > Müssen sie das wirklich? Dann übertrag "NAN" und "INFINITY" als String. > Oder denk Dir ein anderes Protokoll aus. Wenn ich das mache, habe ich ein proprietäres Format das ich auf beiden Plattformen extra implementieren muß! Da mache ich es lieber wie von mir vorher beschrieben über Pointercasting und implementiere ein byteumordnung falls wirklich notwendig. Das funktioniert entweder auf Anhieb ohne Änderung oder das Problem sticht sofort ins Auge (in einem float vertauschte bits, bytes) und fallen so stark auf, die Byteumordnung ist dann aber nur mehr ein Klacks. Plattformunabhängig ist weder das eine noch das andere. Ich verstehe nicht warum man ein wirklich schön definiertes Format (IEEE) durch Zerstören (Stringumwandlung) und wieder Rekonstruieren besser behandelt, als wenn man sich die Unterschiede der Plattform anschaut und entsprechend umsetzt. Zu meinen bisherigen Erfahrungen dazu. Mein Spezialgebiet ist die Strom-, Spannungs-, Leistungsmessung im Einphasen- und Drehstromnetzen. Habe auf folgenden Plattformen mit floats gearbeitet: 68000 Kommunikation mit PC Embedded Arm mit uLinux + TI 6713 floatingpoint DSP und Anbindung an PC Hat aber dabei keine Plattformprobleme mit den floats gegeben, auch ohne UM- und Rückwandlung in von Strings. Fabian O. schrieb: > In der Regel hat man doch relativ klare Wertebereiche, > in denen sich z.B. Messwerte abspielen. Die skaliert man auf einen > passend großen Integer und gut ists. Na ja? Ich muss z.B. beim Strom mA bis KA behandeln, da entsprechende verschiedene Stromwandler/Shunts davor sitzen. Bei der Spannung gehts auch von V bis MV (für EVUs). Da mit einem float zu arbeiten ist wesentlich konfortabler als 2 Integer für Meßwert und Skalierungsfaktor. Habe beides in der Praxis gemacht und weiss wovon ich spreche. Zu der von dir angesprochenen Auflösung ob 24-bit wirklich notwendig sind. Wenn man einiges an Mathematik (Winkelfuntionen, Wurzel ..) auch noch braucht muß man doch auch bei 32-bit int verdammt aufpassen nichts an Genauigkeit zu verlieren.
Fritz schrieb: > Ich verstehe nicht warum man ein wirklich schön definiertes Format > (IEEE) durch Zerstören (Stringumwandlung) und wieder Rekonstruieren > besser behandelt, als wenn man sich die Unterschiede der Plattform > anschaut und entsprechend umsetzt. Der Artikel geht eben genau darum, was man beachten muss, dass der gleiche C-Code auf verschiedenen Plattformen ohne Änderung läuft. Nicht, wie man am effizientsten Fließkommazahlen überträgt. Beides gleichzeitig geht eben leider nicht. Wenn es Dir nichts ausmacht, den Code für einen anderen Prozessor ggf. ändern zu müssen, hält Dich ja niemand davon ab, es so zu machen. Außer, dass Du wie gesagt besser memcpy() statt dem Pointercast nehmen solltest. Fritz schrieb: > Da mit einem float zu arbeiten ist wesentlich konfortabler als 2 Integer > für Meßwert und Skalierungsfaktor. Habe beides in der Praxis gemacht und > weiss wovon ich spreche. Dass das komfortabler ist, hat niemand bestritten. Die meisten der nicht-portablen Beispiele sind komfortabler zu schreiben. Unkomfortabel wirds erst, wenn der Code nach dem Portieren plötzlich nicht mehr wie erwartet funktioniert, und man vor allem zunächst nicht weiß, warum. Fritz schrieb: > Zu der von dir angesprochenen Auflösung ob 24-bit wirklich notwendig > sind. > Wenn man einiges an Mathematik (Winkelfuntionen, Wurzel ..) auch noch > braucht muß man doch auch bei 32-bit int verdammt aufpassen nichts an > Genauigkeit zu verlieren. Für Berechnungen (Zwischenergebnisse) keine Frage. Aber ist es nicht eher so, dass entweder der Mikrocontroller mit der Messeinrichtung alle Berechnungen durchführt und dann die Endergebnisse zurückgibt? Oder eben nur die Rohwerte misst und die Berechnung z.B. an den PC delegiert. Zwischenergebnisse, die so hohe Präzision erfordern, über die Leitung zu schicken, halte ich konzeptionell für fragwürdig. Aber mag sein, dass es auch sowas gibt. Dann muss man eben entweder den Aufwand treiben, das sauber zu serialisieren oder man verzichtet auf plattformunabhängigen Code.
Gast
#3078654
Fabian O. schrieb: > Der Artikel geht eben genau darum, was man beachten muss, dass der > gleiche C-Code auf verschiedenen Plattformen ohne Änderung läuft. > Nicht, wie man am effizientsten Fließkommazahlen überträgt. Du hast in deiner Gleitkommaabsatz von Übertragung zu anderen Platformen gesprochen mit extra zu implementierenden Stringformatierungen. Die von mir vorgeschlagene Pointercastoperation ist aber nur notwendig wenn man extern kommuniziert und nicht intern im C-code. Nach unserer langen Diskussion bin ich zu der Erkenntnis gekommen, dass die Verwendung von float (IEEE definitionsgemäß) viel besser geeignet ist als int um portablen Code zu schreiben. Also sollte man eher float verwenden als int wenn es die Performance zuläßt. Fabian O. schrieb: > Fritz schrieb: >> Da mit einem float zu arbeiten ist wesentlich konfortabler als 2 Integer >> für Meßwert und Skalierungsfaktor. Habe beides in der Praxis gemacht und >> weiss wovon ich spreche. > > Dass das komfortabler ist, hat niemand bestritten. Die meisten der > nicht-portablen Beispiele sind komfortabler zu schreiben. Unkomfortabel > wirds erst, wenn der Code nach dem Portieren plötzlich nicht mehr wie > erwartet funktioniert, und man vor allem zunächst nicht weiß, warum. 1.) Pointercasting brauche ich im C-Programm ohne Kommunikation nicht. 2.) Alle C-Compiler die vorgeben IEEE-floats zu verwenden egal ob per SW oder FPU werden keine Probleme bei der Portierung machen, wenn der Compiler nicht fehlerhaft ist. 3.) Die Verwendung von floats erlaubt eine besseres Abstrahierungsvermögen als integer. Eine Trennung von Hardware zu Applikation ist klarer möglich. Dazu folgendes Beispiel aus meiner Praxis die Messung einer Spannung: Am Anfang steht der ADC mit seinen unterschiedlichen Auflösungen 8, 10, 12 16 bit oder was auch immer. Diesn Wert mußt du in ein Format bringen, das am besten zur Weiterverarbeitung geeignet ist. Meist dann ein Fixpointformat um z.B. eine FFT zu berechnen. Dazu muß ich einen Skalierungsfaktor berechnen der auch die Kalibrierung mit einschließt. Nach der FFT-Berechnung kann ich dann mittels diesen Skalierungsfaktor auf den physikalischen Meßwert kommen. Für verschiedene physikalische Messwerte wie Spannung, Strom, Leistung, .. brauche ich jeweils eigene Skalierungsfaktoren, die möglicherweise auch noch miteinander verknüpft sind. Das läßt sich alles sauber mit int programmieren, keine Frage. Aber: wenn ich gleich nach der AD-Wandlung in einer routine den AD-Wert in ein float der entsprechenden physikalischen Größe umwandlen. Weitere Berechnungen entsprechen direkt den mathematischen Formeln (Z.B. FFT) und müssen nachher nicht mehr umskaliert werden. Was ich damit sagen will ist, dass die Abstrahierung von ADC-Wert in einem Schritt zum physikalischem Meßwert als float sinnvoll und leicht verständlich ist. Die unportablen Teile der Anwendung können so klar in die ADC->float routine gekapselt werden, der Rest ist dann völlig portabel. Das habe ich mit "komfortabel" gemeint. Fabian O. schrieb: > Der Compiler darf Dir > solche Konstrukte in C99 ggf. wegoptimieren. Wirklich legal ist es nur > per memcpy(). float fl; int32 data32; fl = *((float*)&data32); Was soll denn bei der Optimierung anderes herauskommen als ein memcpy?
Gast
#3078658
Fritz schrieb: > Also sollte man eher float > verwenden als int wenn es die Performance zuläßt. Soso. Und warum meidet man float auf Mikrocontrollern wie die Pest?
Fritz schrieb: > fl = *((float*)&data32); > > Was soll denn bei der Optimierung anderes herauskommen als ein memcpy? Wenn man per float * auf Daten zugreift, die eigentlich uint32_t sind, dann sieht der Compiler keinen möglichen Konflikt dazwischen und es kann sein, dass die aktuelle Version der Daten nur im Register und nicht im Speicher steht. Haben Pointer und Daten den gleichen Typ (oder irgendeinen char*), dann weiss er, dass es einen Konflikt geben kann. Stell dir vor, der Compiler trennt dieses Statement in float *p = (float*)&data32; float f = *p; und verzögert das zweite davon an eine Stelle, an der der aktuelle Wert von data32 im Register steht und nicht im Speicher. Ich weiss aber nicht, ob das bei einem solchen Beispiel realistisch vorkommen wird, oder ob Compiler angesichts von &data32 ohnehin auf "Vorsicht" schalten.
Gast
#3078682
Performer schrieb: > Soso. Und warum meidet man float auf Mikrocontrollern wie die Pest? War wohl eher der Zwang float nicht zu verwenden. 1.) Weil viele uCs keine FPU haben, 2.) Weil die libs für float sehr viel Platz brauchen, speziell wenn man auch printf verwendet, und die kleinen uCs eben nicht genung flash haben. 3.) Weil die Performance von SW float sehr schlecht ist. 4.) Weil ein float 4 byte braucht anstatt 2 für ein int16 (also mehr RAM braucht). 5.) Weil die freien abgespeckten Versionen der kommerziellen IDEs nur 32K Programm erlauben und damit die Verwendung der flaot-libs praktisch unmöglich machen. Das hat sich aber mit der Einführung des ARM Cortex M4 mit FPU doch stark geändert. Jetzt spielen nur noch die 4. u. 5. eine Rolle.
Gast
#3078685
Fritz schrieb: > Das hat sich aber mit der Einführung des ARM Cortex M4 mit FPU doch > stark geändert. > Jetzt spielen nur noch die 4. u. 5. eine Rolle. Aso. Wenn plattformübergreifend > "ARM Cortex M4 mit FPU" bedeutet ...
Fritz schrieb: > Du hast in deiner Gleitkommaabsatz von Übertragung zu anderen Platformen > gesprochen mit extra zu implementierenden Stringformatierungen. Die von > mir vorgeschlagene Pointercastoperation ist aber nur notwendig wenn man > extern kommuniziert und nicht intern im C-code. > > [...] > > 1.) Pointercasting brauche ich im C-Programm ohne Kommunikation nicht. Genau darum geht es doch die ganze Zeit: Wie man Fließkommazahlen nach draußen kommuniziert, z.B. vom Mikrocontroller zum PC. Innerhalb des Mikrocontrollers oder innerhalb des PCs kannst Du so viel mit float rechnen, wie Du lustig bist. Das gibt überhaupt keine Probleme hinsichtlich Portabilität. Deshalb ist mir auch nicht so klar, was Du mit dem restlichen Text sagen möchtest. Klar kannst Du das innerhalb des C-Programms alles mit float rechnen. Dafür gibts den Datentyp ja. Das Problem entsteht erst, wenn Du mit Type punning (Pointer Cast) am C-Typsystem vorbei auf die interne Bitrepräsentation zugreifst. Da gibt es nämlich keine Garantien mehr, dass die zwischen Mikrocontroller und PC gleich ist. Fritz schrieb: > float fl; > int32 data32; > > fl = *((float*)&data32); > > Was soll denn bei der Optimierung anderes herauskommen als ein memcpy?
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
Der Compiler darf die Zuweisung an data32 wegoptimieren, da data32 in der Funktion nicht mehr über seinen Namen, einen int32-Pointer oder char-Pointer gelesen wird. Der float* darf gemäß der Aliasing-Regel nicht auf einen int32 zeigen.
Performer schrieb: > Soso. Und warum meidet man float auf Mikrocontrollern wie die Pest? Das habe ich mich auch schon gelegentlich gefragt. Klar, bei 4KB Tinys ist das Unfug. Aber schon bei 8-Bittern mit 32K Flash kann float in der Programmierung wesentlich angenehmer sein als eine Umgehung durch Festkommaarithmetik, und vom Platzverbrauch evtl. tolerierbar. Insbesondere es nur um Berechnungen geht und platzraubende Ein/Ausgabe nicht erforderlich ist. Nicht in jedem Fall ist die Performance wirklich kritisch.
Gast
#3078695
A. K. schrieb: > Stell dir vor, der Compiler trennt dieses Statement in > float *p = (float*)&data32; > float f = *p; > und verzögert das zweite davon an eine Stelle, an der der aktuelle Wert > von data32 im Register steht und nicht im Speicher. Ich weiss aber > nicht, ob das bei einem solchen Beispiel realistisch vorkommen wird, > oder ob Compiler angesichts von &data32 ohnehin auf "Vorsicht" schalten. Sorry, das kann ich mir nicht vorstellen. Mein C++Builder macht das optimiert oder nicht optimiert so wie memcpy. Was sagen denn die C-Standard Spezialisten dazu? fl = *((float*)&data32); Sollte das nicht im Standard eine eindeutige Definition haben?
Fritz schrieb: > Sollte das nicht im Standard eine eindeutige Definition haben? § 6.5 Abs. 7 im C99-Standard: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf An object shall have its stored value accessed only by an lvalue expression that has one of the following types: — a type compatible with the effective type of the object, — a qualified version of a type compatible with the effective type of the object, — a type that is the signed or unsigned type corresponding to the effective type of the object, — a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object, — an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or — a character type. Ist übrigens alles im Artikel verlinkt ...
Gast
#3078716
Vorab möchte ich feststellen, dass Memcpy sicher die bessere Lösung ist. Aber um das eindeutig zu klären bitte Fabian O. schrieb: > Der Compiler darf die Zuweisung an data32 wegoptimieren, da data32 in > der Funktion nicht mehr über seinen Namen, einen int32-Pointer oder > char-Pointer gelesen wird. Der float* darf gemäß der Aliasing-Regel > nicht auf einen int32 zeigen. Was ist jetzt genau die Aliasing-Regel, wenn das ein Fehler ist, warum gibt der Compiler keine Fehlermeldung aus? Warum kann er wegoptimieren ist &data32 kein int32-Pointer?
Gast
#3078753
>Was sagen denn die C-Standard Spezialisten dazu? >fl = *((float*)&data32); Nur zum Verständnis: Was ist an diesem Verfahren besser, als eine Union zu verwenden?
franz schrieb:> Nur zum Verständnis: Was ist an diesem Verfahren besser, als eine Union > zu verwenden? Bei der Union darf man offiziell nur jene Komponente lesen, die man vorher reingeschrieben hat. Also in union { uint32_t u32; float f32; }; nicht u32 schreiben und danach f32 lesen.
Fritz schrieb: > Mein C++Builder macht das optimiert oder nicht optimiert so wie memcpy. Reale Compiler sind hier irrelevant. > Sorry, das kann ich mir nicht vorstellen. Weshalb nicht? Casts sind eine recht gefährliche Ecke und hebeln einige Sicherheitsmechanismen aus. Ich empfehle daher, sie sparsam einzusetzen.
franz schrieb: >>Was sagen denn die C-Standard Spezialisten dazu? >>fl = *((float*)&data32); > > Nur zum Verständnis: Was ist an diesem Verfahren besser, als eine Union > zu verwenden? Nichts, beides ist unspecified (type punning). Fritz schrieb: > Was ist jetzt genau die Aliasing-Regel, wenn das ein Fehler ist, warum > gibt der Compiler keine Fehlermeldung aus? Also GCC kann das, etwa mit -fsyntax-only -Werror=strict-aliasing.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
1 | |
2 | |
3 | |
Übrigens optimiert GCC mit Optimierung bei der memcpy-Version
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
den memcpy-Ausruf komplett raus. Getestet mit GCC 4.3, 4.5, 4.6, 4.7 und 4.8. In meinem Umfeld wäre der float-Cast übrigens kein Problem, solcher Code würde schon durch kein Review kommen ;-)
Gast
#3078826
>Bei der Union darf man offiziell nur jene Komponente lesen, die man >vorher reingeschrieben hat. Also in > union { > uint32_t u32; > float f32; > }; >nicht u32 schreiben und danach f32 lesen. Danke. BTW: Ich habe einen Vorschlag zum Theman Datenübertragung eines Floats und korrekter Interpretation. Man könnte die Mantisse und den Exponent berechnen und dann die beiden übertragen. Ich weiß, es ist umständlich, aber Fehlinterpretationen sind dann ausgeschlossen.
Gast
#3079150
A. K. schrieb: >> Mein C++Builder macht das optimiert oder nicht optimiert so wie memcpy. > > Reale Compiler sind hier irrelevant. Bitte warum!!!!???
Weil "mein Compiler hat damit kein Problem" reinweg nichts über realistische bis theoretische Portabilitätskriterien und entsprechende Problemfelder aussagt.
Gast
#3079164
franz schrieb: > BTW: Ich habe einen Vorschlag zum Theman Datenübertragung eines Floats > und korrekter Interpretation. Man könnte die Mantisse und den Exponent > berechnen und dann die beiden übertragen. Ich weiß, es ist umständlich, > aber Fehlinterpretationen sind dann ausgeschlossen. Halte ich auch für eine gute Lösung. Leider geben uns ja hier die Spezialisten nur Antworten wie man es falsch oder nicht C-spezifikationsgerecht macht, aber nicht wie man das IEEE-Bitmuster korrekt von einer auf die andere Plattform überträgt. Ich persönlich würde am liebsten 4 einzelne Bytes in einer definierten Reihenfolge (z.B. Mant0-Mant7, Mant8-Mant15, Mant16-Mant23-E0, E1-E7-V) übertragen. Das könnte sehr wohl in jeweils einer Lese und Schreibroutine in C für meinen Compiler gekapselt und geschreiben werden. Wie weit das aber plattformübergreifend C-spzifisch OK ist kann ich nicht sagen. Haben wir ja oben diskutiert. Hilfreich in dem Artikel wäre auch wenn man im Compiler per möglicher globale defines spezifisch auf big- bzw. littleendian oder integerbitlänge reagieren kann. Gibt es dafür Konstanten?
Gast
#3079168
A. K. (prx) schrieb: > Weil "mein Compiler hat damit kein Problem" reinweg nichts über > realistische bis theoretische Portabilitätskriterien und entsprechende > Problemfelder aussagt. Interessant. Dann ist das ganze hier wohl nur ein Trockenkurs für Theoretiker. Ich dachte die Artikel sollten von praktischer Relevanz sein, wegen "Andreas Schwarz schrieb: Das Hauptkriterium ist: wie interessant und nützlich ist der Artikel für den Leser;" Naja, Theorie kann ja auch ganz interessant sein. Den Nerd wird's freuen. Der Beamtenapparat ergötzt sich schließlich auch am Kleingedruckten seiner Verordnungen. :-)
Gast
#3079171
A. K. schrieb: > Weil "mein Compiler hat damit kein Problem" reinweg nichts über > realistische bis theoretische Portabilitätskriterien und entsprechende > Problemfelder aussagt. Theoretisch OK! Realistisch gibt es zumindesten Lösungen zwischen realen Plattformen die offensichtlich keine Portabilitäsprobleme generiren. Aber ich warte noch immer auf eine theoretisch korrekte Lösung?
Fritz schrieb: > Leider geben uns ja hier die Spezialisten nur Antworten wie man es > falsch oder nicht C-spezifikationsgerecht macht, aber nicht wie man das > IEEE-Bitmuster korrekt von einer auf die andere Plattform überträgt. Es gibt doch verschiedene Lösungsansätze. Nur gefallen sie Dir offenbar alle nicht. - Umwandeln in String. Fehlinterpretation ausgeschlossen, sogar menschenlesbar. - Umwandeln in Festkomma. Sehr effizient, sehr einfach zu definieren. - Getrennte Übertragung von Exponent und Mantisse als Integer. Letzteres geht mit den Funktionen aus der Standardbibliothek in einer Zeile:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
Ausgabe:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
Die Mantisse wird also als 32-Bit-Integer übertragen, als Exponent würden 8 Bit reichen. Serialisieren / Deserialisieren geht in einer Zeile mit Standardfunktionen. Die Genauigkeit ist sogar höher als ein einfacher float. Wenn Dir das alles nicht gefällt, machst Du eben weiter Type Punning. Oder Du friemelst die Bits von Hand ins IEEE-Format, geht sicher auch. Sehe nur keinen wirklichen Nutzen drinnen. Fritz schrieb: > Hilfreich in dem Artikel wäre auch wenn man im Compiler per möglicher > globale defines spezifisch auf big- bzw. littleendian oder > integerbitlänge reagieren kann. > > Gibt es dafür Konstanten? Ja, stehen im Artikel.
Adler schrieb: > Interessant. Dann ist das ganze hier wohl nur ein Trockenkurs für > Theoretiker. Ich dachte die Artikel sollten von praktischer Relevanz > sein, wegen Portabilität bezieht sich nicht auf die Portierung von C++Builder V1.2.3 auf C++Builder V1.2.3. Daher nützt es reinweg nichts, wenn dieser C++Builder V1.2.3 eine bestimmte Formulierung problemlos frisst, aber andere Compiler damit Probleme bekommen könnten. Ein Fokus des Artikels und des Threads liegt m.E. auf der Vermeidung von Konstruktionen, die bestimmte Eigenschaften von Compilern voraussetzen, die in der Sprache undefiniert oder unspezifiziert sind. Das hat notwendigerweise viel mit der Sprachdefinition zu tun, weniger mit den Eigenschaften von einem oder mehreren realen Compilern. Der Thread ist allerdings mittlerweile ein wenig vom Thema Portabilität von Programmen abgewandert und fokussiert sich nun eher auf Portabilität von Datenformaten zwischen verschiedenen Systemen. Ist auch ein interessantes Thema, aber ein anderes. Bei Portabilität von Programmen wie Daten sind beide Aspekte relevant. Sowohl die theoretische sich aus der Sprachdefinition ergebende Situation als auch die praktische sich aus realen Maschinen und Compilern ergebene. So erlaubt die Sprachdefinition Fliesskommaformate, die nicht auf IEEE 754 basieren, und das ist keine Theorie, denn solche Systeme existieren nach wie vor in wirtschaftlich relevantem Umfang. Allerdings wird man sich für praktische Betrachtungen derzeit eigentlich immer auf IEEE 754 beschränken können. Ein weiterer ähnlicher Aspekt wird im Artikel kurz angeschnitten: die Darstellbarkeit von -2^(N-1). Theoretisch muss man - dem Standard folgend - darauf verzichten. Praktisch kann man wohl davon ausgehen, dass für absehbare Zeit bei Ganzzahlen nur Zweierkomplementdarstellung existieren wird. Dass man mit allzu enger Fixierung auf praktische Betrachtungen aber auf die Nase fallen kann zeigte die Jahr 2000 Umstellung. Viele Programme aus früheren Jahrzehnten waren nie für den Einsatz in derart langem Zeitraum gedacht und bekamen daher Probleme. Das gleiche kann mit Programmen geschehen, die sich allzu sehr auf bestimmte praktische Eigenschaften von Maschinen und Compilern fixieren. Denn der Umstand, dass heutige reale Compiler und Maschinen bestimmte Eigenschaften haben, garantiert nicht, dass es in 20 Jahren immer noch so sein wird. Ich hatte oben ein Beispiel gebracht, in dem der Optimizer ein C Statement aufsplittet und einen Teil davon an eine andere Stelle schiebt. C Compiler von anno 1980 setzten Statements ziemlich 1:1 um, da konnte das nie geschehen, weshalb man damals beispielsweise auch kein "volatile" brauchte. Heute sind solche Aktionen bei hochoptimierenden Compilern Alltag. Manche Probleme mit GCC rühren daher, dass die Optimierung dieses Compilers aus dem Highend-Sektor stammt und er daher aggressiver und mit anderem Ziel vorgeht, als Compiler, die nur den Mikrocontroller-Markt adressieren.
Fritz schrieb: > Aber ich warte noch immer auf eine theoretisch korrekte Lösung? Korrekter als ASCII mit Einschränkung auf die definierten Wertebereiche wirds nicht. Da Fliesskommadarstellung ohnehin nicht auf absolut exakte Ergebnisse abzielt muss das ausreichen. Wers noch weiter treiben will nimmt ASCII Darstellung auf Hex-Basis, was im Rahmen binärer Internformate exakt umsetzbar ist. Eine theoretisch in jedem Fall absolut korrekte Lösung scheitert m.E. an der Offenheit der Spezifikation der Sprache.
Fritz schrieb: > Leider geben uns ja hier die Spezialisten nur Antworten wie man es > falsch oder nicht C-spezifikationsgerecht macht, aber nicht wie man das > IEEE-Bitmuster korrekt von einer auf die andere Plattform überträgt. Beitrag "Re: Feedback zum Artikel "Plattformunabhängige Programmierung in C"" > Hilfreich in dem Artikel wäre auch wenn man im Compiler per möglicher > globale defines spezifisch auf big- bzw. littleendian oder > integerbitlänge reagieren kann. > > Gibt es dafür Konstanten? AFAIK nicht plattformübergreifend. Bei GCC gibt's immerhin Makros wie in Beitrag "Re: Feedback zum Artikel "Plattformunabhängige Programmierung in C"" Ansonsten muss man sich seine eigene hostconfig.h erzeugen, etwa
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
Sowas hab ich bisher erst einmal gebraucht um Endianess aus einem Programm zu faktorisieren. Ein Test zur Laufzeit war zu aufwändig und je nach Compiler nicht optimiert. Obiger Ansatz setzt allerdings voraus, daß das Programm auf dem jeweiligen Host ausgeführt werden kann, was nicht ganz trivial ist, etwa wenn das Programm auf einem µC zu hosten ist. Teilweise lassen sich Host-Features auch zur Compilezeit, d.h. auf der Build-Plattform erfragen, so dass kein Code auf dem Host ausgeführt werden muss. Etwa:
1 | |
Was je nach Host zu einem Compile-Fehler führt (int != 2 Byte) oder durchgeht (int = 2 Byte). Damit ist man dann bei den Auto-Tools (autoconf, etc.) angelangt...
Gast
#3079983
Fabian O. schrieb: > Wenn Dir das alles nicht gefällt, machst Du eben weiter Type Punning. > Oder Du friemelst die Bits von Hand ins IEEE-Format, geht sicher auch. > Sehe nur keinen wirklichen Nutzen drinnen. Irgendwie scheine ich dir auf die Nerven zu gehen? Aber schlußendlich hast du nun eine Lösung (serialize, deserialize von oben) präsentiert, die C-konform ist und bezüglich Performance und benötigten Bytes für die Kommunikation einer Stringum- und Rückwandlung überlegen ist. Auch Infinity wird (meinem kurzem Test zufolge) richtig übertragen. Wenn ich deine im Artikel erwähnte Formulierung " empfiehlt sich die Wandlung in Festkommazahlen oder Strings. " ansehe, kommt auch ein geübter C-programmierer nicht sofort auf obige Lösung. Da du aber die Integerproblematik sehr ausführlich behandelt und mit Beispielen versehen hast, würde ich es sehr begrüßen, wenn du obige Funktionen Serialize und Deserialize als korrekte Umwandlung einfügen würdest. Aber bitte für float und double getrennt jeweils auch mit den entsprechend richtigen math.h Funtionen für float: frexpf, ldexpf, powf für double: frexp, ldexp, pow Ob du auch noch Stringum- und Rückwandlung als Alernative drinnen lassen möchtest, sei dir überlassen.
Fritz schrieb: > Irgendwie scheine ich dir auf die Nerven zu gehen? Naja, es ist schon etwas mühsam, wenn Du Dich immer wieder darüber beschwerst, dass in einem Artikel über plattformunabhängigen Code nicht empfohlen wird, plattformabhängige Konstrukte zu verwenden ... Wie gesagt, es zwingt Dich niemand, Deinen Code plattformunabhängig zu machen. Das meine ich wirklich völlig neutral. Es ist ja (zumindest imo) genau eine der Stärken von C, dass man solche extrem effizienten Lösungen wie die interne Repräsentation eines floats als int zu interpretieren, überhaupt hat. Damit kann man ja auch tatsächlich nützliche Dinge machen: http://en.wikipedia.org/wiki/Fast_inverse_square_root In vielen anderen Hochsprachen hat man keine Chance, das hinzubekommen. Ebenso ist toll, dass es C für nahezu jeden Prozessor gibt. Nur kann man eben nicht auch noch erwarten, dass extrem effiziente Konstrukte, die auf maschinenabhängigen Eigenschaften basieren, auch noch portabel sind. Man kann nicht alles haben. Der Artikel soll zeigen, auf was man aufpassen muss, wenn man seinen Code auf verschiedenen Plattformen einsetzen will und was prinzipiell schief gehen kann. Wenn Du sagst, mein Code wird nur auf Prozessoren mit gleicher Float-Repräsentation laufen (und damit hast Du ja auch ziemlich wahrscheinlich recht), dann bitte, mach es so. Ich benutze zum Beispiel auch ohne schlechtes Gewissen uint8_t statt unsigned char oder uint_least8_t, obwohl es uint8_t nicht auf jeder Plattform gibt. Einfach weil ichs leserlicher finde und nicht erwarte, dass der Code mal auf einem komischen DSP laufen muss ... Aber ich finde es sinnvoll, solche Dinge zumindest zu wissen und im Hinterkopf haben. > Aber schlußendlich hast du nun eine Lösung (serialize, deserialize von > oben) präsentiert, die C-konform ist und bezüglich Performance und > benötigten Bytes für die Kommunikation einer Stringum- und Rückwandlung > überlegen ist. Auch Infinity wird (meinem kurzem Test zufolge) richtig > übertragen. > > Wenn ich deine im Artikel erwähnte Formulierung > " empfiehlt sich die Wandlung in Festkommazahlen oder Strings. " > ansehe, kommt auch ein geübter C-programmierer nicht sofort auf obige > Lösung. Da du aber die Integerproblematik sehr ausführlich behandelt und > mit Beispielen versehen hast, würde ich es sehr begrüßen, wenn du obige > Funktionen Serialize und Deserialize als korrekte Umwandlung einfügen > würdest. Habe ich schon gemacht. Ich hatte die Funktionen beim Schreiben des Artikels zugegebenermaßen auch nicht im Kopf. Die hat erst Johann L. in seinem Beitrag ins Spiel gebracht. Bin aber auch nach wie vor der Meinung, dass man mit Festkomma oder Strings als Netzwerkformat die meisten Anwendungsfälle sehr gut bzw. besser abdecken kann. Aber gut, die Diskussion hatten wir schon ... > Aber bitte für float und double getrennt jeweils auch mit den > entsprechend richtigen math.h Funtionen > für float: frexpf, ldexpf, powf > für double: frexp, ldexp, pow Die Varianten für float sind keine Standardfunktionen, ist also hinsichtlich Portabilität nicht zu empfehlen. Auf Prozessoren mit FPU ist es eh egal, weil der mit double genau so gut rechnen kann. Aber ich schreibe noch nen Satz dazu, dass es die gibt. > Ob du auch noch Stringum- und Rückwandlung als Alernative drinnen lassen > möchtest, sei dir überlassen. Das IST eine Alternative, und zwar nicht die schlechteste. Warum sollte man die nicht mal nennen sollen? Was in der jeweiligen Anwendung am sinnvollsten ist, muss schon jeder selber entscheiden. Je mehr Alternativen man zur Auswahl hat, desto besser.
Fabian O. schrieb: > Die Varianten für float sind keine Standardfunktionen, ist also > hinsichtlich Portabilität nicht zu empfehlen. Korrektur: In C99 sind sie Teil des Standards.
Gast
#3080224
Fabian O. schrieb: > Auf Prozessoren mit FPU > ist es eh egal, weil der mit double genau so gut rechnen kann. Nein leider nicht! Da wir hier im Mikrokontroller Forum sind: Es gibt seit ein paar Jahren den ARM Cortex M4 schon von mehreren Herstellern und auch sehr preiswert und inzwischen hier im Forum sehr beliebt. Der hat aber eine FPU die nur float kann aber leider keine double.
Gast
#3080268
Hallo Fabian ich bins, muß nochmal lästig sein. Du hast beide Varianten (float und double) in den Artikel gestellt, aber die double Variante ist nicht korrekt. Du verwendest für die Mantisse ein int32, was aber für ein double viel zu klein ist. Die integers für die double routinen müssen int64 sein!
Weiterer Aspekt der Portierbarkeit ist das Verhalten von NANs, d.h. ob es eine stille NaN ist (non-signaling) oder nicht (signaling). Wenn man die Plattform wachselt kann man sich dann wahlweise wundern, wo die Exceptions herkommen, oder warum keine mehr kommen und die Algorithmen stattdessen Käse liefern :-)
Fritz schrieb: > Der hat aber eine FPU die nur float kann aber leider keine double. OK, wusste ich nicht. Dann lohnt es sich natürlich, die Float-Funktionen zu nutzen. Fritz schrieb: > Du hast beide Varianten (float und double) in den Artikel gestellt, aber > die double Variante ist nicht korrekt. Du verwendest für die Mantisse > ein int32, was aber für ein double viel zu klein ist. Die integers für > die double routinen müssen int64 sein! Naja, was heißt "korrekt". Ist halt die Frage, welche Genauigkeit man braucht. Denk mal, für viele Anwendungen reichen 32 Bit völlig. Man könnte im Prinzip jede beliebige Anzahl an Mantissenbits übertragen, auch 16, 24, 32, 40, 48, je nach benötigter Genauigkeit. Glaub ich stell das wieder zurück auf eine Variante als Beispiel. Ist ja auch nicht dafür gedacht, 1:1 kopiert zu werden, sondern einfach zu zeigen, wie man die Funktionen benutzen kann.
Wäre ein ldexp[f] (x, 31) nicht besser als ein x * pow[f] (2, 31) ? Sowohl was die Effizienz als auch Rundungsfehler angeht. Zwar wird man Rundungsartefakte haben, aber die müssen ja nicht größer sein als nötig. Und was spricht eigentlich gegen ein "x * (1ul << 31)"?
Ich finde die Variante mit pow(2, 31) am klarsten. Macht es denn einen Unterschied? Hätte erwartet, dass das zur Compilezeit durch die entsprechende Konstante ersetzt wird. Oder kann der Compiler das nicht?
Habs mal ausprobiert.
1 | |
2 | |
3 | |
4 | |
und
1 | |
2 | |
3 | |
4 | |
ergeben mit GCC am PC identischen Code:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
Die Version mit ldexp()
1 | |
2 | |
3 | |
4 | |
ergibt:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
Würde mal sagen, die Multiplikation ist schneller? Für "dümmere" Compiler schreibt man aber vielleicht besser (1UL << 31).
Fabian O. schrieb: > Ich finde die Variante mit pow(2, 31) am klarsten. Macht es denn einen > Unterschied? Hätte erwartet, dass das zur Compilezeit durch die > entsprechende Konstante ersetzt wird. Jo, stimmt. Sind ja beide Parameter bekannt. GCC 4.8 faltet das sogar ohne Optimierung...
Aber da wir hier beim Thema Portabilität sind: Solche Optimierungen sind keine Selbstverständlichkeit.
Gast
#3080761
Fabian O. schrieb: > Naja, was heißt "korrekt". Ist halt die Frage, welche Genauigkeit man > braucht. Denk mal, für viele Anwendungen reichen 32 Bit völlig. Man > könnte im Prinzip jede beliebige Anzahl an Mantissenbits übertragen, > auch 16, 24, 32, 40, 48, je nach benötigter Genauigkeit. Einerseits bist du sehr genau und pedantisch was die Umsetzung der Standards betrifft. Dann machst du aber Vorschläge die darauf hinauslaufen: wo double draufsteht ist aber nicht double drin!!??
Gast
#3080857
Johann L. schrieb: > Weiterer Aspekt der Portierbarkeit ist das Verhalten von NANs, d.h. ob > es eine stille NaN ist (non-signaling) oder nicht (signaling). > > Wenn man die Plattform wachselt kann man sich dann wahlweise wundern, wo > die Exceptions herkommen, oder warum keine mehr kommen und die > Algorithmen stattdessen Käse liefern :-) Da fürchte ich, dass die beiden routinen serilize und deserialize auch Probleme machen könnten. Aber seis drum, irgendwie habe ich das Gefühl wie wenn ich von Wien nach London fliegen will, aber mir die Fluggesellschaft vorschlägt, dann fliegen Sie von Wien nach Stockholm und steigen dort in die Maschine nach London um. Komme zwar auch dorthin, für mich aber mit manchen Nachteilen dauert länger, Umwelt! ... Ist halt ein Umweg. Dabei könnte man mit einer Erweiterung der math.h - lib das Problem so einfach, elegant und effizient (benötigt nur 4/8 byte, ist schneller) lösen: 2 Funktionen: int32_t toieebitsf(float x); float fromieebitsf(int32_t i); Ins integer werden die bits so hineinkopiert, wie es der IEEE Standard vorsieht und umgekehrt. Dasselbe entsprechend auch für double. Käme auf den meisten Plattformen auf ein memcpy heraus. Wäre als in32_t portabel. Ich dachte bisher C ist diejenige Hochsprache, die den schnellsten und effizientesten Code erzeugen kann. Wie man sieht, geht aber nicht. Offensichtlich haben die Standardisierungsgremien ein sicher häufig auftretendes Problem nicht berücksichtigt.
Das Überlaufverhalten müsste noch definiert werden.
Fritz schrieb: > Ich dachte bisher C ist diejenige Hochsprache, die den > schnellsten und effizientesten Code erzeugen kann. Das ist nicht der Fall. Die in C recht häufige Möglichkeit von zulässigem Aliasing kann ein ziemliches Problem für einen C Optimizer darstellen.
Gast
#3080884
A. K. schrieb: > Das Überlaufverhalten müsste noch definiert werden. Wieso? Im IEEE-Standard ist doch alles sehr gut definiert. Ich transportiere doch nur ein Bitmuster von der einen Plattform auf die andere. Beide Plattformen sollten doch dieses Bitmuster gleich nach IEEE behandeln. Falls nicht ist die IEEE-Implementation in einer Plattform halt nicht richtig, das kann aber auch bei anderer Umsetzung zu Problemen führen.
Gast
#3080892
A. K. schrieb: > Das ist nicht der Fall. Die in C recht häufige Möglichkeit von > zulässigem Aliasing kann ein ziemliches Problem für einen C Optimizer > darstellen. Ich meinte nur im Vergleich zu anderen Hochsparchen, nicht zu handgeschriebenen Assemblercode.
Gast
#3080901
Der Artikel geht davon aus, dass man Plattformunabhängigkeit (= portierbar) braucht. Meine Erfahrung in der Praxis ist, dass man das in nahezu allen Fällen nicht braucht. Das bedeutet, dass sich der Aufwand (Aufwand == Kosten) den man in Plattformunabhängigkeit steckt nicht amortisiert. Daher sollte man es lassen. Im Normalfall sind die verwendeten Algorithmen nicht so kompliziert, dass man sie für alle Ewigkeit in eine portierbare Bibliothek stecken muss, damit sich der ursprüngliche Implementierungsaufwand über mehrere Portierungen amortisiert. Damit ist an bei einer Grundeinstellung des agilen Entwickelns: YAGNI - You ain't gona need it. Mache nichts, von dem du nicht heute schon weißt, dass du es wirklich in der Zukunft brauchst. Bei der üblichen Portierungsrate von Embedded-Anwendungen, die nahe Null liegt, weiß man heute ziemlich sicher, dass man Portierbarkeit nicht braucht. Daher lohnt es nicht. Wer mit agilen Methoden nichts am Hut hat, der kennt trotzdem vielleicht KISS - Keep it simple stupid. Mach es einfach. Auch das greift hier. Portierbarkeit wird mit zusätzlichen Abstraktionsebenen erkauft. Je mehr Code, desto mehr Fehler kann man einbauen, desto aufwendiger wird die Wartung des Codes. Und schließlich, plattformunabhängiger Code kann bedeuten, dass man gegen die Architektur eines Mikrocontrollers kämpft, nur damit der Code plattformunabhängig ist. Damit stellt man eine Eigenschaft (Plattformunabhängigkeit) über eine effiziente Lösung jetzt und hier. Bei einer Wahrscheinlichkeit von nahe Null dass man die Plattformunabhängigkeit braucht ein sehr schlechter Tausch.
Gast
#3080912
Und noch einer schrieb: > Der Artikel.... Du sprichst mir mit deinen Worten aus der Seele. Ich als alter uC-Anwender sehe das genauso. Ich habe halt versucht, mit meinen dauernden Fragen die Theoretiker selbst draufkommen zu lassen wo halt der Unterschied zwischen Theorie und Praxis liegt.
Fritz schrieb: >> Das Überlaufverhalten müsste noch definiert werden. > > Wieso? Im IEEE-Standard ist doch alles sehr gut definiert. Da schon. Aber nicht in umgekehrter Richtung.
Gast
#3080924
Klar, wenn ein Artikel "Brötchen backen für Fortgeschrittene" geschrieben wird, komme ich auch nicht umhin die Leute in die Richtung zu stupsen, daß die Praxis gezeigt hat, daß zum Frühstück Müsli zu essen deutlich effizienter ist.
Fritz schrieb: > Ich meinte nur im Vergleich zu anderen Hochsparchen, nicht zu > handgeschriebenen Assemblercode. Ich auch. Aber da liegt beispielsweise FORTRAN vorne.
Und noch einer schrieb: > Meine Erfahrung in der Praxis ist, dass man das in > nahezu allen Fällen nicht braucht. Meine Erfahrung wiederum ist, dass man das in vielen Fällen brauchen kann. Sowohl bei Mikrocontrollern, als auch in anderem Umfeld. Beispiele: - Code für 1-Wire- oder SHT11-Sensoren lässt sich weitgehend portabel gestalten, nur der unmittelbare Portzugriff muss angepasst werden. - Netzwerkprotokolle alle Art, beispielsweise RS485 oder CAN lassen sich ausgezeichnet wiederverwenden, wenn der darunter liegende systemabhängige Layer gleich ist. > Und schließlich, plattformunabhängiger Code kann bedeuten, dass man > gegen die Architektur eines Mikrocontrollers kämpft Der Flieskommakram geht in diese Richtung. Der Rest hingegen ist eher eine Frage von "gewusst wie".
Fritz schrieb: > Einerseits bist du sehr genau und pedantisch was die Umsetzung der > Standards betrifft. Dann machst du aber Vorschläge die darauf > hinauslaufen: > > wo double draufsteht ist aber nicht double drin!!?? Wir haben offensichtlich verschiedene Anforderungen. Du willst auf Biegen und Brechen einen IEEE float/double zwischen den Geräten verschicken. Darum gehts aber im Artikel nicht. Sondern welche Möglichkeit man hat, wenn der Code auf jeder Plattform laufen soll. In C gibt es eben leider keine Funktion float_to_ieee_754_char_array() und zurück. Mag sein, dass das manchmal praktisch wäre, gibts aber eben nicht. So gesehen bin ich da sogar sehr pragmatisch: Ich nehme die Möglichkeiten, die der C-Standard mitliefert. Denn auch auf die Gefahr hin, mich zu wiederholen: Wozu muss man bitte einen double mit voller Genauigkeit zwischen zwei Geräten verschicken? Nenn mir mal eine sinnvolle Anwendung dafür. Mir ist bewusst, dass es Algorithmen gibt, für die man gar nicht genug Stellen für die Zwischenergebnisse haben kann, weil sie durch die vielen Iterationen und große Dynamikbereiche Rundsfehler schnell stark auswirken. Aber solche Algorithmen rechnet man doch nicht im Ping-Pong-Verfahren mit zwei Geräten: Gerät A rechnet einen Schritt, schickt die den Wert an B, B rechnet einen Schritt, schickt den Wert zurück an A, A rechnet wieder einen Schritt ... Wenn man sowas baut, ja, dann darf man beim Übertragung keine zusätzlichen Rundungsfehler einbauen. Die Situation in eingebetten Geräten ist dagegen: Datenquelle (z.B. Sensor) -> Gerät A -> Gerät A berechnet einen Algorithmus -> schickt Ergebnis an B -> B berechnet einen Algorithmus -> Datensenke (in Datei speichern, am Bildschirm ausgeben, an Aktor schicken). Für einen Datensatz hat man also einmal eine Übertragung. Wozu braucht die mehr 32 Bit Mantisse (+ Exponent)? Was für Ausgangswerte stecken da drinnen, die so genau sind? Wenn es das in Spezialmessgeräten im Wert von Einfamilienhäusen gibt, gut, dann überträgt man halt mehr. Aber in 99,9% der für mich denkbaren Anwendungsfälle wäre das völliger Overkill. Man muss sich sowieso ein Protokoll ausdenken, mit dem man seine Daten verschickt. Warum sollte man darin also nicht festlegen, wie viele Mantissenbits man für einen Datensatz braucht? Man muss das Protokoll immer auf Sender und Empfänger implementieren, man hat also völlige Wahlfreiheit. Und auf jedem Sender und Empfänger, für den es eine standardkonforme C-Implementierung gibt, gibt frexp() und ldexp(). Übrigens auch printf(), scanf(), atof(), .... Integerverarbeitung (Stichwort Festkomma) sowieso. Also bediene ich mich doch aus diesen Möglichkeiten, statt irgendeinem IEEE-Standard hinterzuhecheln, den ich nicht ohne Verrenkungen plattformübergreifend konvertiert bekomme. Falls man auf einer Seite keine Wahlfreiheit hat, erübrigt sich die Diskussion sowieso: Dann muss man des implementieren, was die Gegenseite will. Und was ich auch schon mehrmals gesagt habe: Wenns Dir nicht drauf ankommt, dass Code plattformunabhängig ist, sondern dass er schnell und effizient ist, hält Dich weiterhin kein Mensch davon ab, Dir per memcpy() die Bitrepräsentation des floats zu holen. Aber zu erwarten, dass genau diese Variante, die plattformabhängig ist, im Artikel über Plattformunabhängigkeit empfohlen wird, ist absurd. Das ist, wie wenn man sich drüber beschwert, wenn in einem Artikel zu umweltbewussten Verhalten empfohlen wird, mit dem Fahrrad zum Bäcker zu fahren. Das Auto ist schließlich viel schneller und bequemer. Oder dass die Autohersteller gefälligst Autos bauen sollen, die die Umwelt nicht belasten.
Gast
#3081355
Fabian O. schrieb: > Wir haben offensichtlich verschiedene Anforderungen. Du willst auf > Biegen und Brechen einen IEEE float/double zwischen den Geräten > verschicken. Ja weil ich auf der versendenden Seite nicht jedes float/double analysieren will wieviel Genauigkeit der Wert braucht, möglicherweise muß ich auch einen Kollegen fragen was Sache ist. Der wird eher die volle Genauigkeit verlangen, um nicht der Schuldige zu sein, wenn etwas dann nicht funktioniert. Bei der Wiederverwendung des Codes muß man auch mehr aufpassen. Sollte man aber auch einmal Zwischenergebnisse zu Debugzwecken auf dem PC brauchen, ist es halt auch vorteilhafter die originalen float oder double zu verwenden. Fabian O. schrieb: > Denn auch auf die Gefahr hin, mich zu wiederholen: Wozu muss man bitte > einen double mit voller Genauigkeit zwischen zwei Geräten verschicken? > Nenn mir mal eine sinnvolle Anwendung dafür. Mit voller Genauigkeit vielleicht nicht. Aber mit float kommt man schon leicht nicht aus: 1.) Agilent hat ein Digitalvoltmeter mit 8,5 Stellen Auflösung. Wenn du nun bedenkst, dass man intern gern mit einer Stelle mehr rechnet kommt man schon reichlich über das float format hinaus. 2.) Die GPS-Zeit wird in usec beginnend 5. Januar 1980 um 24:00:00 angegeben, da kommt auch ein recht heftige Genauigkeit zusammen. Du hast schon recht die vollen 52-bit werde ich kaum brauchen. Aber bei weniger bit müßte man wahrscheinlich noch die Rundung beachten.
Fritz schrieb: > 2.) Die GPS-Zeit wird in usec beginnend 5. Januar 1980 um 24:00:00 > angegeben, da kommt auch ein recht heftige Genauigkeit zusammen. Warum in aller Welt sollte man die in einen double wandeln? Einen float von einer Plattform auf die andere zu transportieren hat doch zunächst nichts mit der plattformunanhängigkeit des Codes zu tun, sondern damit, dass sich beide Seiten auf ein Protokoll, d.h. das Format der zu übertragenden Daten festlegen.
Gast
#3083674
Und noch einer schrieb: > Meine Erfahrung in der Praxis ist, dass man das in > nahezu allen Fällen nicht braucht. Du scheinst keine Erfahrung mit nachhaltiger Entwicklung zu haben. Im Automotive-Bereich wird alle paar Jahre die ganze Software auf die nächste Generation portiert. Gruß Anja
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.