C++ Daten konvertieren

MarkP

Lieutenant
Registriert
Jan. 2016
Beiträge
751
C++ ist ja bekanntlich sehr strikt mit Datentypen, allerdings gibt es auch 1000 Wege zwischen den Typen zu konvertieren.

stringstream kann das besonders gut

typ x >> stringstream
typ y << stringstream

klappt in den allermeisten Fällen, zumindest solange man im Voraus weiss, dass das was da konvertiert wird in den Ziel-Typ rein passt.

Jetzt habe ich eine Verständnis-Frage, wo ich mir nicht sicher bin ob das so einfach geht.
Ich habe ein UINT, was nur Werte zwischen 1 und ca. 90.000 enthalten kann.
Ich möchte nun diesen Wert zu Typ void* konvertieren und später wieder zurück zu UINT, also nicht einen Pointer auf den Speicherplatz wo der Wert steht, sondern direkt, so dass der Pointer selbst den Wert enthält, ohne dass mich interessieren würde was an der Adresse steht auf die der Pointer zeigt und das Ganze sollte mit identischem sourcecode sowohl mit einem 32bit als auch mit einem 64bit Compiler funktionieren.

Mein Ansatz ist nun

Code:
UINT x = 1234;

void* y = reinterpret_cast<void*>(x);

UINT z = (UINT)y;

Der reinterpret_cast sollte ja auf beiden Systemen die Grösse anpassen und solange ich weiss, dass da immer nur Werte zwischen 1 und ca. 90.000 drin stehen sollte auch der cast zurück auf UINT gehen, oder übersehe ich da jetzt was?
 
Bei solchen Fragen postet meistens jemand anderes diesen Link:
https://de.wikipedia.org/wiki/XY_Problem

Bist du sicher, dass du den richtigen Weg gewählt hast?
Wenn du uns dein EIGENTLICHES Ziel bzw. Problem mitteilst kann man evtl sinnvoller helfen.
 
  • Gefällt mir
Reaktionen: KitKat::new(), T_55, BeBur und 2 andere
Mein eigentliches Ziel ist es einem Thread beim Create eine Variable zu übergeben, ohne den Umweg über alloc() und malloc() zu gehen, wo dann ein pointer auf ein struct übergeben wird.
Also das hier:
https://docs.microsoft.com/en-us/windows/win32/procthread/creating-threads
Da ist mir halt das WaitFor.... zu umständlich.

Aber wenn ich das hier aufschreibe, dann kommen mit letzter Sicherheit zuerst 100 Hinweise darauf, dass man das so nicht machen sollte, also lauter Antworten die die eigentliche Frage nicht beantworten.
Darum habe ich das Problem auf den wesentlichen Punkt, nämlich die Konvertierung von UINT zu void* und zurück reduziert.
Muss ich dann noch erwähnen, dass ich mein ganzes Projekt so gecoded habe, dass identischer Sourcecode sowohl mit 32bit als auch mit 64bit Compiler geht?
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Baal Netbeck
Kannst du erklaeren, was du mit der Konvertierung beim stringstream meinst? "<<" und ">>" sind Operatoren, die man fuer eigene Typen ueberladen kann, dabei findet aber keine Konvertierung statt.

void*, malloc() und Co. ist in der Regel sehr C-maessig und fuer aktuelles C++ nicht zeitgemaess. Hantierst du da permanent mit der Win32 API bzw. anderem Legacy-Kram rum oder geht's dir wirklich nur darum nen Thread zu erstellen und dem Daten zu uebergeben? Wenn letzteres, dann muss man leider einfach darauf hinweisen, dass man das so nicht (mehr) macht und du dir mal std::thread oder std::async anschauen solltest.

Kannst du mehr ueber dein Projekt und die Besonderheiten sagen? Warum musst du besonderes Augenmerk darauf legen, dass dein Code sowohl von 32- als auch 64-Bit Compilern uebersetzt werden kann?

Und jetzt noch zur eigentlichen Frage: Du moechtest im void* den Wert des uints speichern, allerdings nicht an der Adresse, auf die der Pointer zeigt, sondern "im Pointer direkt"? Das verstehe ich nicht, moechtest du die Adresse auf die der Pointer zeigt aendern, oder das, was an der Adresse, auf die der Pointer zeigt, steht?
 
  • Gefällt mir
Reaktionen: BeBur
MarkP schrieb:
Aber wenn ich das hier aufschreibe, dann kommen mit letzter Sicherheit ....... Antworten die die eigentliche Frage nicht beantworten.
m.c.ignaz schrieb:
Wenn letzteres, dann muss man leider einfach darauf hinweisen, dass man das so nicht (mehr) macht und du dir mal std::thread oder std::async anschauen solltest.

QED - Was zu beweisen war.
 
MarkP schrieb:
QED - Was zu beweisen war.
Na wenn du selbst schon weisst, dass du Mist machst, wozu laesst du dir das von uns nochmal sagen? ;)
Andere Schlussfolgerungen laesst dein Mangel an Informationen an diesem Punkt naemlich nicht zu.
Ich will gar nicht ausschliessen, dass du da auf dem richtigen Weg bist, aber Menschen verstehen gern, worum es geht, weil ihnen Dinge sonst sinnlos vorkommen.

Und weiter: Siehe Edit meines vorherigen Posts.
 
Der void Pointer selbst ist eine Speicheraddresse.
Diese kannst du nicht wählen und schon gar nicht in einem Zahlenbereich 1..90000
Wählen kannst du auf was er zeigt.
Aber vor allem, was hast du überhaupt vor?
 
Zuletzt bearbeitet:
Dass es ein X-Y-Problem gewesen ist, ist ja bereits geklärt.

Theoretisch müsste dein Code aus Post #1 richtig sein, aber Achtung: Wenn du die Variable x nicht auf dem Stack, sondern als Pointer erzeugen würdest, wäre es nicht mehr vom Standard gedeckt.

Kurzes Beispiel-Programm mit deinem Code:
https://onlinegdb.com/Hy52DvowP

Bitte beachte, dass ich dort "intptr_t" benutzt habe. Das ist ein Int, der garantiert einen Pointer halten kann. Sonst kann es sein, dass auf einem 64Bit-System der Pointer 64Bit hat, eine Integer aber trotzdem nur 32Bit, dann würde der (UINT)-Cast zu einem Compiler-Fehler bzw. Warnung führen: "loosing precision" und die müsste mit Flags ausgeschaltet werden. Ganz schlecht!


Wenn du aber Daten ohne Kopieren übergeben willst, gibt es, abgesehen von der Benutzung von std:threads etc, noch bessere Möglichkeiten. Schau dir dazu mal Moves in C++ an. Damit übergibst du Daten, auf die ein Pointer zeigt, ohne Kopie, bist aber sicher vor Memory Leaks, nullpointer dereferences etc. sicher. Halt den klassischen C-Bugs, die mit "dummen" Pointern kommen.

@Piak
Ein Pointer hat natürlich eine eigene Speicheradresse (wo ist der Pointer gespeichert) und eine Value. Die Value kannst du natürlich wählen. D.h. bei
C++:
int i = 2;               // Value von int ist 2, Adresse von int ist z.B.  0x7ffeffed3070
void* pointer = &i;      // Value von pointer ist  0x7ffeffed3070
schreibst du die Adresse der Variable i als Value in den Pointer.
Mit "pointer" greifst du dann auch wieder auf die Value von pointer zu ( 0x7ffeffed3070 ).
Mit "*pointer" greifst du auf die Value zu, die auf der Adresse der Value von Pointer ist (2)

Genauso geht
C++:
int i = 2;            // Value von int ist 2, Adresse von int ist z.B.  0x7ffeffed3070
void* pointer = i;       // Value von pointer ist 2
Was du nicht machen dürftest wäre ein "*pointer". Dann würdest du auf die Adresse zugreifen, die in der Value von pointer hinterlegt ist, also sowas wie 0x0000002. Das wird zu einem Crash führen, da du mit 99,9999% Wahrscheinlichkeit nicht auf diesen Speicher zugreifen kannst. Ein simpler Zugriff auf "pointer" ist natürlich zulässig.
 
Zuletzt bearbeitet:
Vielleicht muss ich es NOCH einfacher erklären?
Ein Pointer ist doch genau wie ein UINT effektiv nur eine Zahl, nur mit verschiedenen Wertebereichen und zu verschiedenem Zweck.
Wenn ich also den UINT Wert 1234 in einen Pointer "direkt" packen will, dann will ich einen Pointer der auf die Adresse 1234 zeigt, ohne dass mich interessieren würde was an Adresse 1234 steht, denn am Ziel schaue ich nicht auf diese Adresse sondern konvertiere ich nur wieder den Wert 1234 zurück zu UINT.
Der Wert wird dabei nicht verändert oder manupuliert oder was, er wird nur konvertiert und zurück konvertiert, was in jedem Falle funktioniert, solange 1234 im Wertebereich beider Typen liegt.
Das Ganze brauche ich halt nur deshalb vorübergehend als void*, weil CreateThread() nun mal nur eine Variable vom Typ LPVOID akzeptiert und weil LPVOID in 32bit und 64bit unterschiedliche Wertebereiche hat, brauche ich eine Konvertierung die mit identischem Sourcecode auf beiden Compilern geht.
 
Zuletzt bearbeitet:
Wie ich schrieb, das geht nicht! Ein Pointer enthält eine Adresse. Keine frei wählbare Zahl
 
Achso? Eine Adresse ist also keine Zahl und die Adresse die ich addressieren will ist nicht frei wählbar?
Selbstverständlich geht das, es ist sogar weit verbreiteter Standard in der multithreaded Socket-Programmierung, dass man im LPVOID vom CreateThread "direkt" den Socket übergibt.

Code:
SOCKET Socket = socket(AF_INET,SOCK_STREAM,0);
// usw., usw.
SOCKET SubSocket = accept(Socket,(SOCKADDR*)&sinRemote,&nAddrSize);

DWORD nThreadID;
CreateThread(0,0,AcceptFunc,(LPVOID)SubSocket,0,&nThreadID);

DWORD WINAPI  AcceptFunc(LPVOID data)
{
SOCKET Socket = (SOCKET)data;
usw.

Der einzige Unterschied zu meiner Frage ist, dass SOCKET und LPVOID in 32 und 64bit immer denselben Wertebereich haben und darum einfach gecastet werden können, wahrend ich beim UINT mit reinterpret_cast arbeiten muss.
 
@Piak
Ein Pointer-Objekt ist erstmal nur ein Stück Speicher mit der Größe eines Zeigers auf dem Zielsystem. Dort kann man jede passende Zahl reinschreiben. Wenn die dort drin stehende Zahl jedoch irgendetwas Beliebiges ist, stürzt das Programm beim Zugriff auf den Pointer mit ACCESS_VIOLATION o.ä. ab. Die Zahl, die im Pointer-Speicher selbst steht, kann ich aber noch abrufen.
D.h. es auf diese Weise zu machen ist total sinnlos, aber trotzdem möglich.
 
Wie nun bereits mehrfach erwähnt, Antworten die die Frage nicht beantworten, sondern mir erklären wollen warum man das so nicht machen sollte kenne ich schon, darum hatte ich in der Eingangsfrage die ganzen Erklärungen wozu ich das brauche weggelassen, hat mir aber offensichtlich nicht genutzt.

Nochmal: Ich will überhaupt nicht schauen was an der Adresse steht auf die der Pointer zeigt, darum kann es da auch keine ACCESS_VIOLATION geben.
Ich kann doch auch völlig ohne Probleme machen

Code:
int val = 42;
int* pVal = &val;
int k = *pVal; // k == 42
int pValValue = reinterpret_cast<int>( pVal ); // pValValue = address of val

genauso kann ich auch völlig ohne Probleme machen

Code:
int* pVal = 1234;
int pValValue = reinterpret_cast<int>( pVal ); // pValValue = 1234

ich darf nur nicht auf die Adresse 1234 zugreifen, weil die wahrscheinlich nicht zu meinem Programm gehört.
 
Zuletzt bearbeitet:
MarkP schrieb:
Der einzige Unterschied zu meiner Frage ist, dass SOCKET und LPVOID in 32 und 64bit immer denselben Wertebereich haben und darum einfach gecastet werden können, wahrend ich beim UINT mit reinterpret_cast arbeiten muss.

Du beschreibst hier ein reines C Problem.
In C++ brauchst du solche Klimmzüge in der Regel nicht da du dort std::thread verwenden würdest. Dort besteht dieses Problem nicht da du dort frei in der Funktionssignatur bist.

Um jetzt den Bogen zu schließen, ja dein C Code funktioniert in der Regel, solange sizeof(LPVOID) >= sizeof(anderer Typ).
 
  • Gefällt mir
Reaktionen: T_55 und MarkP
Hach @wayne_757 ich danke dir, mehr wollte ich gar nicht wissen.

Ich weiss wohl, dass ich überwiegend reines C code, aber halt doch als C++, weil ich dann doch einige Dinge mache die in C so nicht machbar sind.
Dabei gebe ich gerne zu, dass ich schon lange nicht mehr weiss, welcher Teil noch zu reinem C gehört und wo C++ anfängt.
 
Um das ganze jetzt nochmal im Kontext von C++ zu beantworten: Nein, das was du machen willst ist mit standardkonformem C++ nicht möglich. cppreference sagt zu reinterpret_cast:
https://en.cppreference.com/w/cpp/language/reinterpret_cast schrieb:
3) A value of any integral or enumeration type can be converted to a pointer type. A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed to have its original value, otherwise the resulting pointer cannot be dereferenced safely (the round-trip conversion in the opposite direction is not guaranteed; the same pointer may have multiple integer representations) The null pointer constant NULL or integer zero is not guaranteed to yield the null pointer value of the target type; static_cast or implicit conversion should be used for this purpose.
Hier auch noch die entsprechende Frage auf stackoverflow mit Zitat aus dem Standard.

Das Verhalten ist also implementation-defined, das bedeutet es hängt von deinem Compiler ab. Es ist aber anzunehmen, dass dein Compiler dir erlaubt folgendes zu machen:
C++:
std::uintptr_t value = 1234;
void* ptrValue = reinterpret_cast<void*>(value);
std::uintptr_t copiedValue = reinterpret_cast<std::uintptr_t>(ptrValue);
Auf Windows mit MSVC wird das immer funktionieren. Es wird zusätzlich auch immer funktionieren, solange sizeof(void*) >= sizeof(integer_t) für einen beliebigen integer_t deiner Wahl. Wobei du hier aufpassen solltest, weil der reinterpret_cast keine narrowing/widening conversion macht, das heißt du solltest deinen integer zusätzlich mit static_cast auf die richtige größe bringen. Ansonsten bekommst du vom Compiler einen Error/Warning.

Dir sollte allerdings bewusst sein, dass das was du machen willst ein ziemlicher Hack ist, und definitiv nicht als best-practices durch geht. Und auch wenn du es offensichtlich nicht gerne hörst, std::thread wäre die bessere Wahl, außer du arbeitest in irgend einer Legacy Codebase wo es schon unzählige CreateThread calls gibt.

Gruß
BlackMark
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: kuddlmuddl und T_55
Kann nur allen zustimmen die zu std::thread raten. Die Daten einfach an die Thread-Funktion weitergeben und fertig.
std::move ist auch damit kombinierbar so wechselt der Besitz an den Thread sofern die Daten so groß sind das es sich überhaupt lohnt. Aber auch smart Pointer zb std::shared_ptr kann man an den Thread übergeben dann ist er Mitbesitzer der Daten und es können mehrere Threads (synchronisierung nicht vergessen) an den Daten naschen.
 
BlackMark schrieb:
....., außer du arbeitest in irgend einer Legacy Codebase wo es schon unzählige CreateThread calls gibt.

Da ist schon "Codebase" drastisch übertrieben.
Ich bin ein Hobby-Coder der noch nie irgendein Programm verkauft hat, sondern immer nur für den eigenen Bedarf und für umsonst macht.
Mir ist es völlig egal ob ich 100x länger an einem Projekt sitze weil ich so nahe es geht an der untersten Basis code und darum drastisch viel mehr zu tippen habe, denn es geht mir gar nicht so sehr um ein möglichst schnell erreichtes Endergebnis, mir geht es darum ein Hobby zu haben bei dem ich nun schon über 20 Jahre an einem einzelnen Projekt code.
Ich betreibe seit fast 20 Jahren einen Winzig-Gameserver für Backgammon, auf dem nur meine Frau und eine Handvoll Freunde spielen, meines Wissens weltweit der einzige Gameserver der für die User absolut kostenlos UND auch absolut werbefrei ist.
Logisch ist die "Codebase" davon das, was man vor ca. 25 Jahren in kostenlosen online-Tutorials finden konnte und an dieser Basis hat sich bis heute nicht viel geändert.
Ich bin regelrecht stolz darauf eine Client-Software für meine Handvoll User zu haben, die ab Win2000 völlig unverändert auf jeder beliebigen Version von Windows läuft, mit identischem Sourcecode in 32 und 64bit.
Dass der Code so gut wie gar keinen Code-Standards folgt liegt daran, dass ich mich noch nie überhaupt damit beschäftigt habe was denn wohl die Standards wären, muss ich ja auch nicht, denn ich will das Projekt weder verkaufen noch sonst irgendwie Geld damit verdienen.
 
MarkP schrieb:
Dass der Code so gut wie gar keinen Code-Standards folgt liegt daran, dass ich mich noch nie überhaupt damit beschäftigt habe was denn wohl die Standards wären, muss ich ja auch nicht, denn ich will das Projekt weder verkaufen noch sonst irgendwie Geld damit verdienen.
Du musst aber zwischen Code-Standards (aka coding guidelines / best practices) und standardkonformem Code unterscheiden. Und keines von beidem hat etwas damit zu tun ob es ein kommerzielles Produkt ist oder nicht. Dein Code sollte standardkonform sein, damit er garantiert funktioniert, und nicht manchmal crashed oder mit einem anderen/neuen Compiler plötzlich nicht mehr funktioniert. Und die Coding-Guidelines existieren um dir das Leben leichter zu machen, weil sie verhindern sollen, dass du dir selbst in den Fuß schießt.

MarkP schrieb:
Da ist mir halt das WaitFor.... zu umständlich.
Und es gibt offensichtlich immer noch ein XY-Problem. Wenn ich das richtig verstehe, geht es dir eigentlich darum, dass du auf der Seite wo dein Thread gespawned wird nicht warten willst, aber trotzdem Daten an den Thread übergeben willst, ohne dass diese out-of-scope gehen.
Was du also eigentlich willst ist einen Ownership-Transfer der Daten vom Ersteller an den Thread selber. Wenn du das korrekt und standardkonform machen willst, bleibt dir aber nur der Weg über den Heap. Zum Beispiel so:

C++:
DWORD WINAPI threadFn(LPVOID lpParam) {
    std::unique_ptr<UINT> ptrData(static_cast<UINT*>(lpParam));
}

// Creator
{
    CreateThread(nullptr, 0, threadFn, new UINT{1234}, 0, nullptr);
}

Gruß
BlackMark
 
Zurück
Oben