Konvertieren eines Zeigers in eine Ganzzahl

85

Ich versuche, einen vorhandenen Code an eine 64-Bit-Maschine anzupassen. Das Hauptproblem besteht darin, dass der vorherige Codierer in einer Funktion ein void * -Argument verwendet, das in der Funktion selbst in einen geeigneten Typ konvertiert wird. Ein kurzes Beispiel:

void function(MESSAGE_ID id, void* param)
{
    if(id == FOO) {
        int real_param = (int)param;
        // ...
    }
}

Auf einem 64-Bit-Computer erhalte ich natürlich die folgende Fehlermeldung:

error: cast from 'void*' to 'int' loses precision

Ich möchte dies korrigieren, damit es auf einem 32-Bit-Computer immer noch und so sauber wie möglich funktioniert. Irgendeine Idee ?

PierreBdR
quelle
5
Ich weiß, dass dies ein alter Beitrag ist, aber es scheint, dass die akzeptierte Antwort nicht ganz richtig ist. Ein konkretes Beispiel dafür, dass es size_tnicht funktioniert, ist der segmentierte i386-Speicher. Obwohl eine 32-Bit-Maschine, sizeofkehrt 2für zurück size_t. Die Antwort von Alex unten scheint korrekt zu sein. Alex 'Antwort und uintptr_tfunktioniert fast überall und ist jetzt Standard. Es bietet eine C ++ 11-Behandlung und sogar die C ++ 03-Header-Schutzvorrichtungen.
JWW

Antworten:

68

Verwenden Sie intptr_tund uintptr_t.

Um sicherzustellen, dass es portabel definiert ist, können Sie folgenden Code verwenden:

#if defined(__BORLANDC__)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
    typedef unsigned long uintptr_t;
#elif defined(_MSC_VER)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
#else
    #include <stdint.h>
#endif

Legen Sie das einfach in eine .h-Datei und fügen Sie es dort ein, wo Sie es benötigen.

Alternativ können Sie die Microsoft-Version der stdint.hDatei von hier herunterladen oder eine tragbare Version von hier verwenden .

Milan Babuškov
quelle
Unter stackoverflow.com/questions/126279/… finden Sie Informationen dazu, wie Sie eine stdint.h erhalten, die mit MSVC (und möglicherweise Borland) funktioniert.
Michael Burr
2
Beide Links kaputt!
Antonio
Diese Antwort bezieht sich auf C, aber die Sprache ist mit C ++ gekennzeichnet, daher ist sie nicht die Antwort, nach der ich gesucht habe.
HaseeB Mir
@HaSeeBMiR Eine geeignete Lösung besteht darin <cstdint>, zu der entsprechenden zu wechseln oder diese herunterzuladen, cstdintwenn Sie eine herunterladen stdint.h.
Justin Time - Stellen Sie Monica
1
@HaSeeBMiR Der einzige Grund, warum sich die Antwort auf C anstelle von C ++ bezieht, besteht darin, dass anstelle des entsprechenden C ++ - Headers ein C-Header verwendet wird. Der C-Präprozessor ist Teil von C ++ und cstdintTeil des C ++ - Standards, ebenso wie alle dort definierten Typnamen. Es ist in der Tat für die angegebenen Tags geeignet. ... Ich bin nicht damit einverstanden, die Typen manuell zu definieren, aber es kann erforderlich sein, wenn Sie mit Compilern arbeiten, die dies nicht tun.
Justin Time - Stellen Sie Monica
90

Ich würde sagen, dies ist der moderne C ++ - Weg.

#include <cstdint>
void *p;
auto i = reinterpret_cast<std::uintptr_t>(p);

BEARBEITEN :

Der richtige Typ für die Ganzzahl

Der richtige Weg, einen Zeiger als Ganzzahl zu speichern, besteht darin, die Typen uintptr_toder zu intptr_tverwenden. (Siehe auch in cppreference Integer-Typen für C99 ).

Diese Typen sind in <stdint.h>C99 und im Namespace stdfür C ++ 11 in definiert <cstdint>(siehe Integer-Typen für C ++ ).

C ++ 11 (und höher) Version

#include <cstdint>
std::uintptr_t i;

C ++ 03 Version

extern "C" {
#include <stdint.h>
}

uintptr_t i;

C99 Version

#include <stdint.h>
uintptr_t i;

Der richtige Casting-Operator

In C gibt es nur eine Besetzung, und die Verwendung der C-Besetzung in C ++ ist verpönt (verwenden Sie sie also nicht in C ++). In C ++ gibt es verschiedene Casts. reinterpret_castist die richtige Besetzung für diese Konvertierung (siehe auch hier ).

C ++ 11 Version

auto i = reinterpret_cast<std::uintptr_t>(p);

C ++ 03 Version

uintptr_t i = reinterpret_cast<uintptr_t>(p);

C-Version

uintptr_t i = (uintptr_t)p; // C Version

Verwandte Fragen

Alex
quelle
6
die einzige Antwort, die reinterpret_cast richtig erwähnt
plasmacel
Wenn Sie <cstdint> einschließen möchten, möchten Sie wahrscheinlich stattdessen auch std :: uintptr_t verwenden.
Linleno
Genial ... Die Besetzung ist das, wonach ich gesucht habe. Wenn wir aufgefordert werden, uintptr_tstatt zu verwenden size_t, warum ist es dann erforderlich reinterpret_cast? Es scheint einfach zu static_castsein, da der Standard speziell die kompatiblen Datentypen bereitstellt ...
jww
1
@jww read: en.cppreference.com/w/cpp/language/static_cast Mein Verständnis hier ist, dass static_castder Typ möglicherweise konvertiert wird oder wenn es sich um einen Zeiger handelt, Zeigeranpassungen vornehmen können, wenn der Typ dies benötigt. reinterpret_caständert wirklich nur den Typ des zugrunde liegenden Speichermusters (keine Mutationen). zu verdeutlichen: static_castverhält sich hier identisch.
Alex
2
Dies sollte stattdessen als ausgewählte Antwort markiert werden, da es alle Details zum Umwandeln in C und C ++ enthält .
HaseeB Mir
42

'size_t' und 'ptrdiff_t' sind erforderlich, um Ihrer Architektur zu entsprechen (was auch immer es ist). Daher denke ich, anstatt 'int' zu verwenden, sollten Sie in der Lage sein, 'size_t' zu verwenden, das auf einem 64-Bit-System ein 64-Bit-Typ sein sollte.

Diese Diskussion ohne Vorzeichen int vs size_t geht etwas detaillierter.

Richard Corden
quelle
34
Während size_t normalerweise groß genug ist, um einen Zeiger aufzunehmen, ist dies nicht unbedingt der Fall. Es ist besser, einen stdint.h-Header zu suchen (falls Ihr Compiler noch keinen hat) und uintptr_t zu verwenden.
Michael Burr
3
Leider ist die einzige Einschränkung, size_tdass es das Ergebnis von jedem enthalten muss sizeof(). Dies macht es nicht unbedingt 64 Bit auf x64. siehe auch
Antoine
3
size_t kann den Wert eines Nichtmitgliedszeigers sicher speichern. Siehe en.cppreference.com/w/cpp/types/size_t .
AndyJost
1
@AndyJost Nein, das kann es nicht. Sogar Ihr eigener Link bestätigt dies.
yyny
1
@YoYoYonnY: "Auf vielen Plattformen (eine Ausnahme bilden Systeme mit segmentierter Adressierung) kann std :: size_t den Wert eines Nichtmitgliedszeigers sicher speichern. In diesem Fall ist er gleichbedeutend mit std :: uintptr_t." - wovon redest du?
Slashmais
16

Verwenden Sie uintptr_tals Ganzzahltyp.

Mondschatten
quelle
8

Mehrere Antworten haben auf uintptr_tund hingewiesen#include <stdint.h> als "die" Lösung . Das ist, wie ich vorschlage, ein Teil der Antwort, aber nicht die ganze Antwort. Sie müssen auch überprüfen, wo die Funktion mit der Nachrichten-ID von FOO aufgerufen wird.

Betrachten Sie diesen Code und die Zusammenstellung:

$ cat kk.c
#include <stdio.h>
static void function(int n, void *p)
{
    unsigned long z = *(unsigned long *)p;
    printf("%d - %lu\n", n, z);
}

int main(void)
{
    function(1, 2);
    return(0);
}
$ rmk kk
        gcc -m64 -g -O -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith \
            -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
            -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE kk.c -o kk 
kk.c: In function 'main':
kk.c:10: warning: passing argument 2 of 'func' makes pointer from integer without a cast
$

Sie werden feststellen, dass am aufrufenden Ort (in main()) ein Problem vorliegt - das Konvertieren einer Ganzzahl in einen Zeiger ohne Umwandlung. Sie müssen Ihre function()in all ihren Verwendungen analysieren, um zu sehen, wie Werte an sie übergeben werden. Der Code in meinem function()würde funktionieren, wenn die Aufrufe geschrieben würden:

unsigned long i = 0x2341;
function(1, &i);

Da Ihre wahrscheinlich anders geschrieben sind, müssen Sie die Punkte überprüfen, an denen die Funktion aufgerufen wird, um sicherzustellen, dass es sinnvoll ist, den angegebenen Wert zu verwenden. Vergessen Sie nicht, dass Sie möglicherweise einen latenten Fehler finden.

Wenn Sie den Wert des void *Parameters (wie konvertiert) formatieren möchten, schauen Sie sich den <inttypes.h>Header genau an (anstelle von stdint.h- inttypes.hbietet die Dienste von stdint.h, was ungewöhnlich ist, aber der C99-Standard besagt, dass der Header <inttypes.h>den Header <stdint.h>und enthält erweitert es um zusätzliche Funktionen, die von gehosteten Implementierungen bereitgestellt werden ) und verwendet die PRIxxx-Makros in Ihren Formatzeichenfolgen.

Außerdem gelten meine Kommentare ausschließlich für C und nicht für C ++, aber Ihr Code befindet sich in der Teilmenge von C ++, die zwischen C und C ++ portierbar ist. Die Chancen stehen gut bis gut, dass meine Kommentare zutreffen.

Jonathan Leffler
quelle
3
Ich denke, Sie haben den Punkt meiner Frage verpasst. Der Code speichert den Wert einer Ganzzahl in einem Zeiger. Und dieser Teil des Codes macht das Gegenteil (z. B. Extrahieren des Werts der Ganzzahl, die als Zeiger geschrieben wurde).
PierreBdR
@PierreBdR Trotzdem macht er einen sehr gültigen Punkt. Es ist nicht immer so einfach, sich Code anzusehen (auch wenn Compiler davor warnen), der ein signiertes int verwendet, aber für eine Größe verwendet wird, und es für in Ordnung zu halten, es in unsigniert zu ändern. Leider ist es nicht immer so einfach. Sie müssen jeden Fall explizit betrachten, es sei denn, Sie möchten potenzielle Fehler verursachen - und zwar subtile Fehler.
Pryftan
4
  1. #include <stdint.h>
  2. Verwenden Sie den uintptr_tin der enthaltenen Standard-Header-Datei definierten Standardtyp.
Michael S.
quelle
3

Ich bin auf diese Frage gestoßen, als ich den Quellcode von SQLite studiert habe .

In der Datei sqliteInt.h ist ein Absatz des Codes definiert, der eine Makrokonvertierung zwischen Ganzzahl und Zeiger definiert. Der Autor gab eine sehr gute Erklärung ab und wies zunächst darauf hin, dass es sich um ein compilerabhängiges Problem handeln sollte, und implementierte dann die Lösung, um die meisten gängigen Compiler zu berücksichtigen.

#if defined(__PTRDIFF_TYPE__)  /* This case should work for GCC */
# define SQLITE_INT_TO_PTR(X)  ((void*)(__PTRDIFF_TYPE__)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__)       /* Works for compilers other than LLVM */
# define SQLITE_INT_TO_PTR(X)  ((void*)&((char*)0)[X])
# define SQLITE_PTR_TO_INT(X)  ((int)(((char*)X)-(char*)0))
#elif defined(HAVE_STDINT_H)   /* Use this case if we have ANSI headers */
# define SQLITE_INT_TO_PTR(X)  ((void*)(intptr_t)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(intptr_t)(X))
#else                          /* Generates a warning - but it always works     */
# define SQLITE_INT_TO_PTR(X)  ((void*)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(X))
#endif

Und hier ist ein Zitat des Kommentars für weitere Details:

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type.
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
**
** Ticket #3860:  The llvm-gcc-4.2 compiler from Apple chokes on
** the ((void*)&((char*)0)[X]) construct.  But MSVC chokes on ((void*)(X)).
** So we have to define the macros in different ways depending on the
** compiler.
*/

Der Kredit geht an die Committer.

B.Mr.W.
quelle
2

Am besten vermeiden Sie die Konvertierung vom Zeigertyp in Nicht-Zeigertypen. Dies ist in Ihrem Fall jedoch eindeutig nicht möglich.

Wie alle sagten, sollten Sie uintptr_t verwenden.

Dieser Link enthält gute Informationen zur Konvertierung in 64-Bit-Code.

Es gibt auch eine gute Diskussion darüber auf comp.std.c

Benoit
quelle
2

Ich denke, die "Bedeutung" von void * ist in diesem Fall ein generischer Griff. Es ist kein Zeiger auf einen Wert, es ist der Wert selbst. (Dies ist zufällig die Verwendung von void * durch C- und C ++ - Programmierer.)

Wenn es einen ganzzahligen Wert enthält, sollte es besser im ganzzahligen Bereich liegen!

Hier ist einfaches Rendern in Ganzzahlen:

int x = (char*)p - (char*)0;

Es sollte nur eine Warnung geben.

Craig Hicks
quelle
0

Da uintptr_tist nicht in C , dort zu sein garantiert ++ / C ++ 11 , wenn dies eine Einbahn Umwandlung ist , kann man bedenkt uintmax_t, immer definiert in <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

Um auf Nummer sicher zu gehen, könnte man irgendwo im Code eine Behauptung hinzufügen:

static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
              "No suitable integer type for conversion from pointer type");
Antonio
quelle
Wenn Sie uintptr_t nicht haben, ist uintmax_t auch keine Antwort: Es gibt keine Garantie, dass Sie den Wert eines Zeigers darin speichern können! Möglicherweise gibt es keinen Integer-Typ, der dies tut.
PierreBdR