size_t vs. uintptr_t

O padrão C garante que size_t é um tipo que pode conter qualquer índice de array. Isso significa que, logicamente, size_t deve ser capaz de manter qualquer tipo de ponteiro. Eu li em alguns sites que eu achei no Googles que isso é legal e / ou deve sempre funcionar:

 void *v = malloc(10); size_t s = (size_t) v; 

Então, em C99, o padrão introduziu os tipos intptr_t e uintptr_t , que são tipos assinados e não assinados garantidos para manter pointers:

 uintptr_t p = (size_t) v; 

Então, qual é a diferença entre usar size_t e uintptr_t ? Ambos não assinados, e ambos devem ser capazes de manter qualquer tipo de ponteiro, então eles parecem funcionalmente idênticos. Existe alguma razão real e convincente para usar o uintptr_t (ou melhor ainda, um void * ) em vez de um size_t , além da clareza? Em uma estrutura opaca, onde o campo será tratado apenas por funções internas, existe alguma razão para não fazer isso?

Da mesma forma, ptrdiff_t tem sido um tipo assinado capaz de manter as diferenças de ponteiro e, portanto, capaz de manter mais qualquer ponteiro, então, como ele é diferente de intptr_t ?

Não são todos esses tipos basicamente servindo versões trivialmente diferentes da mesma function? Se não, por quê? O que eu não posso fazer com um deles que não posso fazer com o outro? Se sim, por que o C99 adicionou dois tipos essencialmente supérfluos ao idioma?

Estou disposto a desconsiderar os indicadores de function, pois eles não se aplicam ao problema atual, mas sinta-se à vontade para mencioná-los, pois tenho uma suspeita de que eles estarão no centro da resposta “correta”.

    size_t é um tipo que pode conter qualquer índice de matriz. Isso significa que, logicamente, size_t deve ser capaz de manter qualquer tipo de ponteiro

    Não necessariamente! Volte aos tempos das arquiteturas de 16 bits segmentadas, por exemplo: um array pode ser limitado a um único segmento (assim seria um intptr_t 16 bits), MAS você poderia ter múltiplos segmentos (seria necessário um tipo intptr_t 32 bits) para escolher o segmento, bem como o deslocamento dentro dele). Eu sei que essas coisas parecem estranhas nesses dias de arquiteturas uniformemente endereçáveis, mas o padrão DEVE atender a uma variedade maior do que “o que é normal em 2009”, você sabe!

    Em relação à sua afirmação:

    “O padrão C garante que size_t é um tipo que pode conter qualquer índice de matriz. Isso significa que, logicamente, size_t deve ser capaz de manter qualquer tipo de ponteiro.”

    Isso é chamado de falácia, um equívoco resultante do raciocínio incorreto. Você pode pensar que o último segue do primeiro, mas isso não é necessariamente assim.

    Ponteiros e índices de array não são a mesma coisa. É bastante plausível imaginar uma implementação em conformidade que limite arrays a 65536 elementos, mas permita que os pointers direcionem qualquer valor para um espaço de endereço de 128 bits.

    C99 afirma que o limite superior de uma variável size_t é definido por SIZE_MAX e pode ser tão baixo quanto 65535 (veja C99 TR3, 7.18.3, inalterado em C11). Os indicadores seriam bastante limitados se estivessem restritos a essa faixa nos sistemas modernos.

    Na prática, você provavelmente descobrirá que sua suposição é válida, mas não é porque o padrão garante isso. Porque na verdade não garante isso.

    Deixarei que todas as outras respostas sejam válidas em relação ao raciocínio com limitações de segmento, arquiteturas exóticas e assim por diante.

    A simples diferença de nomes não é suficiente para usar o tipo apropriado para a coisa certa?

    Se você está armazenando um tamanho, use size_t . Se você estiver armazenando um ponteiro, use intptr_t . Uma pessoa lendo seu código saberá instantaneamente que “aha, isso é um tamanho de algo, provavelmente em bytes”, e “oh, aqui está um valor de ponteiro sendo armazenado como um inteiro, por algum motivo”.

    Caso contrário, você poderia usar apenas unsigned long (ou, nestes tempos aqui modernos, unsigned long long ) para tudo. O tamanho não é tudo, os nomes dos tipos carregam significado, o que é útil, já que ajuda a descrever o programa.

    É possível que o tamanho da maior matriz seja menor que um ponteiro. Pense em arquiteturas segmentadas – os pointers podem ter 32 bits, mas um único segmento pode ser capaz de endereçar apenas 64 KB (por exemplo, a arquitetura 8086 de modo real antigo).

    Embora estes não sejam mais usados ​​em computadores desktop, o padrão C é destinado a suportar arquiteturas pequenas e especializadas. Ainda existem sistemas embarcados sendo desenvolvidos com CPUs de 8 ou 16 bits, por exemplo.

    Eu imagino (e isso vale para todos os nomes de tipos) que melhor transmite suas intenções no código.

    Por exemplo, mesmo que unsigned short e wchar_t sejam do mesmo tamanho no Windows (acho), usar wchar_t vez de unsigned short mostra a intenção de usá-lo para armazenar um caractere largo, em vez de apenas um número arbitrário.

    Olhando para trás e para frente, e lembrando que várias arquiteturas excêntricas estavam espalhadas pela paisagem, tenho certeza de que elas estavam tentando envolver todos os sistemas existentes e também prover todos os possíveis sistemas futuros.

    Tão certo, a maneira como as coisas se resolveram, até agora não precisávamos de tantos tipos.

    Mas mesmo no LP64, um paradigma bastante comum, precisávamos de size_t e ssize_t para a interface de chamada do sistema. Pode-se imaginar um legado mais restrito ou um sistema futuro, em que o uso de um tipo completo de 64 bits é caro e eles podem querer apostar em operações de I / O maiores que 4 GB, mas ainda assim ter pointers de 64 bits.

    Eu acho que você tem que se perguntar: o que pode ter sido desenvolvido, o que pode vir no futuro. (Talvez pointers de 128 bits distribuídos em toda a Internet, mas não mais do que 64 bits em uma chamada de sistema, ou talvez até mesmo um limite de 32 bits “legado” 🙂 Imagem que os sistemas legados podem obter novos compiladores C. .

    Além disso, observe o que existia naquela época. Além dos modelos de memory em modo real zillion 286, que tal os mainframes de ponteiro de 18 bits / palavra de 60 bits do CDC? Como sobre a série Cray? Não importa o normal ILP64, LP64, LLP64. (Eu sempre achei que a Microsoft era pretensiosa com o LLP64, deveria ter sido o P64.) Eu certamente posso imaginar um comitê tentando cobrir todas as bases …

     int main(){ int a[4]={0,1,5,3}; int a0 = a[0]; int a1 = *(a+1); int a2 = *(2+a); int a3 = 3[a]; return a2; } 

    Implicando que intptr_t deve sempre replace size_t e vice versa.