Nas implementações com um modelo de memória plana (basicamente tudo), a conversão para uintptr_t
Just Work.
(Mas consulte As comparações de ponteiros devem ser assinadas ou não assinadas no x86 de 64 bits? Para discutir se você deve tratar os ponteiros como assinados ou não, incluindo problemas de formação de ponteiros fora dos objetos que são UB em C.)
Mas sistemas com modelos de memória não-planos existem, e pensar sobre eles podem ajudar a explicar a situação atual, como C ++ ter especificações diferentes para <
vs. std::less
.
Parte do objetivo dos <
ponteiros para separar objetos sendo UB em C (ou pelo menos não especificados em algumas revisões de C ++) é permitir máquinas estranhas, incluindo modelos de memória não plana.
Um exemplo conhecido é o modo real x86-16, em que os ponteiros são segmentados: offset, formando um endereço linear de 20 bits via (segment << 4) + offset
. O mesmo endereço linear pode ser representado por várias combinações diferentes seg: off.
C ++ std::less
em ponteiros em ISAs estranhos pode precisar ser caro , por exemplo, "normalizar" um segmento: deslocamento em x86-16 para deslocamento <= 15. No entanto, não há uma maneira portátil de implementar isso. A manipulação necessária para normalizar um uintptr_t
(ou a representação de objeto de um objeto ponteiro) é específica da implementação.
Mas mesmo em sistemas onde o C ++ std::less
precisa ser caro, <
não precisa ser. Por exemplo, supondo um modelo de memória "grande" em que um objeto se encaixe em um segmento, <
basta comparar a parte deslocada e nem mesmo se preocupar com a parte do segmento. (Ponteiros dentro do mesmo objeto terão o mesmo segmento e, caso contrário, o UB no C. C ++ 17 foi alterado para meramente "não especificado", o que ainda pode permitir ignorar a normalização e apenas comparar compensações.) Isso pressupõe que todos os ponteiros de qualquer parte de um objeto sempre use o mesmo seg
valor, nunca normalizando. Isso é o que você esperaria de uma ABI para um modelo de memória "grande" em oposição a "grande". (Veja a discussão nos comentários ).
(Esse modelo de memória pode ter um tamanho máximo de objeto de 64 kiB, por exemplo, mas um espaço de endereço total máximo muito maior, com espaço para muitos desses objetos de tamanho máximo. O ISO C permite que as implementações tenham um limite no tamanho do objeto menor que o o valor máximo (não assinado) size_t
pode representar, SIZE_MAX
por exemplo, mesmo em sistemas de modelo de memória plana, o GNU C limita o tamanho máximo do objeto para PTRDIFF_MAX
que o cálculo do tamanho ignore o estouro assinado.) Consulte esta resposta e discussão nos comentários.
Se você deseja permitir objetos maiores que um segmento, precisa de um modelo de memória "enorme" que precise se preocupar em exceder a parte deslocada de um ponteiro ao fazer um p++
loop através de uma matriz ou ao fazer uma aritmética de indexação / ponteiro. Isso leva a códigos mais lentos em todos os lugares, mas provavelmente significaria que p < q
funcionaria para ponteiros para objetos diferentes, porque uma implementação direcionada a um modelo de memória "enorme" normalmente escolheria manter todos os ponteiros normalizados o tempo todo. Consulte O que há perto, longe e grandes indicadores? - alguns compiladores C reais para o modo real x86 tiveram uma opção de compilar para o modelo "enorme", onde todos os ponteiros assumiram o padrão de "enorme", a menos que declarado o contrário.
A segmentação em modo real x86 não é o único modelo de memória não plana possível , é apenas um exemplo concreto útil para ilustrar como ele foi tratado pelas implementações de C / C ++. Na vida real, as implementações estenderam o ISO C com o conceito de ponteiros far
vs. near
, permitindo que os programadores escolham quando podem simplesmente armazenar / passar pela parte offset de 16 bits, relativa a algum segmento de dados comum.
Mas uma implementação ISO C pura teria que escolher entre um modelo de memória pequeno (tudo, exceto código no mesmo 64kiB com ponteiros de 16 bits) ou grande ou enorme, com todos os ponteiros de 32 bits. Alguns loops podem otimizar incrementando apenas a parte de deslocamento, mas os objetos ponteiros não podem ser otimizados para serem menores.
Se você soubesse qual era a manipulação mágica para qualquer implementação, poderia implementá-la em C puro . O problema é que sistemas diferentes usam endereços diferentes e os detalhes não são parametrizados por macros portáteis.
Ou talvez não: isso pode envolver a procura de algo em uma tabela de segmento especial ou algo assim, por exemplo, como o modo protegido x86, em vez do modo real, onde a parte do segmento do endereço é um índice, não um valor a ser mudado. Você pode configurar segmentos parcialmente sobrepostos no modo protegido, e as partes dos endereços do seletor de segmentos não seriam necessariamente ordenadas na mesma ordem que os endereços base do segmento correspondente. Obter um endereço linear de um ponteiro seg: off no modo protegido x86 pode envolver uma chamada do sistema, se o GDT e / ou LDT não estiverem mapeados em páginas legíveis em seu processo.
(É claro que os sistemas operacionais convencionais para x86 usam um modelo de memória simples, de modo que a base do segmento seja sempre 0 (exceto para armazenamento local de segmentos usando fs
ou gs
segmentos), e apenas a parte "deslocamento" de 32 ou 64 bits é usada como ponteiro .)
Você pode adicionar manualmente o código para várias plataformas específicas, por exemplo, por padrão, supor flat ou #ifdef
algo para detectar o modo real x86 e dividir uintptr_t
em metades de 16 bits para seg -= off>>4; off &= 0xf;
depois combinar essas partes novamente em um número de 32 bits.