Os piores efeitos colaterais de Chars assinam. (Explicação dos efeitos de assinatura em chars e elencos)

StackOverflow https://stackoverflow.com/questions/2192880

Pergunta

Frequentemente trabalho com bibliotecas que usam char ao trabalhar com bytes no C ++. A alternativa é definir um "byte" como char não assinado, mas não o padrão que eles decidiram usar. Frequentemente, passo bytes de C# para as DLLs C ++ e os funddo para Char para trabalhar com a biblioteca.

Ao lançar ints a chars ou chars para outros tipos simples, quais são alguns dos efeitos colaterais que podem ocorrer. Especificamente, quando esse código quebrado em que você trabalhou e como descobriu que foi por causa da assinatura do char?

Sorte que eu não encontrei isso no meu código, usei um truque de fundição assinado em uma aula de sistemas incorporados na escola. Estou procurando entender melhor o problema, pois acho que é relevante para o trabalho que estou fazendo.

Foi útil?

Solução

Um grande risco é se você precisar mudar os bytes. Um char assinado mantém o bit de sinalização quando deslocado à direita, enquanto um char não assinado não. Aqui está um pequeno programa de teste:

#include <stdio.h>

int main (void)
{
    signed char a = -1;
    unsigned char b = 255;

    printf("%d\n%d\n", a >> 1, b >> 1);

    return 0;
}

Ele deve imprimir -1 e 127, mesmo que A e B comecem com o mesmo padrão de bits (dado os caracteres de 8 bits, o complemento de dois e os valores assinados usando o turno aritmético).

Em resumo, você não pode confiar no turno de trabalho de forma idêntica para chars assinados e não assinados; portanto, se você precisar de portabilidade, use unsigned char ao invés de char ou signed char.

Outras dicas

Os gotchas mais óbvios chegam quando você precisa comparar o valor numérico de um char com uma constante hexadecimal ao implementar protocolos ou esquemas de codificação.

Por exemplo, ao implementar o Telnet, você pode querer fazer isso.

// Check for IAC (hex FF) byte
if (ch == 0xFF)
{
    // ...

Ou ao testar sequências de vários bytes UTF-8.

if (ch >= 0x80)
{
    // ...

Felizmente, esses erros geralmente não sobrevivem muito tempo, mesmo os testes mais superficiais em uma plataforma com um assinado char deve revelá -los. Eles podem ser consertados usando uma constante de caracteres, convertendo a constante numérica em um char ou converter o personagem em um unsigned char antes do operador de comparação promove ambos para um int. Convertendo o char diretamente para um unsigned Não vai funcionar, no entanto.

if (ch == '\xff')               // OK

if ((unsigned char)ch == 0xff)  // OK, so long as char has 8-bits

if (ch == (char)0xff)           // Usually OK, relies on implementation defined behaviour

if ((unsigned)ch == 0xff)       // still wrong

Fui mordido pela assinatura de Char ao escrever algoritmos de pesquisa que usavam caracteres do texto como índices em árvores de estado. Eu também tive isso causando problemas ao expandir os caracteres em tipos maiores, e o bit de sinal se propaga causando problemas em outros lugares.

Descobri quando comecei a obter resultados bizarros, e segfaults decorrentes de pesquisar textos que não fossem os que eu usei durante o desenvolvimento inicial (obviamente caracteres com valores> 127 ou <0 vão causar isso e não será necessariamente será necessariamente presente em seus arquivos de texto típicos.

Sempre verifique a assinatura de uma variável ao trabalhar com ela. Geralmente, agora faço tipos assinados, a menos que tenha um bom motivo de outra forma, lançando quando necessário. Isso se encaixa bem com o uso onipresente de char nas bibliotecas para simplesmente representar um byte. Lembre -se de que a assinatura de char não é definido (diferentemente de outros tipos), você deve dar um tratamento especial e esteja atento.

Aquele que mais me irrita:

typedef char byte;

byte b = 12;

cout << b << endl;

Claro que é cosméticos, mas arrr ...

Ao lançar ints a chars ou chars para outros tipos simples

O ponto crítico é que lançar um valor assinado de um tipo primitivo para outro (maior) do tipo) não retém o padrão de bits (assumindo o complemento de dois). Um char assinado com padrão de bit 0xff é -1, enquanto um assinado com o valor decimal -1 é 0xffff. Lançar um char não assinado com valor 0xff Para um curta não assinado, no entanto, rende 0x00ff. Portanto, sempre pense na assinatura adequada antes de digitar um tipo de dados maior ou menor. Nunca carregue dados não assinados em tipos de dados assinados, se você não precisar - Se uma biblioteca externa forçar você a fazê -lo, faça a conversão o mais tarde possível (ou o mais cedo possível se o código externo funcionar como fonte de dados).

As especificações do idioma C e C ++ definem 3 tipos de dados para manter os caracteres: char, signed char e unsigned char. Os últimos 2 foram discutidos em outras respostas. Vamos olhar para o char modelo.

Os padrão (s) dizem que o char tipo de dados poderia ser assinado ou não assinado e é uma decisão de implementação. Isso significa que alguns compiladores ou versões dos compiladores podem implementar char diferentemente. A implicação é que o char O tipo de dados não é propício para operações aritméticas ou booleanas. Para operações aritméticas e booleanas, signed e unsigned versões de char vai funcionar bem.

Em resumo, existem 3 versões de char tipo de dados. o char O tipo de dados tem um bom desempenho para manter caracteres, mas não é adequado para aritmética em plataformas e tradutores, pois é assinatura é implementação definida.

Você falhará miseravelmente ao compilar para várias plataformas porque o padrão C ++ não define char ser de uma certa "assinatura".

Portanto, o GCC apresenta -fsigned-char e -funsigned-char opções para forçar certos comportamentos. Mais sobre esse tópico pode ser encontrado aqui, por exemplo.

EDITAR:

Como você solicitou exemplos de código quebrado, existem muitas possibilidades para quebrar o código que processa dados binários. Por exemplo, a imagem você processa amostras de áudio de 8 bits (faixa de -128 a 127) e deseja pela metade o volume. Agora imagine este cenário (no qual o programador ingênuo assume char == signed char):

char sampleIn;

// If the sample is -1 (= almost silent), and the compiler treats char as unsigned,
// then the value of 'sampleIn' will be 255
read_one_byte_sample(&sampleIn);

// Ok, halven the volume. The value will be 127!
char sampleOut = sampleOut / 2;

// And write the processed sample to the output file, for example.
// (unsigned char)127 has the exact same bit pattern as (signed char)127,
// so this will write a sample with the loudest volume!!
write_one_byte_sample_to_output_file(&sampleOut);

Espero que gostem desse exemplo ;-) Mas, para ser sincero, nunca me deparei com esses problemas, nem mesmo como iniciante, tanto quanto me lembro ...

Espero que esta resposta seja suficiente para você desviar. Que tal um breve comentário?

Extensão de sinal. A primeira versão da minha função de codificação de URL produziu strings como "%ffffffa3".

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top