O que é uintptr_t
e para que pode ser usado?
O que é uintptr_t
e para que pode ser usado?
Respostas:
uintptr_t
é um tipo inteiro não assinado capaz de armazenar um ponteiro de dados. O que normalmente significa que é do mesmo tamanho que um ponteiro.
É opcionalmente definido nos padrões C ++ 11 e posteriores.
Um motivo comum para querer um tipo inteiro que possa conter o tipo de ponteiro de uma arquitetura é executar operações específicas de número inteiro em um ponteiro ou ocultar o tipo de um ponteiro, fornecendo-o como um "identificador" inteiro.
Edit: Note que Steve Jessop tem alguns detalhes adicionais muito interessantes (que eu não roubarei) em outra resposta aqui para vocês, tipos pedantes :)
size_t
somente é necessário o tamanho do objeto maior e pode ser menor que um ponteiro. Isto seria esperado em arquiteturas segmentadas como o 8086 (16 bits size_t
, mas 32 bits void*
)
ptrdiff_t
. uintptr_t
não foi feito para isso.
unsigned int
geralmente não é grande o suficiente. Mas pode ser grande o suficiente. Esse tipo existe especificamente para remover todos os "pressupostos" .
A primeira coisa, no momento em que a pergunta foi feita, uintptr_t
não estava em C ++. Está em C99, em <stdint.h>
, como um tipo opcional. Muitos compiladores C ++ 03 fornecem esse arquivo. Também está em C ++ 11, em <cstdint>
, onde novamente é opcional e se refere a C99 para a definição.
Em C99, é definido como "um tipo inteiro não assinado com a propriedade que qualquer ponteiro válido para anular pode ser convertido para esse tipo, depois convertido de volta para ponteiro para anular, e o resultado será comparado ao ponteiro original".
Entenda isso como o que diz. Não diz nada sobre tamanho.
uintptr_t
pode ter o mesmo tamanho de a void*
. Pode ser maior. Pode ser concebivelmente menor, embora essa implementação em C ++ seja perversa. Por exemplo, em alguma plataforma hipotética em que void*
há 32 bits, mas apenas 24 bits de espaço de endereço virtual são usados, você pode ter 24 bits uintptr_t
que atendam aos requisitos. Não sei por que uma implementação faria isso, mas o padrão permite isso.
void*
. No entanto, afeta possíveis direções futuras, especialmente se você quiser alterar para usar algo que realmente é apenas um identificador inteiro, e não um ponteiro convertido.
typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;
. Em vez disso: callback.dataPtr = (void*)val
. Por outro lado, é claro que você obtém void*
e precisa devolvê-lo int
.
É um tipo inteiro não assinado, exatamente do tamanho de um ponteiro. Sempre que você precisar fazer algo incomum com um ponteiro - como, por exemplo, inverter todos os bits (não pergunte o porquê), você o converte uintptr_t
e o manipula como um número inteiro usual, depois lança de volta.
void*
valor de ponteiro para uintptr_t
e para trás produz um void*
valor que se compara igual ao ponteiro original. uintptr_t
geralmente é do mesmo tamanho que void*
, mas isso não é garantido, nem existe garantia de que os bits do valor convertido tenham algum significado específico. E não há garantia de que ele possa reter um valor convertido de ponteiro para função sem perda de informações. Finalmente, não é garantido que exista.
Já existem muitas boas respostas para a parte "what is uintptr_t data type". Vou tentar abordar o "para que ele pode ser usado?" parte neste post.
Principalmente para operações bit a bit em ponteiros. Lembre-se de que em C ++ não é possível executar operações bit a bit em ponteiros. Por razões, consulte Por que você não pode executar operações bit a bit no ponteiro em C e existe uma maneira de contornar isso?
Portanto, para realizar operações bit a bit em ponteiros, seria necessário converter ponteiros para digitar unitpr_t e, em seguida, executar operações bit a bit.
Aqui está um exemplo de uma função que eu acabei de escrever para fazer bit a bit exclusivo ou de 2 ponteiros para armazenar em uma lista vinculada ao XOR, para que possamos percorrer as duas direções como uma lista duplamente vinculada, mas sem a penalidade de armazenar 2 ponteiros em cada nó .
template <typename T>
T* xor_ptrs(T* t1, T* t2)
{
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
}
Correndo o risco de obter outro crachá Necromancer, gostaria de adicionar um uso muito bom para uintptr_t (ou mesmo intptr_t) e que esteja escrevendo código incorporado testável. Eu escrevo principalmente código incorporado direcionado a vários processadores de braço e atualmente tensilica. Elas possuem várias larguras de barramento nativas e a tensilica é na verdade uma arquitetura de Harvard com barramentos de código e dados separados que podem ter larguras diferentes. Eu uso um estilo de desenvolvimento orientado a testes para grande parte do meu código, o que significa que eu faço testes de unidade para todas as unidades de código que escrevo. O teste de unidade no hardware de destino real é um aborrecimento, por isso geralmente escrevo tudo em um PC baseado em Intel, seja no Windows ou Linux usando Ceedling e GCC. Dito isto, muitos códigos incorporados envolvem manipulação de endereços e manipulação de bits. A maioria das minhas máquinas Intel são de 64 bits. Portanto, se você for testar o código de manipulação de endereço, precisará de um objeto generalizado para fazer as contas. Assim, o uintptr_t oferece a você uma maneira independente de depurar seu código antes da tentativa de implantar no hardware de destino. Outro problema é que algumas máquinas ou mesmo modelos de memória em alguns compiladores, ponteiros de função e ponteiros de dados têm larguras diferentes. Nessas máquinas, o compilador pode nem mesmo permitir a conversão entre as duas classes, mas o uintptr_t deve ser capaz de manter uma ou outra.