unsigned int vs. size_t

492

Percebo que o código C e C ++ moderno parece usar em size_tvez de int/ unsigned intpraticamente em todos os lugares - desde parâmetros para funções de string C até o STL. Estou curioso para saber o motivo e os benefícios que isso traz.

Roubar
fonte

Respostas:

388

O size_ttipo é o tipo inteiro não assinado que é o resultado do sizeofoperador (e do offsetofoperador); portanto, é garantido que seja grande o suficiente para conter o tamanho do maior objeto que seu sistema pode manipular (por exemplo, uma matriz estática de 8 GB).

O size_ttipo pode ser maior que, igual a ou menor que um unsigned int, e seu compilador pode fazer suposições sobre isso para otimização.

Você pode encontrar informações mais precisas no padrão C99, seção 7.17, cujo rascunho está disponível na Internet em formato pdf , ou no padrão C11, seção 7.19, também disponível como rascunho em pdf .

Remo.D
fonte
50
Não. Pense no x86-16 com o modelo de memória grande (não enorme): os ponteiros estão distantes (32 bits), mas os objetos individuais são limitados a 64k (portanto, size_t pode ter 16 bits).
dan04
8
"tamanho do objeto maior" não é uma expressão ruim, mas absolutamente correta. O tamanho de um objeto pode ser muito mais limitado que o espaço de endereço.
precisa saber é o seguinte
3
"seu compilador pode fazer suposições sobre isso": espero que o compilador conheça o intervalo exato de valores que size_tpodem representar! Se não, quem faz?
Marc van Leeuwen
4
@ Marc: Eu acho que o ponto era mais que o compilador poderia fazer algo com esse conhecimento.
8
Eu só queria que esse tipo cada vez mais popular não exigisse a inclusão de um arquivo de cabeçalho.
user2023370
73

Em resumo, size_tnunca é negativo e maximiza o desempenho porque é typedef para ser o tipo inteiro não assinado que é grande o suficiente - mas não muito grande - para representar o tamanho do maior objeto possível na plataforma de destino.

Os tamanhos nunca devem ser negativos e, de fato, size_tsão do tipo não assinado. Além disso, como size_tnão é assinado, é possível armazenar números aproximadamente duas vezes maiores que o tipo assinado correspondente, porque podemos usar o bit de sinal para representar a magnitude, como todos os outros bits do número inteiro não assinado. Quando ganhamos mais um bit, multiplicamos o intervalo de números que podemos representar por um fator de cerca de dois.

Então, você pergunta, por que não usar apenas um unsigned int? Pode não ser capaz de armazenar números grandes o suficiente. Em uma implementação com unsigned int32 bits, o maior número que pode representar é 4294967295. Alguns processadores, como o IP16L32, podem copiar objetos maiores que 4294967295bytes.

Então, você pergunta, por que não usar um unsigned long int? Exige um pedágio de desempenho em algumas plataformas. O padrão C exige que um longocupe pelo menos 32 bits. Uma plataforma IP16L32 implementa cada comprimento de 32 bits como um par de palavras de 16 bits. Quase todos os operadores de 32 bits nessas plataformas exigem duas instruções, se não mais, porque trabalham com os 32 bits em dois pedaços de 16 bits. Por exemplo, mover um comprimento de 32 bits geralmente requer duas instruções da máquina - uma para mover cada pedaço de 16 bits.

O uso size_tevita esse custo de desempenho. De acordo com este artigo fantástico , "Type size_té um typedef que é um apelido para algum tipo de número inteiro não assinado, normalmente unsigned intou unsigned long, mas possivelmente até unsigned long long. Cada implementação do Standard C deve escolher o número inteiro não assinado que é grande o suficiente - mas não maior que o necessário - para representar o tamanho do maior objeto possível na plataforma de destino ".

Rose Perrone
fonte
1
Desculpe comentar isso depois de tanto tempo, mas eu só tive que confirmar o maior número que um int não assinado pode conter - talvez eu esteja entendendo mal sua terminologia, mas pensei que o maior número que um int não assinado pode conter é 4294967295, sendo 65356 o máximo de um curto não assinado.
9303 Mitch
Se o seu int não assinado ocupar 32 bits, sim, o maior número que ele pode conter é 2 ^ 32 - 1, que é 4294967295 (0xffffffff). Você tem outra pergunta?
Rose Perrone
3
@Itch: O maior valor que pode ser representado em uma unsigned intlata e varia de um sistema para outro. É necessário que seja pelo menos 65536 , mas é comum 4294967295e pode ser 18446744073709551615(2 ** 64-1) em alguns sistemas.
21812 Keith Thompson
1
O maior valor que um int não assinado de 16 bits pode conter é 65535, não 65536. Uma diferença pequena mas importante como 65536 é igual a 0 em um int não assinado de 16 bits.
Sie Raybould
1
@ gnasher729: Você tem certeza sobre o padrão C ++? Depois de pesquisar por algum tempo, tenho a impressão de que eles simplesmente removeram todas as garantias absolutas sobre intervalos inteiros (excluindo unsigned char). O padrão parece não conter a string '65535' ou '65536' em qualquer lugar e '+32767' ocorre apenas (1,9: 9) em uma nota como o maior número possível de números inteiro representável int; nenhuma garantia é dada, mesmo que INT_MAXnão possa ser menor que isso!
Marc van Leeuwen
51

O tipo size_t é o tipo retornado pelo operador sizeof. É um número inteiro não assinado capaz de expressar o tamanho em bytes de qualquer intervalo de memória suportado na máquina host. É (normalmente) relacionado a ptrdiff_t, em que ptrdiff_t é um valor inteiro assinado, de modo que sizeof (ptrdiff_t) e sizeof (size_t) sejam iguais.

Ao escrever o código C, você sempre deve usar size_t sempre que lidar com intervalos de memória.

O tipo int, por outro lado, é basicamente definido como o tamanho do valor inteiro (assinado) que a máquina host pode usar para executar com eficiência a aritmética inteira. Por exemplo, em muitos computadores mais antigos do tipo PC, o valor sizeof (size_t) seria 4 (bytes), mas sizeof (int) seria 2 (byte). A aritmética de 16 bits era mais rápida que a aritmética de 32 bits, embora a CPU pudesse lidar com um espaço de memória (lógico) de até 4 GiB.

Use o tipo int somente quando você se preocupa com a eficiência, pois sua precisão real depende muito das opções do compilador e da arquitetura da máquina. Em particular, o padrão C especifica os seguintes invariantes: sizeof (char) <= sizeof (short) <= sizeof (int) <= sizeof (long) não colocando outras limitações na representação real da precisão disponível ao programador para cada um dos esses tipos primitivos.

Nota: NÃO é o mesmo que em Java (que realmente especifica a precisão de bits para cada um dos tipos 'char', 'byte', 'short', 'int' e 'long').

Kevin S.
fonte
a definição de fato de int é 16 bits em 16 máquinas e 32 bits em algo maior. Muito código foi escrito, assumindo que int tem 32 bits de largura, para alterar isso agora e, como resultado, as pessoas sempre devem usar size_t ou {, u} int {8,16,32,64} _t se quiserem algo específico - - como precaução, as pessoas devem sempre usá-las sempre, em vez dos tipos inteiros integrais.
Clearer
3
"É um número inteiro não assinado capaz de expressar o tamanho em bytes de qualquer intervalo de memória suportado na máquina host." -> No. size_té capaz de representar o tamanho de qualquer objeto único (por exemplo: número, matriz, estrutura). Todo o intervalo de memória pode excedersize_t
chux - Reinstala Monica
"Ao escrever código C, você sempre deve usar size_t sempre que lidar com intervalos de memória." - isso implica que todo índice para toda matriz deve ser size_t- espero que você não esteja falando sério. Na maioria das vezes, não lidamos com matrizes em que a cardinalidade do espaço de endereço + portabilidade é importante. Nesses casos, você aceitaria size_t. Nos demais casos, você obtém índices de números inteiros (assinados). Como a confusão (que ocorre sem aviso prévio) decorrente do comportamento inesperado de sub-fluxo de assinaturas é mais comum e pior que os problemas de portabilidade que podem surgir nos outros casos.
johannes_lalala
23

Digite size_t deve ser grande o suficiente para armazenar o tamanho de qualquer objeto possível. Int não assinado não precisa atender a essa condição.

Por exemplo, nos sistemas de 64 bits, int e unsigned int podem ter 32 bits de largura, mas size_t deve ser grande o suficiente para armazenar números maiores que 4G

Maciej Hehl
fonte
38
"objeto" é o idioma usado pelo padrão.
R .. GitHub Pare de ajudar o gelo
2
Eu acho size_tque só teria que ser tão grande se o compilador pudesse aceitar um tipo X tal que sizeof (X) renderia um valor maior que 4G. A maioria dos compiladores rejeitaria typedef unsigned char foo[1000000000000LL][1000000000000LL], por exemplo , e foo[65536][65536];poderia mesmo ser legitimamente rejeitada se excedesse um limite definido pela implementação documentado.
precisa
1
@MattJoiner: A redação está correta. "Objeto" não é vago, mas definido como "região de armazenamento".
Lightness Races in Orbit
4

Este trecho do manual glibc 0.02 também pode ser relevante ao pesquisar o tópico:

Há um problema em potencial com o tipo size_t e as versões do GCC anteriores à liberação 2.4. O ANSI C exige que size_t sempre seja um tipo não assinado. Para compatibilidade com os arquivos de cabeçalho dos sistemas existentes, o GCC define size_t em stddef.h' to be whatever type the system'ssys / types.h 'como ele é. A maioria dos sistemas Unix que definem size_t em `sys / types.h ', definem como um tipo assinado. Algum código na biblioteca depende do tamanho_t ser um tipo não assinado e não funcionará corretamente se estiver assinado.

O código da biblioteca GNU C que espera que size_t não seja assinado está correto. A definição de size_t como um tipo assinado está incorreta. Planejamos que na versão 2.4, o GCC sempre defina size_t como um tipo não assinado e o fixincludes' script will massage the system'ssys / types.h 'para não entrar em conflito com isso.

Enquanto isso, resolvemos esse problema dizendo ao GCC explicitamente para usar um tipo não assinado para size_t ao compilar a biblioteca GNU C. O `configure 'detectará automaticamente o tipo que o GCC usa para size_t organizar para substituí-lo, se necessário.

Graeme Burke
fonte
2

Se meu compilador estiver definido como 32 bits, size_tnão será outro senão um typedef para unsigned int. Se meu compilador estiver definido como 64 bits, size_tnada mais será do que um typedef para unsigned long long.

Zebrafish
fonte
1
Pode ser definido apenas unsigned longpara os dois casos em alguns sistemas operacionais.
StaceyGirl 17/08/19
-4

size_t é o tamanho de um ponteiro.

Portanto, em 32 bits ou no modelo comum ILP32 (inteiro, longo, ponteiro) size_t é 32 bits. e em 64 bits ou no modelo LP64 (longo, ponteiro) comum size_t é de 64 bits (números inteiros ainda são 32 bits).

Existem outros modelos, mas esses são os que o g ++ usa (pelo menos por padrão)


fonte
15
size_tnão é necessariamente do mesmo tamanho que um ponteiro, embora geralmente seja. Um ponteiro deve ser capaz de apontar para qualquer local na memória; size_tsó precisa ser grande o suficiente para representar o tamanho do maior objeto único.
Keith Thompson