Hallo,
ich überlege mir mittels eines 8Byte Array eine Gleitkommazahl zu
übertragen.
Diese soll in einer 8Byte float Variablen unterkommen.
Das übertragende Array ist als unsigned int8 definiert.
Ich dachte mir durch das Bit Shiften die Daten byteweise rein zu
schreiben,
nur bin ich ins Stocken geraten was das ablegen der Zahl in der float
Variable angeht.
Die Gleitkommazahl wollte ich vor dem Verschicken mit 10000
multiplizieren um diese als eine
Ganzzahl mittels Array zu übertragen.
Äh, ja...
Vielleicht schreibst du dir mal im Detail auf, was wann und warum von wo
nach wo übertragen werden soll, und was eine 8-Byte-Float Zahl sein
soll, was eine Gleitkommazahl sein soll, wofür der Faktor 10000 gut sein
soll, usw.
Und dann formulierst du deine Frage nochmals komplett neu.
Oliver
4byte iee754 floating point => float (aka Single Precision)
8byte iee754 floating point => double (aka Double Precision)
hier bitte nur double oder float sagen sonst verwirrst du alle
keine Ahnung wofür du das * 10000 brauchst
wenn du garantieren kannst das beide System iee754 konforme floating
points haben und du nicht drehen musst (little/big endian) dann kannst
du einfach deine 8bytes rüberjagen und dort als double nutzen - nix mit
konvertieren usw.
Wenn deine Übertragung mit beliebigen Bitmustern zurecht kommt, dann
kannst du einfach den Speicherbereich der double-Variablen übertragen.
Nix umwandeln, nix kopieren in int oder byte oder so.
Wenn die Übertragung extra Steuerzeichen braucht, ist es etwas
aufwändiger.
Aber da bietet sich eine Konvertierung nach ASCII an.
S. R. schrieb:> Und ja, das ist weder portabel noch vom Standard garantiert. Aber es> funktioniert in der Realität. Besser als dein Bitgewürge ist es> allemale.
Warum die union mit undefined behafiour benutzen, wenn nen einfacher
char Pointer funktioniert?
1
voidsende_floatA(doublex){
2
charconst*p=(charconst*)&x;
3
for(inti=0;i<8;i++){
4
sende_byte(p[i]);
5
}
6
}
Oder mit memcpy
1
voidsende_floatB(doublex){
2
charp[8];
3
memcpy(p,&x,8);
4
for(inti=0;i<8;i++){
5
sende_byte(p[i]);
6
}
7
}
gcc x86 compiliert beide Funktionen mit dem exakt gleichen Ergebnis.
Eine Funktion sende_bytes(void *data, size_t len); sollte das alles
machen können
Der Aufruf sende_bytes(&Variablenname, sizeof(Variablenname);
Man kann das auch hinter einem Makro verstecken oder noch mit
sende_double kapseln.
Ohne kopieren oder unions.
Endianess bitte noch beachten. Generell ist das alles hier weiterhin
undefined behavior. Jenachdem welche Maschinen beteiligt sind, ist das
Ergebnis unterschiedlich.
Johannes O. schrieb:> Endianess bitte noch beachten. Generell ist das alles hier weiterhin> undefined behavior. Jenachdem welche Maschinen beteiligt sind, ist das> Ergebnis unterschiedlich.
Das ist nicht die Bedeutung von undefined behaviour in c (oder c++)
Dirk B. schrieb:> Eine Funktion sende_bytes(void *data, size_t len); sollte das alles> machen können>> Der Aufruf sende_bytes(&Variablenname, sizeof(Variablenname);>> Man kann das auch hinter einem Makro verstecken oder noch mit> sende_double kapseln.>> Ohne kopieren oder unions.
Die Schleife in eine Funktion auslagern ist natürliche der richtige
Schritt. Über die Signatur, char oder void Pointer lässt sich allerdings
streiten.
Ein sizeof hinter einem Makro verstecken ist allerdings keine gute Idee.
mh schrieb:> Die Schleife in eine Funktion auslagern ist natürliche der richtige> Schritt. Über die Signatur, char oder void Pointer lässt sich allerdings> streiten.
Eigentlich nicht. char* ist für Text, void* für Daten mit nicht genauer
spezifiziertem Typ. Was passt hier wohl besser?
Rolf M. schrieb:> mh schrieb:>> Die Schleife in eine Funktion auslagern ist natürliche der richtige>> Schritt. Über die Signatur, char oder void Pointer lässt sich allerdings>> streiten.>> Eigentlich nicht. char* ist für Text, void* für Daten mit nicht genauer> spezifiziertem Typ. Was passt hier wohl besser?
Ich würde sagen "Pointer auf Bytes mit unspezifiziertem Inhalt" ist der
richtige Datentyp. Und das ist in c ein char*. In der Funktion muss eh
in einen char* gecasted werden, um auf die einzelnen Bytes zugreifen zu
dürfen.
Ich habe noch nicht ganz verstanden, wofür genau das benötigt wird. Aber
ich hatte mal ein ähnliches Problem. Da wollte ich ein double in eine
Datei schreiben, um den Wert später, möglicherweise auf einem anderen
Rechner, wieder einzulesen. Und das ganze natürlich ohne ein Bit an
Genauigkeit zu verlieren.
Nach einer Diskussion in einem andere Forum bin ich dann beim sog. Hex
Format des printf() Befehls gelandet:
mh schrieb:> Rolf M. schrieb:>> mh schrieb:>>> Die Schleife in eine Funktion auslagern ist natürliche der richtige>>> Schritt. Über die Signatur, char oder void Pointer lässt sich allerdings>>> streiten.>>>> Eigentlich nicht. char* ist für Text, void* für Daten mit nicht genauer>> spezifiziertem Typ. Was passt hier wohl besser?>> Ich würde sagen "Pointer auf Bytes mit unspezifiziertem Inhalt" ist der> richtige Datentyp.
Und wofür siehst du void* als den richtigen Typ an, wenn nicht für
sowas? Das ist genau für unspezifizierten Inhalt gemacht. Dass es Bytes
sind, ist logisch. Daraus ist alles im Speicher aufgebaut.
> Und das ist in c ein char*.
Wie gesagt: char* sollte man für Text verwenden, und sonst für nichts.
Wenn schon, dann wenigstens z.B. unsigned char*.
> In der Funktion muss eh in einen char* gecasted werden, um auf die einzelnen> Bytes zugreifen zu dürfen.
Das ist richtig, aber ändert nichts daran. Die Parameterliste einer
Funktion ist im Prinzip die Definition einer Schnittstelle, und der Typ
sollte möglichst gut zum Ausdruck bringen, was erwartet wird. Und für
mich steht sowas wie char* für Text und unsigned char* für "ein Zeiger
auf einen kleinen vorzeichenlosen Integer" und nicht für "Zeiger auf
irgendwas unspezifisches".
Für mich ist void* ein Pointer auf unbekannten Typ.
Rolf M. schrieb:> Die Parameterliste einer> Funktion ist im Prinzip die Definition einer Schnittstelle, und der Typ> sollte möglichst gut zum Ausdruck bringen, was erwartet wird.
Die Funktion, um die es geht, erwartet Bytes, da sie Bytes verschicken
soll. Also sollten Bytes als Typ in der Parameterliste stehen. In C ist
der Typ für Bytes char, da es keinen expliziten Typ dafür gibt. Der
Standard hat extra Ausnahmen für character types. C++ hat für diesen
Zweck zum Glück endlich std::byte.
Void* steht für einen unbekannten oder unbestimmten Typ. Ein Beispiel
dafür ist der Pointer auf Nutzerdaten in einem Callback. An der Stelle
ist egal worauf der Pointer zeigt, solange die Funktion, die aufgerufen
wird, um welchen konkreten Typ es geht.
Rolf M. schrieb:> Wenn schon, dann wenigstens z.B. unsigned char*.
Warum? Du sagst selbst
Rolf M. schrieb:> unsigned char* für "ein Zeiger> auf einen kleinen vorzeichenlosen Integer"
Was ist die Bedeutung von "Vorzeichen" bei einem "Byte"? Das ist die
gleiche Frage wie was ist ein "Vorzeichen" bei einem "Buchstaben"?
mh schrieb:> Für mich ist void* ein Pointer auf unbekannten Typ.>> Die Funktion, um die es geht, erwartet Bytes, da sie Bytes verschicken> soll.
Sie erwartet einen Speicherblock mit unbekanntem Inhalt.
> Void* steht für einen unbekannten oder unbestimmten Typ. Ein Beispiel> dafür ist der Pointer auf Nutzerdaten in einem Callback. An der Stelle> ist egal worauf der Pointer zeigt, solange die Funktion, die aufgerufen> wird, um welchen konkreten Typ es geht.
Aber genau das ist doch hier auch der Fall: Ein Sender will Nutzerdaten
eines der Sendefunktion unbekannten Typs zu einem Empfänger schicken.
Für die Übertragung ist es egal, worauf er zeigt, solange sich die
Nutzer der Übertraungsfunktionen einig über den Typ sind.
> Rolf M. schrieb:>> Wenn schon, dann wenigstens z.B. unsigned char*.>> Warum? Du sagst selbst>> Rolf M. schrieb:>> unsigned char* für "ein Zeiger>> auf einen kleinen vorzeichenlosen Integer"
Ja, ich halte unsigned char* ja auch für den falschen Typ. Aber er ist
wenigstens besser als nur char*. Schon alleine, weil dann der Bit-Shift
nicht Gefahr läuft, Mist zu produzieren. Denn bei char ist nicht
festgelegt, ob er vorzeichenbehaftet ist oder nicht.
> Was ist die Bedeutung von "Vorzeichen" bei einem "Byte"?
unsigned char ist nicht einfach nur "ein Byte", sondern ein Integer, der
ein Byte groß ist. Er hat einen Zahlenwert im Bereich 0 bis 255
(mindestens). Deshalb ist es ja eigentlich der falsche Typ.
> Das ist die gleiche Frage wie was ist ein "Vorzeichen" bei einem> "Buchstaben"?
Deshalb nimmt man für Text (und nur dafür) char. Da ist wie gesagt nicht
spezifiziert, ob der ein Vorzeichen hat oder nicht, weil das bei der
Nutzung für Text überhaupt keine Rolle spielt. Und ja, es wäre schöner
gewesen, wenn die Unterscheidung zwischen Integern und Zeichen in C
etwas stärker ausgeprägt wäre, was sie aber leider nicht ist.
Der interne Typ muss zum Parameter der Funktion sende_bytes passen.
Da der Wert nur durchgereicht und nicht bearbeitet wird, ist der genaue
Typ in diesem Beispiel eigentlich egal.
Rolf M. schrieb:> mh schrieb:>> Für mich ist void* ein Pointer auf unbekannten Typ.>>>> Die Funktion, um die es geht, erwartet Bytes, da sie Bytes verschicken>> soll.>> Sie erwartet einen Speicherblock mit unbekanntem Inhalt.
Genau! Kein unbekannter Typ, sondern Bytes im Speicher mit unbekanntem
Inhalt.
Rolf M. schrieb:> Für die Übertragung ist es egal, worauf er zeigt, solange sich die> Nutzer der Übertraungsfunktionen einig über den Typ sind.
Die Nutzer müssen sich noch um einiges mehr als den Typ gedanken machen.
Zum Beispiel wie genau das Objekt im Speicher liegt.
Rolf M. schrieb:> Schon alleine, weil dann der Bit-Shift> nicht Gefahr läuft, Mist zu produzieren. Denn bei char ist nicht> festgelegt, ob er vorzeichenbehaftet ist oder nicht.
Das ist tatsächlich ein Argument für unsigned.
Rolf M. schrieb:> unsigned char ist nicht einfach nur "ein Byte", sondern ein Integer, der> ein Byte groß ist. Er hat einen Zahlenwert im Bereich 0 bis 255> (mindestens).
Zustimmung.
Rolf M. schrieb:> Deshalb nimmt man für Text (und nur dafür) char. Da ist wie gesagt nicht> spezifiziert, ob der ein Vorzeichen hat oder nicht, weil das bei der> Nutzung für Text überhaupt keine Rolle spielt.
Für ein "Byte" im Speicher gilt genau das gleiche. Da es nur für
character types die nötigen Ausnahmen für den "bytewisen" Zugriff auf
Objekte im Speicher gibt, gibt es damit keine Alternative als char für
diesen Zweck.
mh schrieb:> Rolf M. schrieb:>> mh schrieb:>>> Für mich ist void* ein Pointer auf unbekannten Typ.>>>>>> Die Funktion, um die es geht, erwartet Bytes, da sie Bytes verschicken>>> soll.>>>> Sie erwartet einen Speicherblock mit unbekanntem Inhalt.> Genau! Kein unbekannter Typ, sondern Bytes im Speicher mit unbekanntem> Inhalt.
Das ist für mich das selbe. Daten jedes beliebigen Datentyps bestehen
aus Bytes im Speicher. Und ich sehe auch nicht, wo im Bezug auf die
Übergabe per Paramter der große Unterschied sein soll, ob ich die
Adresse eines double an eine Callback-Funktion oder an eine
Sende-Funktion übergebe. Warum soll das eine einen void*, das andere
aber einen char* bekommen?
malloc() allokiert auch Bytes im Speicher und gibt einen void* darauf
zurück. Und die Funktion fwrite() will einen void*, der auf die Bytes
zeigt, die es auf die Festplatte rausschreiben soll.
> Rolf M. schrieb:>> Für die Übertragung ist es egal, worauf er zeigt, solange sich die>> Nutzer der Übertraungsfunktionen einig über den Typ sind.> Die Nutzer müssen sich noch um einiges mehr als den Typ gedanken machen.> Zum Beispiel wie genau das Objekt im Speicher liegt.
Das ist aber unabhängig davon, welcher Typ an die Sende-Funktionen
übergeben wird.
> Rolf M. schrieb:>> unsigned char ist nicht einfach nur "ein Byte", sondern ein Integer, der>> ein Byte groß ist. Er hat einen Zahlenwert im Bereich 0 bis 255>> (mindestens).> Zustimmung.
Und was repräsentiert dann dieser Wert? Für sich genommen gar nichts
brauchbares, weil es eben kein kleiner Integer, sondern einfach nur ein
Teil eines an der Stelle unbekannten Typ ist.
> Für ein "Byte" im Speicher gilt genau das gleiche. Da es nur für> character types die nötigen Ausnahmen für den "bytewisen" Zugriff auf> Objekte im Speicher gibt, gibt es damit keine Alternative als char für> diesen Zweck.
Intern für den Zugriff ja. Der ist aber ein Implementierungs-Detail. Die
Schnittstelle sollte sich nicht daran orientieren, wie die Funktion
intern ihre Aufgabe erledigt.
Der Typ ist für die Übertragung (an dieser Stelle) völlig unwichtig.
Darum wurde ein Typ gewählt, der leicht in andere Pointer gewandelt
werden kann.
Dies trifft für void* (zumindest bei C) zu.
Ein Problem vom TO ist, dass er unbedingt ein double in ein char[8]
konvertieren wollte, da er nicht verstanden hat, dass dies der
Übertragungsroutine völlig egal ist.
Auch sowas kann man mit void* vermeiden.
Man kann die Funktion auch sende_daten nennen, damit sich keiner mehr an
den bytes aufge..t.
Rolf M. schrieb:> Und was repräsentiert dann dieser Wert? Für sich genommen gar nichts> brauchbares, weil es eben kein kleiner Integer, sondern einfach nur ein> Teil eines an der Stelle unbekannten Typ ist.
Was repräsentiert der void*, der auf der anderen Seite von deiner
empfange_bytes Funktion zurückgegeben wird?
Rolf M. schrieb:> malloc() allokiert auch Bytes im Speicher und gibt einen void* darauf> zurück. Und die Funktion fwrite() will einen void*, der auf die Bytes> zeigt, die es auf die Festplatte rausschreiben soll.
Ich bin mir nicht sicher, ob die API der cstdlib an dieser Stelle ein
gutes Beispiel ist. Wenn du noch das, an dieser Stelle naheliegende,
memcpy dazu nimmst, hast du drei Funktionen, die direkt mit den Bytes im
Speicher arbeiten, und jeweils ziemlich unterschiedliche Parameter dafür
nutzen. Nur size, oder doch size und count? Erst src und dann dst oder
doch lieber dst und dann src? Dann sagt der Standard zu fwrite:
1
For each object, size calls are made to the fputc function, taking the values (in order) from an array of unsigned char exactly overlaying the object.
und zu memcpy:
1
The memcpy function copies n characters from the object pointed to by s2 into the object pointed to by s1.
Die ganzen Funktionen, die "Buchstaben" verarbeiten, aber int als Typ
benutzen, liste ich mal lieber nicht auf.
Weil das ganze Spass macht nehme ich auch noch memset:
1
The memset function copies the value of c (converted to an unsigned char) into
2
each of the first n characters of the object pointed to by s.
Der Parameter c hat natürlich den Typ int.
Dirk B. schrieb:> Man kann die Funktion auch sende_daten nennen, damit sich keiner mehr an> den bytes aufge..t.
Das ist natürlich die Lösung...
mh schrieb:> Rolf M. schrieb:>> Und was repräsentiert dann dieser Wert? Für sich genommen gar nichts>> brauchbares, weil es eben kein kleiner Integer, sondern einfach nur ein>> Teil eines an der Stelle unbekannten Typ ist.>> Was repräsentiert der void*, der auf der anderen Seite von deiner> empfange_bytes Funktion zurückgegeben wird?
Nichts spezifisches. Deshalb ja void. Genau dafür ist void* da. Alle
anderen suggerieren Daten von einem bestimmten Typ, auch char. Der
Aufrufer der Empfangsfunktion muss dann wissen, was dahinter steckt (wie
eben z.B. ein double) und das in diesen Typ casten. Die Empfangsfunktion
selber weiß das aber nicht. Sie bekommt einfach einen Zeiger auf
"irgendwas"(=void), wo sie die Daten ablegen kann.
> Ich bin mir nicht sicher, ob die API der cstdlib an dieser Stelle ein> gutes Beispiel ist.
Von mir aus kannst du auch bei POSIX schauen. Die Funktion write() will
beispielsweise einen const void* haben. Die Funktion mmap() einen void*.
shmat() auch. Oder die WinAPI. WriteFile() nimmt einen LPCVOID, was
Microsofts "fancy"-Spezialversion eines const void* ist. Oder FreeRTOS.
xStreamBufferSend() will auch einen const void*.
Es ist einfach der Standardfall, dass für sowas void* zum Einsatz kommt.
> Wenn du noch das, an dieser Stelle naheliegende, memcpy dazu nimmst, hast du> drei Funktionen, die direkt mit den Bytes im Speicher arbeiten, und jeweils> ziemlich unterschiedliche Parameter dafür nutzen. Nur size, oder doch size> und count?
Aber immer void*. Wozu size und count, habe ich tatsächlich auch nie
verstanden.
> Die ganzen Funktionen, die "Buchstaben" verarbeiten, aber int als Typ> benutzen, liste ich mal lieber nicht auf.
C nutzt für einzelne Zeichen konsistent immer int als Typ. Soweit ich
weiß, um bei Funktionen, die das unterstützen, auch EOF separat
darstellen zu können. Es wird aber auch bei den Funktionen durchgezogen,
wo's kein EOF gibt.
> Weil das ganze Spass macht nehme ich auch noch memset:The memset> function copies the value of c (converted to an unsigned char) into> each of the first n characters of the object pointed to by s.> Der Parameter c hat natürlich den Typ int.
memset gehört in C anscheinend auch zu dein Stringfunktionen, warum auch
immer. Daher wird - wie eben bei allem, wo auch einzelne Zeichen
vorkommen - int verwendet. Ist ja immerhin auch in <string.h>
deklariert.
Mir ist bewusst, warum die Funktionen int als Typ benutzen, und ich
finde die Begründung mangelhaft. Um das Warum geht es an dieser Stelle
auch nicht. Mein Punkt ist, dass ich deiner Meinung
Rolf M. schrieb:> Deshalb nimmt man für Text (und nur dafür) char.
nicht zustimmen kann, und der c-Standard als Gegenbeispiel dient.