Converter um ponteiro em um inteiro

88

Estou tentando adaptar um código existente a uma máquina de 64 bits. O principal problema é que em uma função, o codificador anterior usa um argumento void * que é convertido em um tipo adequado na própria função. Um pequeno exemplo:

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

Claro, em uma máquina de 64 bits, recebo o erro:

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

Eu gostaria de corrigir isso para que ele ainda funcione em uma máquina de 32 bits e da forma mais limpa possível. Qualquer ideia ?

PierreBdR
fonte
5
Sei que isso está desenterrando uma postagem antiga, mas parece que a resposta aceita não é totalmente correta. Um exemplo concreto de size_tnão funcionamento é a memória segmentada i386. Embora seja uma máquina de 32 bits, sizeofretorna 2para size_t. A resposta de Alex abaixo parece correta. A resposta de Alex uintptr_tfunciona em quase todos os lugares e agora é padrão. Ele fornece um tratamento C ++ 11 e até mesmo fornece proteções de cabeçalho do C ++ 03.
jww

Respostas:

70

Use intptr_te uintptr_t.

Para garantir que seja definido de forma portátil, você pode usar um código como este:

#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

Basta colocá-lo em algum arquivo .h e incluí-lo onde for necessário.

Como alternativa, você pode baixar a versão do stdint.harquivo da Microsoft aqui ou usar uma versão portátil aqui .

Milan Babuškov
fonte
Consulte stackoverflow.com/questions/126279/… para obter informações sobre como obter um stdint.h que funcione com MSVC (e possivelmente Borland).
Michael Burr
2
Ambos os links quebrados!
Antonio
1
Esta resposta está relacionada a C, mas a linguagem está marcada como C ++, então não é a resposta que eu estava procurando.
HaseeB Mir
@HaSeeBMiR Uma correção apropriada é alternar <cstdint>ou baixar o apropriado cstdintse você baixar um stdint.h.
Justin Time - Reintegrar Monica em
1
@HaSeeBMiR O único motivo pelo qual a resposta está relacionada a C em vez de C ++ é que ele usa um cabeçalho C em vez do cabeçalho C ++ equivalente. O pré-processador C é parte do C ++ e cstdintdo padrão C ++, assim como todos os nomes de tipo definidos lá. Na verdade, é apropriado para as tags especificadas. ... Não concordo em definir os tipos manualmente, mas pode ser necessário ao trabalhar com compiladores que não o fazem.
Justin Time - Reintegrar Monica em
94

Eu diria que esta é a maneira moderna do C ++.

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

EDITAR :

O tipo correto para o inteiro

portanto, a maneira correta de armazenar um ponteiro como um inteiro é usar os tipos uintptr_tou intptr_t. (Veja também em cppreference inteiros tipos para C99 ).

esses tipos são definidos em <stdint.h>para C99 e no namespace stdpara C ++ 11 em <cstdint>(consulte tipos inteiros para C ++ ).

Versão C ++ 11 (e posteriores)

#include <cstdint>
std::uintptr_t i;

Versão C ++ 03

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

uintptr_t i;

Versão C99

#include <stdint.h>
uintptr_t i;

O operador de fundição correto

Em C, há apenas um elenco e usar o elenco de C em C ++ é desaprovado (portanto, não o use em C ++). Em C ++, existem diferentes castes. reinterpret_casté o elenco correto para esta conversão (veja também aqui ).

Versão C ++ 11

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

Versão C ++ 03

uintptr_t i = reinterpret_cast<uintptr_t>(p);

Versão C

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

Perguntas relacionadas

Alexander oh
fonte
6
a única resposta que menciona corretamente reinterpret_cast
plasmacel
Se você pretendia incluir <cstdint>, provavelmente também deseja usar std :: uintptr_t.
Linleno de
Incrível ... O elenco é o que eu estava procurando. Se nos dizem para usar em uintptr_tvez de size_t, por que isso exige reinterpret_cast? Parece uma static_cast
tarefa
1
@jww read: en.cppreference.com/w/cpp/language/static_cast meu entendimento aqui é que static_castpode converter o tipo ou se for um ponteiro pode fazer ajustes de ponteiro se o tipo precisar. reinterpret_casté realmente apenas mudar o tipo de padrão de memória subjacente (sem mutações). para esclarecer: static_castse comporta de forma idêntica aqui.
Alexander Oh
2
em vez disso, ela deve ser marcada como resposta selecionada, pois fornece todos os detalhes de como converter em C e C ++ .
HaseeB Mir
43

'size_t' e 'ptrdiff_t' são necessários para corresponder à sua arquitetura (seja ela qual for). Portanto, acho que em vez de usar 'int', você deve ser capaz de usar 'size_t', que em um sistema de 64 bits deve ser do tipo 64 bits.

Esta discussão unsigned int vs size_t entra em um pouco mais de detalhes.

Richard Corden
fonte
34
Embora size_t seja geralmente grande o suficiente para conter um ponteiro, não é necessariamente o caso. Seria melhor localizar um cabeçalho stdint.h (se o seu compilador ainda não tiver um) e usar uintptr_t.
Michael Burr
3
Infelizmente, a única restrição size_té que deve conter o resultado de qualquer um sizeof(). Isso não significa necessariamente 64 bits em x64. ver também
Antoine
3
size_t pode armazenar com segurança o valor de um ponteiro não membro. Consulte en.cppreference.com/w/cpp/types/size_t .
AndyJost,
2
@AndyJost Não, não pode. Até o seu próprio link confirma isso.
yyny
1
@YoYoYonnY: "Em muitas plataformas (uma exceção são os sistemas com endereçamento segmentado) std :: size_t pode armazenar com segurança o valor de qualquer ponteiro não membro, caso em que é sinônimo de std :: uintptr_t." - do que você está falando?
slashmais de
16

Use uintptr_tcomo seu tipo inteiro.

sombra da Lua
fonte
8

Várias respostas apontaram uintptr_te #include <stdint.h>como 'a' solução. Essa é, eu sugiro, parte da resposta, mas não toda a resposta. Você também precisa verificar onde a função é chamada com o ID de mensagem FOO.

Considere este código e compilação:

$ 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
$

Você observará que há um problema no local de chamada (in main()) - converter um inteiro em um ponteiro sem uma conversão. Você precisará analisá-lo function()em todos os seus usos para ver como os valores são passados ​​para ele. O código dentro do meu function()funcionaria se as chamadas fossem escritas:

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

Como os seus provavelmente estão escritos de forma diferente, você precisa revisar os pontos onde a função é chamada para garantir que faz sentido usar o valor conforme mostrado. Não se esqueça, você pode estar encontrando um bug latente.

Além disso, se você for formatar o valor do void *parâmetro (conforme convertido), observe cuidadosamente o <inttypes.h>cabeçalho (em vez de stdint.h- inttypes.hfornece os serviços de stdint.h, o que é incomum, mas o padrão C99 diz [t] que o cabeçalho <inttypes.h>inclui o cabeçalho <stdint.h>e estende-o com recursos adicionais fornecidos por implementações hospedadas ) e usa as macros PRIxxx em suas strings de formato.

Além disso, meus comentários são estritamente aplicáveis ​​a C em vez de C ++, mas seu código está no subconjunto de C ++ que é portável entre C e C ++. As chances são boas de que meus comentários se apliquem.

Jonathan Leffler
fonte
3
Acho que você não entendeu o ponto da minha pergunta. O código está armazenando o valor de um inteiro em um ponteiro. E essa parte do código está fazendo o oposto (por exemplo, extraindo o valor do inteiro que foi escrito como um ponteiro).
PierreBdR
@PierreBdR No entanto, ele faz uma observação muito válida. Nem sempre é tão simples quanto olhar para um código (incluindo quando os compiladores avisam sobre isso) que usa um int assinado, mas é usado para um tamanho e acha que está tudo bem alterá-lo para não assinado. Infelizmente, nem sempre é tão simples. Você tem que olhar para cada caso explicitamente, a menos que queira causar possíveis bugs - e bugs sutis.
Pryftan de
4
  1. #include <stdint.h>
  2. Use uintptr_to tipo padrão definido no arquivo de cabeçalho padrão incluído.
Michael S.
fonte
4

Me deparei com essa pergunta enquanto estudava o código-fonte do SQLite .

No sqliteInt.h , há um parágrafo de código definido uma macro convertida entre inteiro e ponteiro. O autor fez uma declaração muito boa, primeiro apontando que deveria ser um problema dependente do compilador e então implementou a solução para contabilizar a maioria dos compiladores populares que existem.

#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

E aqui está uma citação do comentário para mais detalhes:

/*
** 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.
*/

O crédito vai para os comprometedores.

B.Mr.W.
fonte
2

A melhor coisa a fazer é evitar a conversão de tipos de ponteiro em tipos não-ponteiro. No entanto, isso claramente não é possível no seu caso.

Como todos disseram, o uintptr_t é o que você deve usar.

Este link contém boas informações sobre a conversão para código de 64 bits.

Também há uma boa discussão sobre isso em comp.std.c

Benoit
fonte
2

Acho que o "significado" de void * neste caso é um identificador genérico. Não é um ponteiro para um valor, é o próprio valor. (Acontece que é assim que void * é usado por programadores C e C ++.)

Se estiver contendo um valor inteiro, é melhor que esteja dentro do intervalo inteiro!

Aqui está uma renderização fácil para inteiro:

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

Deve dar apenas um aviso.

Craig Hicks
fonte
0

Desde uintptr_té não garantido para estar lá em C ++ / C ++ 11 , se esta é uma conversão de uma maneira que você pode considerar uintmax_t, sempre definido no <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

Para jogar pelo seguro, pode-se adicionar uma declaração em qualquer lugar do código:

static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
              "No suitable integer type for conversion from pointer type");
Antonio
fonte
Se você não tem uintptr_t, então uintmax_t também não é uma resposta: não há garantia de que você possa armazenar o valor de um ponteiro nele! Pode não haver tipo inteiro que faça isso.
PierreBdR