signed char / unsigned char / char



  • Hallo zusammen,

    ich habe ein kleines Problem mit chars.
    Ich benutze eine Klasse packet von SFML, die für Netzwerkkommunikation gedacht ist.
    Folgende Methoden werden bereitgestellt:

    Packet & 	operator>> (bool &data)
    Packet & 	operator>> (Int8 &data)
    Packet & 	operator>> (Uint8 &data)
    Packet & 	operator>> (Int16 &data)
    Packet & 	operator>> (Uint16 &data)
    Packet & 	operator>> (Int32 &data)
    Packet & 	operator>> (Uint32 &data)
    Packet & 	operator>> (float &data)
    Packet & 	operator>> (double &data)
    Packet & 	operator>> (char *data)
    Packet & 	operator>> (std::string &data)
    Packet & 	operator>> (wchar_t *data)
    Packet & 	operator>> (std::wstring &data)
    Packet & 	operator>> (String &data)
    

    Int8 und Uint8 sind dabei typedefs, die bei mir das sind:

    typedef signed char sf::Int8;
    typedef unsigned char sf::Uint8;
    

    Jetzt wollte ich das benutzen um etwas empfangenes in eine char-variable zu schreiben:

    int i;
    packet >> i; //klappt super
    char c;
    packet >> c; //klappt nicht!
    unsigned char c2;
    packet >> c2; //klappt
    signed char c3;
    packet >> c3; //klappt
    

    Was hat es mit unsigned char, signed char und char auf sich, warum ist char hier mit keinem von beidem kompatibel?

    P.S.: Bitte nicht verschieben, es geht hier nicht um Netzwerk oder Spieleprogrammierung, sondern um die Sprache C++.



  • In C++ is nicht spezifiziert, ob ein normales char signed ist oder nicht - das ist ein eigenständiger Datentyp.

    (PS: "klappt nicht" ist eine sehr präzise Fehlerbeschreibung ;))



  • error C2678: Binärer Operator '>>': Es konnte kein Operator gefunden werden, der einen linksseitigen Operanden vom Typ 'sf::Packet' akzeptiert (oder keine geeignete Konvertierung möglich)
    1> c:\libraries\sfml 2.0\include\sfml\network\ipaddress.hpp(269): kann 'std::istream &sf::operator >>(std::istream &,sf::IpAddress &)' sein [bei der Verwendung der argumentbezogenen Suche gefunden]
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(177): oder "sf::Packet &sf::Packet::operator >>(bool &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(178): oder "sf::Packet &sf::Packet::operator >>(sf::Int8 &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(179): oder "sf::Packet &sf::Packet::operator >>(sf::Uint8 &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(180): oder "sf::Packet &sf::Packet::operator >>(sf::Int16 &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(181): oder "sf::Packet &sf::Packet::operator >>(sf::Uint16 &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(182): oder "sf::Packet &sf::Packet::operator >>(sf::Int32 &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(183): oder "sf::Packet &sf::Packet::operator >>(sf::Uint32 &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(184): oder "sf::Packet &sf::Packet::operator >>(float &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(185): oder "sf::Packet &sf::Packet::operator >>(double &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(186): oder "sf::Packet &sf::Packet::operator >>(char *)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(187): oder "sf::Packet &sf::Packet::operator >>(std::string &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(188): oder "sf::Packet &sf::Packet::operator >>(wchar_t *)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(189): oder "sf::Packet &sf::Packet::operator >>(std::wstring &)"
    1> c:\libraries\sfml 2.0\include\sfml\network\packet.hpp(190): oder "sf::Packet &sf::Packet::operator >>(sf::String &)"
    1> bei Anpassung der Argumentliste '(sf::Packet, char)'
    1>
    1>Fehler beim Erstellen

    Ich benutze MSVC2010.

    Das Problem ist, dass ich ein char* array versendet habe, das nicht 0-terminiert ist und ich das auf der anderen seite empfangen will.
    Ich habe zuerst die länge als int gesendet und dann das char* array.

    Beim empfangen wollte ich dann in einer schleife die einzelnen chars auslesen.
    Wenn ich jetzte einfach signed oder unsigned nehme, kann es dann sein, dass die werte falsch sind?

    Das char * array ist übrigens kein string, sondern speichert "Daten".

    Edit: Was ist, wenn ich in ein signed oder unsigned char einlese und dann in char caste, kommt dann immer der "richtige" wert bei raus?

    Edit2:
    Mein Versuch sieht jetzt so aus:

    GameStateMessage::GameStateMessage(sf::Packet& packet)
    {
    	int size;
    	packet >> size;
    	if(size == 0)
    	{
    		return;
    	}
    	char * data = new char[size];
    	for(int i = 0; i < size; ++i)
    	{
    		unsigned char c;
    		packet >> c;
    		data[i] = static_cast<char>(c);
    	}
    	data_.Append(data, size);
    	delete[] data;
    }
    

    Wird das funktionieren? (Auch portierbar?)



  • Die Größe der char-Typen ist gleich (und per Definition gleich 1), also dürfte es auch kein Problem sein, unsigned char zu verwenden (solange du niht unbedingt das Vorzeichen benötigst). Ich würde evenuell einen Lese-Operator für vector<char> verwenden, der die komplette Verarbeitung selber übernimmt.

    PS: Bei deinen operator-Überladungen für char* und wchar_t* solltest du übrigens aufpassen, daß du nicht den verfügbaren Speicher überschreitest.



  • CStoll schrieb:

    Die Größe der char-Typen ist gleich (und per Definition gleich 1), also dürfte es auch kein Problem sein, unsigned char zu verwenden (solange du niht unbedingt das Vorzeichen benötigst). Ich würde evenuell einen Lese-Operator für vector<char> verwenden, der die komplette Verarbeitung selber übernimmt.

    PS: Bei deinen operator-Überladungen für char* und wchar_t* solltest du übrigens aufpassen, daß du nicht den verfügbaren Speicher überschreitest.

    Kannst du beides nochmal ein bischen ausführen bitte?
    Hab das nicht völlig verstanden.
    Also erstmal wo/wie du da einen vector<char> verwenden willst und dann, an welcher stelle der verfügbare Speicher überschritten werden kann.



  • Zum ersten: Manuelle Speicherverwalung is immer etwas heikel, deswegen würde ich anstelle deiner Konstrukion mit new lieber einen vecor<> verwenden (btw, was ist eigentlich "data_"? Eventuell könntest du auch jedes gelesene Zeichen direkt dort reinpacken).

    Zum zweiten: Wenn deine Funktion nur einen char* übergeben bekommt, hast du keine Möglichkeit festzustellen, wie groß der Speicherbereich is, der ab dieser Adresse zur Verfügung steht (oder ob du überhaupt dort reinschreiben darfst*). Wenn du da mehr Daten shreibst als rein passen, überschreibst du Daten, die an anderer Stelle noch wichtig werden können (das Ergebnis ist dann undefiniert).

    * C++ erlaubt es auch, String-Literale als char* anzusprechen (wovor einige Compiler warnen). Diese werden allerdings in einem schreibgeschützten Bereich gelager, so daß es beim Schreiben zu einer Access Violation kommt.



  • Danke für die Hilfe.
    Das war mir alles schon bekannt, ich hatte nur gedacht, dass du evtl. was anderes / spezielleres gemeint hattest.
    Ich habs jetzt wie ich oben geschrieben habe gemacht und es funktioniert.

    Ich werd das char array das jedes mal erzeugt wird vermutlich durch einen
    static vector<char>
    ersetzen
    und dann jedes mal
    resize(size);
    machen, dann muss nicht mehr so oft neuallokiert werden. Die Methode wird über 100mal /sek aufgerufen. Häufiges neuallokieren muss ja nicht unbedingt sein, erstens weils dauert und wegen fragmentierung.
    Das mit dem static sollte doch keine probleme geben, wenn ich nur einen Thread benutze, der dadrauf zugreift, oder?

    Edit:
    data_ ist auch ein packet.
    Ich möchte quasi einen teil eines pakets in ein anderes übertragen.



  • Lass den std::vector einfach lokal (ohne static). 100x pro Sekunde ist nix, das spürst du nicht.

    Oder bau den Code so um, dass du nen Stack-Buffer verwendest. Die max. zu erwartende Paketgrösse wird ja bekannt sein. Dann machst du einen Stack-Buffer zweig, der bei Paketen <= N genommen wird, und einen std::vector Zweig, der bei > N genommen wird.

    Oder du hängst die Daten Stückweise an "data_" an, wenn mehr als N Byte daherkommen.

    "static" halte ich hier auf jeden Fall für grossen Pfusch. Wenn du garantieren kannst dass keine zwei Threads gleichzeitig da reinrufen, und die Funktion auch nicht rekursiv aufgerufen werden kann, dann ist es zwar theoretisch OK. Aber trotzdem etwas, was ich nie machen würde. Gerade in "low leveligen" Klassen. Und "GameStateMessage" klingt mir ziemlich "low levelig".

    Oder du kopierst die Daten einfach direkt aus dem Paket raus. Mit sf::Packet::GetData() kommst du ja an die Daten ran. Wenn das sf::Packet ausser dem String nichts anderes enthält, dann beginnen die Nutzdaten ja immer ab sf::Packet::GetData() + sizeof(int). -> easy

    Oder du änderst dein Programm, so dass die Länge gleich gar nicht nochmal redundant mitgeschickt wird, da SFML das sowieso übernimmt. Mit sf::Packet::GetDataSize() kannst du die Länge dann abfragen.

    Oder, falls die Pakete doch noch andere Dinge enthalten, änderst du sf::Packet entsprechend ab, so dass du den Read-Pointer auslesen und versetzen kannst. Dann kannst du die Daten auch ohne weitere Umwege rauskopieren.



  • Q schrieb:

    Was hat es mit unsigned char, signed char und char auf sich, warum ist char hier mit keinem von beidem kompatibel?

    char, signed char und unsigned char sind 3 verschiedene Typen. Mit anderen Worten:
    is_same<char, signed char>::value --> false
    is_same<char, unsigned char>::value --> false

    Bei allen anderen signed-Typen kann man das signed auch weglassen. Das ist dann immer noch derselbe Typ. Also:
    is_same<int, signed int>::value --> true

    Und ob char jetzt vorzeichenbehaftet ist oder nicht, ist "implementation-defined". Der Standard schreibt vor, dass der Wertebereich von char entweder dem von signed char oder dem von unsigned char entsprechen muss und dass der Compilerhersteller das dokumentieren muss.



  • SFML ist an dieser Stelle einfach fehlerhaft (und nicht nur da). Zum Beispiel wird wahrscheinlich auch long nicht funktionieren, weil nur int überladen ist. long double und long long (nicht Standard, können aber die meisten Compiler) fehlen, w/char/_t * ist unlogisch, das Verhalten für bool ist unklar (denn die Größe von bool ist Compiler-abhängig).
    Außerdem kann man nicht angeben, wie die String-Länge serialisiert werden soll. Eine Beschränkung der Länge auf genau 32 Bit ist unnötig und Platzverschwendung.
    In einer vernünftigen Bibliothek würde man die Operatoren für alle möglichen Typen überladen und nicht diese schwachsinnigen typedef s verwenden. Portierbare typedef s für den Benutzer gibts in Boost oder <cstdint> .



  • hustbaer schrieb:

    Oder du kopierst die Daten einfach direkt aus dem Paket raus. Mit sf::Packet::GetData() kommst du ja an die Daten ran. Wenn das sf::Packet ausser dem String nichts anderes enthält, dann beginnen die Nutzdaten ja immer ab sf::Packet::GetData() + sizeof(int). -> easy

    Es enthält leider noch was anderes... Und den ReadPointer kann ich ohne Modifikationen an SFML nicht auslesen.

    hustbaer schrieb:

    Oder du änderst dein Programm, so dass die Länge gleich gar nicht nochmal redundant mitgeschickt wird, da SFML das sowieso übernimmt. Mit sf::Packet::GetDataSize() kannst du die Länge dann abfragen.

    Ich benutze sf::Packet nur zum umwandeln von daten <-> byte/char, das verschicken mache ich mit boost::asio, also wird die länge nicht von selbst mitgeschickt, was bei UDP auch unnötig wäre, weil das afaik schon vom UDP-Protokoll gemacht wird.

    hustbaer schrieb:

    Oder, falls die Pakete doch noch andere Dinge enthalten, änderst du sf::Packet entsprechend ab, so dass du den Read-Pointer auslesen und versetzen kannst. Dann kannst du die Daten auch ohne weitere Umwege rauskopieren.

    Das habe ich mir auch schon überlegt, aber mir gefällt es nicht, SFML zu ändern, dann muss ich bei jedem neuen release wieder meine eigenen Änderungen reintun usw., das wollte ich wenn möglich vermeiden.

    TyRoXx schrieb:

    SFML ist an dieser Stelle einfach fehlerhaft (und nicht nur da). Zum Beispiel wird wahrscheinlich auch long nicht funktionieren, weil nur int überladen ist. long double und long long (nicht Standard, können aber die meisten Compiler) fehlen, w/char/_t * ist unlogisch, das Verhalten für bool ist unklar (denn die Größe von bool ist Compiler-abhängig).
    Außerdem kann man nicht angeben, wie die String-Länge serialisiert werden soll. Eine Beschränkung der Länge auf genau 32 Bit ist unnötig und Platzverschwendung.
    In einer vernünftigen Bibliothek würde man die Operatoren für alle möglichen Typen überladen und nicht diese schwachsinnigen typedef s verwenden. Portierbare typedef s für den Benutzer gibts in Boost oder <cstdint> .

    Ich werd Laurent (Entwickler von SFML) mal Bescheid sagen, vll ändert er da ja mal was.
    SFML werd ich aber trotzdem weiterverwenden solange ich keine vernünftige Alternative habe. Und bisher war ich mit SFML sehr zufrieden.
    Edit: Eine Anfrage wegen char hab ich mal gemacht.
    Int64 / long long wurde schonmal angefragt, hatte er aber nicht gemacht, weil er meinte, dass dafür htonl nicht funktioniert und es kaum jemand braucht.



  • nur zum umwandeln von daten <-> byte/char

    oioioi, dann schreib dir doch einfach ne eigene kleine klasse die das macht. und besser macht als sf::Packet.
    die 2 1/2 zeilen sollte dir das wert sein.



  • Ich will ja auch sowas wie endianess beachten und dann ist es nicht mehr ganz so trivial.

    Dann muss man evtl. noch bytes swappen (oder htonl/htons etc. bemühen) usw..
    Aber drüber nachgedacht hatte ich auch schonmal, evtl. mach ich das.

    Hat gerade wer code zum htonl selbst machen (bytes swappen) parat?



  • template <typename T>
    void swap_endianess(T* src, T* dest)
    {
        char* s = reinterpret_cast<char*>(src);
        char* d = reinterpret_cast<char*>(dest);
    
        for(int i = 0; i < sizeof(T); ++i)
            d[i] = s[length - i - 1];
    }
    

    Verwendung:

    int i = 42;
    int j;
    swap_endianess(&i, &j);
    


  • Danke dafür.

    Dann fehlen aber nochn paar Sachen.
    Also erstmal muss man die eigene Endianess rausfinden.
    Und wie ist das mit float/double, müssen die auch geswapped werden?
    Bei SFML werden die NICHT geswapped.



  • bool little_endian()
    {
        int i = 1;
        return *reinterpret_cast<char*>(&i) == 1;
    }
    

    Außerdem frage ich mich, wie ich nur so eine hässlichen Umwandlungs-Funktion schreiben konnte. Hier nochmal eine schönere:

    template <typename T>
    T swap_endianess(T t)
    {
        char* data = reinterpret_cast<char*>(&t);
        std::reverse(data, data + sizeof(T));
        return t;
    }
    

    bzw hier inplace:

    template <typename T>
    void swap_endianess(T& t)
    {
        char* data = reinterpret_cast<char*>(&t);
        std::reverse(data, data + sizeof(T));
    }
    

    😃


  • Mod

    Im Prinzip ist das Ganze in dieser Form ohnehin Unfug: Ein T ist im Allgemeinen nach einem swap kein T mehr.



  • camper schrieb:

    Im Prinzip ist das Ganze in dieser Form ohnehin Unfug: Ein T ist im Allgemeinen nach einem swap kein T mehr.

    Da das nur mit PODs so funktioniert, ist er das schon. 😉
    Wobei ich die Typen, die ich da durchgejagt habe, auch nicht mehr anrühren würde. Ich verwende diese Funktionen zum Konvertieren nach Big Endian auch für floats und doubles, da ich Big Endian senden muss.



  • Man könnte die Bytes auch direkt in umgekehrter Reihenfolge in die Ausgabe schreiben.



  • TyRoXx schrieb:

    Man könnte die Bytes auch direkt in umgekehrter Reihenfolge in die Ausgabe schreiben.

    Was sich super auf die Datenrate auswirkt.


Log in to reply