uint8_t vs char não assinado

231

Qual é a vantagem de usar uint8_tmais unsigned charem C?

Eu sei que em quase todos os sistemas uint8_té apenas um typedef unsigned char, então por que usá-lo?

Lyndon White
fonte

Respostas:

225

Ele documenta sua intenção - você armazenará pequenos números, em vez de um personagem.

Também parece melhor se você estiver usando outros typedefs como uint16_tou int32_t.

Mark Ransom
fonte
1
Não ficou claro na pergunta original se estávamos falando sobre um tipo padrão ou não. Tenho certeza de que houve muitas variações dessa convenção de nomes ao longo dos anos.
Mark Ransom
8
Usar unsigned charou signed chardocumentar explicitamente a intenção também, uma vez que sem adornos charé o que mostra que você está trabalhando com personagens.
Caf
9
Eu pensei que um sem adornos unsignedera unsigned intpor definição?
Mark Ransom
5
@ endolith, usar uint8_t para uma string não é necessariamente errado, mas é definitivamente estranho.
Mark Ransom
5
@ Endolith, acho que posso defender uint8_t com texto UTF8. De fato, charparece implicar um caractere, enquanto que no contexto de uma string UTF8, pode ser apenas um byte de um caractere multibyte. O uso de uint8_t pode deixar claro que não se deve esperar um caractere em todas as posições - em outras palavras, que cada elemento da string / array é um número inteiro arbitrário sobre o qual não se deve fazer suposições semânticas. É claro que todos os programadores C sabem disso, mas pode ser necessário que os iniciantes façam as perguntas certas.
tne
70

Apenas para ser pedante, alguns sistemas podem não ter um tipo de 8 bits. De acordo com a Wikipedia :

É necessária uma implementação para definir tipos inteiros de largura exata para N = 8, 16, 32 ou 64 se e somente se tiver algum tipo que atenda aos requisitos. Não é necessário defini-los para nenhum outro N, mesmo que ele suporte os tipos apropriados.

Portanto, uint8_tnão existe garantia de existência, embora exista para todas as plataformas em que 8 bits = 1 byte. Algumas plataformas incorporadas podem ser diferentes, mas isso está ficando muito raro. Alguns sistemas podem definir chartipos como 16 bits; nesse caso, provavelmente não haverá um tipo de 8 bits de qualquer tipo.

Fora essa questão (menor), a resposta de @Mark Ransom é a melhor na minha opinião. Use o que mostra mais claramente para o que você está usando os dados.

Além disso, suponho que você quis dizer uint8_t(o typedef padrão do C99 fornecido no stdint.hcabeçalho) e não uint_8(não faz parte de nenhum padrão).

Chris Lutz
fonte
3
@caf, por pura curiosidade - você pode criar um link para a descrição de alguns? Eu sei que eles existem porque alguém mencionou um (e vinculado a documentos do desenvolvedor) em uma discussão moderada comp.lang.c ++. Sobre se as garantias do tipo C / C ++ são muito fracas, mas eu não consigo mais encontrar esse segmento e é sempre útil a referência de que em todas as discussões semelhantes :)
Pavel Minaev
3
"Alguns sistemas podem definir tipos de caracteres como 16 bits; nesse caso, provavelmente não haverá um tipo de 8 bits". - e, apesar de algumas objeções incorretas da minha parte, Pavel demonstrou em sua resposta que, se char é de 16 bits, mesmo que o compilador forneça um tipo de 8 bits, ele não deve chamá-lo uint8_t(ou digitar para isso). Isso ocorre porque o tipo de 8 bits teria bits não utilizados na representação de armazenamento, o que uint8_tnão deve ter.
Steve Jessop
3
A arquitetura SHARC possui palavras de 32 bits. Veja en.wikipedia.org/wiki/… para detalhes.
BCRAN
2
E os DSPs C5000 da TI (que estavam no OMAP1 e OMAP2) têm 16 bits. Eu acho que para o OMAP3 eles foram para a série C6000, com um caractere de 8 bits.
13139 Steve Jessop
4
Indo para o N3242 - "Rascunho de trabalho, padrão para a linguagem de programação C ++", seção 18.4.1 sinopse da <cstdint> diz - typedef unsigned integer type uint8_t; // optional Portanto, em essência, uma biblioteca em conformidade com o padrão C ++ não é necessária para definir o uint8_t (consulte o comentário // opcional )
nightlytrails 23/02
43

O ponto principal é escrever código independente de implementação. unsigned charnão é garantido que seja do tipo 8 bits. uint8_té (se disponível).

Formiga
fonte
4
... se existir em um sistema, mas isso será muito raro. 1
Chris Lutz
2
bem, se você realmente teve problemas com o código não compilado em um sistema porque o uint8_t não existia, você poderia usar o find e sed para alterar automaticamente todas as ocorrências do uint8_t para char não assinado ou algo mais útil para você.
bazz
2
@azzazz - não se você estiver assumindo que é um tipo de 8 bits que não pode - por exemplo, descompactar dados empacotados de forma bytewise por um sistema remoto. A suposição implícita é que a razão para a inexistência de uint8_t está em um processador em que um caractere é superior a 8 bits.
22815 Chris Stratton
jogar na asserção asserção (sizeof (char não assinado) == 8);
bazz
3
@bazz afirmação incorreta, eu tenho medo. sizeof(unsigned char)retornará 1por 1 byte. mas se um char int sistema e são do mesmo tamanho de, por exemplo, 16 bits, então sizeof(int)irá também retornar1
Toby
7

Como você disse, " quase todos os sistemas".

charprovavelmente é um dos menos propensos a mudar, mas assim que você começar a usar os uint16_tamigos, o uint8_tmelhor será usar as combinações e até fazer parte de um padrão de codificação.

Apenas apaixonado
fonte
7

Na minha experiência, existem dois lugares em que queremos usar uint8_t para significar 8 bits (e uint16_t, etc) e onde podemos ter campos menores que 8 bits. Ambos os locais são onde o espaço é importante e, muitas vezes, precisamos examinar um despejo bruto dos dados durante a depuração e precisamos determinar rapidamente o que eles representam.

O primeiro está nos protocolos de RF, especialmente em sistemas de banda estreita. Nesse ambiente, talvez seja necessário agrupar o máximo de informações possível em uma única mensagem. O segundo é no armazenamento flash, onde podemos ter espaço muito limitado (como em sistemas embarcados). Nos dois casos, podemos usar uma estrutura de dados compactada na qual o compilador cuidará da embalagem e descompactação para nós:

#pragma pack(1)
typedef struct {
  uint8_t    flag1:1;
  uint8_t    flag2:1;
  padding1   reserved:6;  /* not necessary but makes this struct more readable */
  uint32_t   sequence_no;
  uint8_t    data[8];
  uint32_t   crc32;
} s_mypacket __attribute__((packed));
#pragma pack()

Qual método você usa depende do seu compilador. Você também pode precisar oferecer suporte a vários compiladores diferentes com os mesmos arquivos de cabeçalho. Isso acontece em sistemas incorporados nos quais dispositivos e servidores podem ser completamente diferentes - por exemplo, você pode ter um dispositivo ARM que se comunica com um servidor Linux x86.

Existem algumas ressalvas no uso de estruturas compactadas. O maior problema é que você deve evitar a exclusão do endereço de um membro. Em sistemas com palavras alinhadas por mutibytes, isso pode resultar em uma exceção desalinhada - e em um coredump.

Algumas pessoas também se preocupam com o desempenho e argumentam que o uso dessas estruturas compactadas tornará o sistema mais lento. É verdade que, nos bastidores, o compilador adiciona código para acessar os membros de dados não alinhados. Você pode ver isso observando o código de montagem no seu IDE.

Porém, como as estruturas compactadas são mais úteis para comunicação e armazenamento de dados, os dados podem ser extraídos para uma representação não compactada ao trabalhar com eles na memória. Normalmente, não precisamos trabalhar com todo o pacote de dados na memória.

Aqui está uma discussão relevante:

pacote pragma (1) nem __attribute__ ((alinhado (1))) funciona

O __attribute __ ((empacotado)) / #pragma do gcc não é seguro?

http://solidsmoke.blogspot.ca/2010/07/woes-of-structure-packing-pragma-pack.html

Tereus Scott
fonte
6

Há pouco. Do ponto de vista da portabilidade, charnão pode ser menor que 8 bits e nada pode ser menor que char, portanto, se uma determinada implementação C tiver um tipo inteiro de 8 bits não assinado, será char. Como alternativa, ele pode não ter um, nesse ponto qualquer typedeftruque é discutível.

Poderia ser usado para documentar melhor seu código, no sentido de que é claro que você precisa de bytes de 8 bits e nada mais. Mas, na prática, já é uma expectativa razoável em praticamente qualquer lugar (existem plataformas DSP nas quais isso não é verdade, mas as chances de seu código ser executado lá são reduzidas e você também pode errar usando uma declaração estática na parte superior do programa em tal plataforma).

Pavel Minaev
fonte
7
@Skizz - Não, o padrão exige unsigned charque seja possível manter valores entre 0 e 255. Se você puder fazer isso em 4 bits, meu chapéu está para você.
31730 Chris Lutz
1
"seria um pouco mais pesado" - pesado no sentido de que você teria que andar (nadar, pegar um avião etc.) até onde estava o escritor do compilador, dar um tapa na parte de trás da cabeça e faça com que eles sejam adicionados uint8_tà implementação. Gostaria de saber, compiladores para DSPs com caracteres de 16 bits normalmente implementam uint8_t, ou não?
Steve Jessop
6
A propósito, pensando bem, talvez seja a maneira mais direta de dizer "eu realmente preciso de 8 bits" - #include <stdint.h>e usar uint8_t. Se a plataforma possuir, ela será entregue a você. Se a plataforma não a possuir, seu programa não será compilado e o motivo será claro e direto.
Pavel Minaev 12/11/2009
2
Ainda sem charuto, desculpe: "Para tipos inteiros não assinados que não sejam caracteres não assinados, os bits da representação do objeto devem ser divididos em dois grupos: bits de valor e bits de preenchimento ... Se houver N bits de valor, cada bit representará um valor diferente. potência de 2 entre 1 e 2 ^ (N-1), para que objetos desse tipo sejam capazes de representar valores de 0 a 2 ^ (N-1) usando uma representação binária pura ... O nome do typedef intN_t designa um tipo inteiro assinado com largura N, sem bits de preenchimento e representação de complemento de dois. "
Pavel Minaev 13/11/2009
1
Se você precisar apenas de módulo aritmético, o campo de bits não assinado funcionará perfeitamente (se for inconveniente). É quando você precisa, digamos, de uma variedade de octetos sem preenchimento, é quando você está SOL. Moral da história não é código para DSPs, e manter, arquiteturas de char adequadas honesto-a-Deus de 8 bits :)
Pavel Minaev
4

Isso é realmente importante, por exemplo, quando você está escrevendo um analisador de rede. os cabeçalhos de pacotes são definidos pela especificação do protocolo, não pela maneira como o compilador C de uma determinada plataforma funciona.

VP.
fonte
Quando perguntei isso, eu estava definindo um protocolo simples para comunicação via serial.
Lyndon Branco
2

Em quase todos os sistemas, encontrei uint8_t == char não assinado, mas isso não é garantido pelo padrão C. Se você está tentando escrever um código portátil e importa exatamente qual o tamanho da memória, use uint8_t. Caso contrário, use char não assinado.

atlpeg
fonte
3
uint8_t sempre corresponde ao intervalo, tamanho unsigned chare preenchimento (nenhum) quando unsigned char é de 8 bits. Quando unsigned charnão é de 8 bits, uint8_tnão existe.
chux - Restabelece Monica
@chux, você tem uma referência ao local exato no padrão em que diz isso? Se unsigned charé de 8 bits, é uint8_tgarantida a ser um typedefdos mesmos e não uma typedefde um tipo inteiro não assinado prolongado ?
hsivonen
@hsivonen "local exato no padrão em que diz isso?" -> Não - ainda veja 7.20.1.1. É facilmente deduzido como unsigned char/signed char/charo menor tipo - não inferior a 8 bits. unsigned charnão tem preenchimento. Para uint8_tser, ele deve ter 8 bits, sem preenchimento, devido a um tipo inteiro fornecido pela implementação: correspondendo aos requisitos mínimos de unsigned char. Quanto a "... garantido ser um typedef ...", parece uma boa pergunta para postar.
chux - Restabelece Monica