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?

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.


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).


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).


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.


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


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).


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.


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.