Hallo,
ich möchte verschiedene Daten über eine Netzwerkverbindung schicken. Um
diese auf die Netzwerkverbindung zu schreiben verwende ich die write
Funktion.
Nun möchte ich alle Werte die ich habe vorher Binärisieren, damit ich
nicht einen langen String schicken muss. Wie mache ich sowas am
effizientesten? Vor allem auch wieder zurück. Ich weiß zwar an welcher
stelle das double im Datenstrom ist aber wie kann ich es dort wieder
möglichst einfach herausholen?
char buffer[100]
double a;
int_32t b;
float c;
Im Buffer soll dann aaaaaaaabbbbcccc stehen. Am anderen Rechner will ich
dann wieder abc bekommen. Wenn es nur ein Wert wäre würde ich
buffer[0] = (char) a;
machen. Aber da bin ich mir nicht sicher ob er dann nur ein Byte
kopiert.
By the way: Sind double und float auf jeden System 64 bzw. 32bit breit?
Gruß
Peter
Peter schrieb:> Nun möchte ich alle Werte die ich habe vorher Binärisieren, damit ich> nicht einen langen String schicken muss. Wie mache ich sowas am> effizientesten? Vor allem auch wieder zurück. Ich weiß zwar an welcher> stelle das double im Datenstrom ist aber wie kann ich es dort wieder> möglichst einfach herausholen?
Was ist "binärisieren"? Dein Beispiel sieht ein bisschen nach RLE aus!?
> Wenn es nur ein Wert wäre würde ich buffer[0] = (char) a;> machen. Aber da bin ich mir nicht sicher ob er dann nur ein Byte> kopiert.
Du castest da einen double-Wert auf einen Char-Wert. Das geht nur mit
Informationsverlust.
> By the way: Sind double und float auf jeden System 64 bzw. 32bit breit?
Nein.
Du wandlest sie falls notwendig in ein IEEE 754 Format und überträgst
sie Byte für Byte.
Dabei musst du noch eine Byte Order definieren.
Schau dir an wie es sonst gemacht wird:
Suche nach "net byte order" und "float network byte order".
Hallo,
binärisieren ist vieleicht etwas unglücklich gewählt. Ich möchte mir ein
Datentelegramm aufbauen, dass ich in einen Rutsch verschicken kann. Ich
habe also einen Pointer auf den Anfang und wenn ich diesen Byteweise
durchgehe schicke ich alle Daten.
>aaaaaaaabbbbcccc
Hier steht jeder Buchstabe für ein Byte. Da ein double meines Wissens
64bit hat also 8*a. Ein 32bit int braucht 4 Byte.
Gruß
Peter
Nun, der Gedanke, der augenscheinlich, für Dich im Moment am wichtigsten
zu begreifen ist, ist der, dass ausnahmslos alle Daten binär, d.h. als
Folge von 0 und 1, im Rechner gespeichert sind.
Daraus ergibt sich, dass eine "Binärisierung", wenn ich recht verstehe,
was Du damit meinen könntest, überflüssig ist.
Was aber nun zwei beliebige Folgen von 0 und 1 im Rechner für praktische
Zwecke nutzbar macht, ist deren Interpretation - als vorzeichenlose
oder vorzeichenbehaftete Integerzahl, als Fliesskommazahl oder als Folge
von ASCII-Zeichen oder weiteres.
Dazu bieten Hochsprachen gewisse Sprachmittel (und Maschinensprachen mit
geringerer Mannigfaltigkeit). Sie beschreiben, wie eine gewisse Bitfolge
zu interpretieren ist - neben der Tatsache, dass sie erlaubt bestimmten
Positionen im Speicher einen Namen - den einer Variablen - zuzuordnen.
Ich kenne nun die von Dir erwähnte "write"-Funktion nicht. Aber nehmen
wir einmal an, - weil dies Deinen weiteren Ausführungen entspricht -,
das ihr Argument ein
1
char*
sei.
Die allfälligen C Ausdrücke, die man "cast" nennt, eignen sich nicht
dafür, die Bitfolge, die Du vorderhand als Fliesskommazahl betrachtest -
und durch
1
doublea;
dem Rechner als solche zu interpretieren befohlen hast -, in einen
anderen Speicherbereich zu kopieren, den Du als
1
charbuffer[100]
(übrigens syntaktisch inkorrekt) deklariert hast. Denn dabei wird immer
zuerst das Argument des cast-Ausdrucks umintpretiert - eben als char.
Das aber willst Du nicht. Allein, weil ein char üblicherweise viel
weniger Bits hat als eine Fliesskommazahl.
Was Du eigentlich willst, ist, wie erwähnt, ein kopieren.
Das, dem geneigten Leser eines C-Buches, augenfällige Mittel, sollte die
Funktion "memcopy" sein. Jedoch wirst Du zutreffend einwenden, dass
diese ja ein
1
char*
als Argument erwartet; eine Erwartung die Du mit einem double oder float
nicht ohne weiteres erfüllen kannst.
Dazu gibt es die Möglichkeit, einen Zeiger auf die Fliesskommazahl zu
verwenden und ihn anstelle der Zahl selbst, umzuwandeln; in einen
Zeiger auf char.
1
doublea;
2
charbuffer[100]
3
4
memcpy(buffer,(char*)a,sizeof(double)
Du siehst wiederum einen cast-Ausdruck, was Dich etwas verwundern wird -
es aber nicht täte, falls Du ein C-Buch gelesen hättest -, denn ich habe
ja oben behauptet, dass es sich um eine Uminterpretation handelt. Der
Unterschied ist aber, dass hierbei ein Zeiger uminterpretiert wird,
eine Adresse, die durch die Uminterpretation aber immer noch auf die
selbe Speicherstelle zeigt.
Würde man nun, mit dem, auf dass dieser Zeiger zeigt rechnen wollen, in
dem man seinen Wert mit dem '*'-Operator ermittelt, so ergäbe sich ein
char - wieder das nicht gewollte. Aber Du willst lediglich kopieren -
und das erfordert keine Uminterpretation des Inhaltes; der Bitfolge auf
die der Zeiger zeigt.
Ich hoffe Dir damit eine Idee in den Kopf gepflanzt zu haben, die Dich
weiter bringt.
Klaus schrieb:> Das, dem geneigten Leser eines C-Buches, augenfällige Mittel, sollte die> Funktion "memcopy" sein. Jedoch wirst Du zutreffend einwenden, dass> diese ja ein> char *>> als Argument erwartet; eine Erwartung die Du mit einem double oder float> nicht ohne weiteres erfüllen kannst.
Das mag beim K&R C richtig gewesen sein.
memcpy erwartet mittlerweile lediglich void*
( http://www.cplusplus.com/reference/cstring/memcpy/ )
In C kann jeder Datenzeiger ohne cast in einen void-Zeiger gewandelt
werden. Und umgekehrt gilt es ebenso.
Peter schrieb:> Hier steht jeder Buchstabe für ein Byte. Da ein double meines Wissens> 64bit hat also 8*a. Ein 32bit int braucht 4 Byte.
NEIN! Die Länge der Datentypen ist bis auf char nicht definiert... das
hast du doch schon in der ersten Antwort auf dein Posting lesen können.
:-/
Klaus schrieb:> double a;> char buffer[100]>> memcpy (buffer, (char *) a, sizeof(double)>> Du siehst wiederum einen cast-Ausdruck, was Dich etwas verwundern wird -> es aber nicht täte, falls Du ein C-Buch gelesen hättest
Du verdrehst da 2 tatsachen: wer ein C-Bucht gelesen hat wundert sich
bei dem Code, und das C Buch braucht nich (nur) er.
Diese Ansamlung von Fehlern! Zumindest etwas besser wäre:
1
doublea;
2
charbuffer[sizeof(a)];// Semikolon am schluss nicht vergessen
3
4
memcpy(buffer,&a,sizeof(a));// Schluessende Klammern, Semikolon und Referenzieren von a nicht vergessen
Wobei das aber das ursprungliche Problem nicht löst.
Hallo Peter,
du befindest dich im Bereich der Datenserialisierung:
https://en.wikipedia.org/wiki/Serialization
Es gibt viele Möglichkeiten deine Problemstellung zu lösen. Eine sehr
einfache Möglichkeit, die oben bereits genannt wurde, ist per memcpy().
Also bspw.
Das funktioniert gut solange beide Seiten die gleiche
Hardwarearchitektur und Compiler nutzen. Möchte man davon unabhängig
sein, gibt es eine Vielzahl vom weiteren Möglichkeiten. Ich bspw.
benutze gern die Basic Encoding Rules
(https://en.wikipedia.org/wiki/Basic_encoding_rules).
Edit: Spelling
Daniel A. schrieb:> Klaus schrieb:>> double a;>> char buffer[100]>>>> memcpy (buffer, (char *) a, sizeof(double)>>>> Du siehst wiederum einen cast-Ausdruck, was Dich etwas verwundern wird ->> es aber nicht täte, falls Du ein C-Buch gelesen hättest> Du verdrehst da 2 tatsachen: wer ein C-Bucht gelesen hat wundert sich> bei dem Code, und das C Buch braucht nich (nur) er.> Diese Ansamlung von Fehlern! Zumindest etwas besser wäre:
Das war Absicht. Aber das kann ich natürlich nicht beweisen.
Es geht darum, das der TO tatsächlich liest. Das Gegenteil hätte sich
bei einer Nachfrage erwiesen.
>>
1
>doublea;
2
>charbuffer[sizeof(a)];// Semikolon am schluss nicht vergessen
3
>
4
>memcpy(buffer,&a,sizeof(a));// Schluessende Klammern, Semikolon und
5
>Referenzierenvonanichtvergessen
6
>
>> Wobei das aber das ursprungliche Problem nicht löst.
Nun wundere ich mich aber - wenn auch nur gelinde. Das löst das Problem
durchaus.
armer ingenieur schrieb:> typedef struct paket_s {> int32_t i;> float f;> double d;> } paket_t;
Achtung! Als Datenpaket ist eine struct eher ungeeignet, weil da noch
Padding-Bytes dazwischen sein können. Die Lösung von S. J. ist viel
brauchbarer.
Peter D. schrieb:> static inline float mkp_fl( uint32_t val ) // make pointer from> uint32_t to float> {> union{> uint32_t u32;> float f;> } out;> out.u32 = val;> return out.f;> }
Bitte verbreitet diese Lösung nicht mehr! Von einer Union darf nur der
Member gelesen werden, der zuletzt beschrieben worden ist. Alles andere
ist Undefined Behavior, also vielleicht geht's, aber drauf verlassen
kann man sich nicht.
tictactoe schrieb:> Bitte verbreitet diese Lösung nicht mehr! Von einer Union darf nur der> Member gelesen werden, der zuletzt beschrieben worden ist.
Dann bliebe ja die einzig erlaubte Nutzung einer Union die
Doppelbelegung von RAM, was heutzutage niemand mehr braucht.
Solche Umwandlungen sind eh immer Target und Compiler abhängig, d.h. man
muß prüfen, ob sie richtig funktionieren.
Man kann sie ja in einen Guard kapseln, der bei nicht geprüftem
Compiler/Target ein #error ausgibt.
Ein universelle Lösung gibt es in C nicht.
Peter schrieb:> Nun möchte ich alle Werte die ich habe vorher Binärisieren
Die Frage ist, ob das überhaupt so eine gute Idee ist. Natürlich ist
eine Text-Darstellung von Zahlen länger, aber das ist ja nur von
Interesse, wenn die Übertragungskapazität knapp ist, also eher selten.
Dafür kann man eine ASCII-Meldung mit jedem Terminalprogramm mitlesen,
bei Binärkodierung ist man dagegen beim Debuggen und Testen ziemlich
aufgeschmissen. Es sei denn, man gehört zu den wenigen Menschen, die
Hex-Code direkt lesen können und im Kopf aus 16 Hex-Zeichen die
Double-Zahl bilden.
Die weitaus meisten Internet-Protokolle arbeiten mit Text, die Erfinder
haben sich da schon was dabei gedacht.
Georg
> Dann bliebe ja die einzig erlaubte Nutzung einer Union die> Doppelbelegung von RAM, was heutzutage niemand mehr braucht.
Och. "Niemand" ist ein bisschen hart. Da soll es noch ein paar
Anwendungen geben die auf Laufzeit und Speicher achten (müssen)...
> Solche Umwandlungen sind eh immer Target und Compiler abhängig, d.h. man> muß prüfen, ob sie richtig funktionieren.> Man kann sie ja in einen Guard kapseln, der bei nicht geprüftem> Compiler/Target ein #error ausgibt.
Das reicht in vielen Fällen aus. Insbesondere hier wo einige Anfänger
unterwegs sind muss man es nicht "unnötig" kompliziert machen.
> Ein universelle Lösung gibt es in C nicht.
Würde ich jetzt nicht so sagen.
https://en.wikipedia.org/wiki/Comparison_of_data_serialization_formats
Muss man halt gucken ob der Aufwand lohnt.
Georg schrieb:> Die weitaus meisten Internet-Protokolle arbeiten mit Text, die Erfinder> haben sich da schon was dabei gedacht.
Ich bin auch grad dabei, ein CAN-Protokoll von Binär auf Text
umzustellen, die Kollegen wollen das so.
Allerdings muß man bei float als Text auf Rundungsfehler achten, d.h.
Sender und Empfänger können zu unterschiedlichen Ergebnissen kommen.
Z.B. gab es ein Gerät, was eine Meßreihe aufnehmen sollte. Der PC kam
z.B. auf 10 Meßwerte, der Sensor meinte aber nur 9 schicken zu müssen
und schon gab es ein Timeout. Die Lösung war dann, daß der Sensor
erstmal die Anzahl schickte.
Peter D. schrieb:> Allerdings muß man bei float als Text auf Rundungsfehler achten, d.h.> Sender und Empfänger können zu unterschiedlichen Ergebnissen kommen.
Ja, wenn unterschiedliche Prozessoren Sprachen Libraries verwendet
werden. Es ist aber gerade die Aufgabe eines Protokolls, von so etwas
unabhängig zu sein, ein Grund mehr, eben keine Binärdaten zu übertragen.
Georg
Hallo nochmal,
danke für eure Tips. Ich habe es mal einen Ansatz mit memcpy versucht.
Das ging auch soweit. Trotzdem werde ich nochmal überlegen ob ich es nur
via Strings mache oder eine komplett andere Lösung anstrebe.
Gruß
Peter
Georg schrieb:> Peter D. schrieb:>> Allerdings muß man bei float als Text auf Rundungsfehler achten, d.h.>> Sender und Empfänger können zu unterschiedlichen Ergebnissen kommen.>> Ja, wenn unterschiedliche Prozessoren Sprachen Libraries verwendet> werden.
Nein, nicht nur dann.
> Es ist aber gerade die Aufgabe eines Protokolls, von so etwas> unabhängig zu sein, ein Grund mehr, eben keine Binärdaten zu übertragen.
Du hast aber gemerkt, daß es gerade um die Probleme geht, die daraus
entstehen, wenn man die Daten nach Text wandelt?
Peter D. schrieb:> tictactoe schrieb:>> Bitte verbreitet diese Lösung nicht mehr! Von einer Union darf nur der>> Member gelesen werden, der zuletzt beschrieben worden ist.>> Dann bliebe ja die einzig erlaubte Nutzung einer Union die> Doppelbelegung von RAM, was heutzutage niemand mehr braucht.
Ja, richtig.
Peter D. schrieb:> Solche Umwandlungen sind eh immer Target und Compiler abhängig, d.h. man> muß prüfen, ob sie richtig funktionieren.> Man kann sie ja in einen Guard kapseln, der bei nicht geprüftem> Compiler/Target ein #error ausgibt.>> Ein universelle Lösung gibt es in C nicht.
Da bin ich definitiv anderer Meinung. Ob die Lösung nun lautet
"Dezimalzahl in ASCII-Ziffern mit der führenden Ziffer zu erst,
abgeschlossen durch ein Leerzeichen" oder "Ziffernwerte zur Basis 256,
niederwertigste zuerst, genau 4 davon", ist ja nur Definitionssache.
Gemeinsam haben beide, dass das Format nicht durch Code festgelegt ist,
sondern "von außen", und dass sich für beide ein wohldefiniertes
C-Programm schreiben lässt.
tictactoe schrieb:> Achtung! Als Datenpaket ist eine struct eher ungeeignet, weil da noch> Padding-Bytes dazwischen sein können.
Schrieb ich ja. Man sollte dabei halt wissen was man macht. Aber das
gleiche Programm auf zwei gleichen Controllern sollte da grundsätzlich
nie ein Problem sein. Und im Endeffekt haben wir hier ein Industriegerät
laufen mit selbstgestricktem Ethernetstack inkl. TCP/IP und diversen
Bussystemen, in vielen Fällen aufbauend auf das Schema. Natürlich nicht
portabel zu irgendwelchen Exoten, aber es reicht für die üblichen
Verdächtigen, man sollte halt aufs Alignment achten um unnötiges Padding
zu vermeiden.
Georg schrieb:> Es ist aber gerade die Aufgabe eines Protokolls, von so etwas> unabhängig zu sein
Wenn es sich um eine reine Kommunikation untereinander handelt, oft zu
vernachlässigen...
Georg schrieb:> ein Grund mehr, eben keine Binärdaten zu übertragen.
Datendurchsatz ist einer dafür.
Peter D. schrieb:> Allerdings muß man bei float als Text auf Rundungsfehler achten, d.h.> Sender und Empfänger können zu unterschiedlichen Ergebnissen kommen.
Bei float muss muss immer auf Rundungsfehler und andere Artefakte
geachtet werden. Tut man das nicht, kann es auch ohne Datenübertragung
und mit den gleichen Tools, Optionen etc. unagenehm werden.
Z.B. gibt es FPUs, die intern mit 80 Bits rechnen, wo aber double mit
weniger Bits dargestellt ist. Wenn dann ein Zwischenergebnis in einem
64-Bit Register gespeichert wird, kann es andere Ergebnisse geben bei
gleicher Arithmetik.
Johann L. schrieb:> Peter D. schrieb:>> Allerdings muß man bei float als Text auf Rundungsfehler achten, d.h.>> Sender und Empfänger können zu unterschiedlichen Ergebnissen kommen.>> Bei float muss muss immer auf Rundungsfehler und andere Artefakte> geachtet werden.
Beim Rechnen ja, aber nicht bei der einfachen Übertragung, die ja an
sich verlustlos möglich sein sollte. Wenn man aber dafür von der Basis 2
auf die Basis 10 und zurück rechnen muss, entstehen durch die
Übertragung Verluste. Wenn dann noch die Anzahl der Dezimalstellen nicht
gegenüber dem Default stark erweitert wird, gibt es noch größere
Verluste.
> Z.B. gibt es FPUs, die intern mit 80 Bits rechnen, wo aber double mit> weniger Bits dargestellt ist. Wenn dann ein Zwischenergebnis in einem> 64-Bit Register gespeichert wird, kann es andere Ergebnisse geben bei> gleicher Arithmetik.
Wobei man sagen muß, daß diese "anderen" Ergebnisse eigentlich die
einzig ISO-C-konforme Umsetzung sind. Jeder Ausdruck - und damit auch
jedes Zwischenergebnis hat einen definierten Typ und muß mit der
Genauigkeit dieses Typs gerechnet werden. Das mag in anderen Sprachen
anders sein, aber hier geht es ja offenbar um C-Code.
>> Wobei das aber das ursprungliche Problem nicht löst.> Nun wundere ich mich aber - wenn auch nur gelinde. Das löst das Problem> durchaus.
Es geht um Daten, die über das Netzwerk versendet werden. Über das
Zielsystem konnte ich noch nichts finden. Sollte der TO 2 PCs haben, und
einer verwendet z.B. Big Endian wärend der andere Little Endian
verwendet, würde es schon nichtmehr funktionieren. Ein Netzwerkprotokoll
muss klar definiert sein, sonst ist es murks. Ich würde die Daten in
IEEE754 binary64 Big Endian codieren. Da ich im Internet keine
brauchbare Implementierung ohne Binaryhacks und mit brauchbaren
Lizenzbedingungen finden konnte, habe ich selbst einen geschrieben:
https://github.com/Daniel-Abrecht/IEEE754_binary_encoder
PS: Ich kann nicht garantieren, dass ich keinen Fehler gemacht habe, und
sorry, dass ich erst so spät Antworte: Es war nicht besonders einfach
diesen Code zu schreiben.
Daniel A. schrieb:> Es geht um Daten, die über das Netzwerk versendet werden. Über das> Zielsystem konnte ich noch nichts finden.
Zunächst get es darum, die Signatur zu bestimmen, und sich zu überlegen
wie man mit INF und (signaling(!)) NaN umgeht. Dazu gibt es
plattformabhängige Mittel, bei gcc etwa:
Damit kann man schon mal sicherstellen, dass der verwendete
(De)Serialisierer halbwegs auf die Binärgarstellungpasst, z.B. MIN_EXP,
MAX_EXP, MANT_DIG, HAS_QUIET_NAN etc.
Hier der Code aus einem Simulator der einen avr-gcc float als 32-Bit
unsigned aus dem AVR-Speicher bekommt und ins Host-format (double)
"deserialisiert". Beide Flattformen sind little.
1
// Decomposed IEEE 754 single
2
typedefstruct
3
{
4
intsign_bit;
5
// Mantissa without (23 bits) and with the leading (implicit) 1 (24 bits)