fredericopissarra Posted March 15, 2019 Posted March 15, 2019 Escrevo muito sobre "escovação de bits" e, de tempos em tempos, topo com alguma pergunta interessante. Dessas perguntas vieram esses dois detalhes que o estudante assume como verdade:O resultado de uma expressão booleana é sempre 0 ou 1 Tá certo que qualquer valor diferente de zero pode ser interpretado com verdadeiro, mas estou falando do resultado de uma expressão. Esse fato é citado na especificação ISO 9989, pelo menos de 1999 em diante (acredito que é assim desde a versão ANSI) e cria opotunidades interessantes. Por exemplo: Suponha que você queira fazer uma função que será usada para decidir se um valor a é maior que, menor que ou igual a um valor b. Pode-se fazer algo assim: int compare( int a, int b ) { if ( a > b ) return 1; else if ( a < b ) return -1; return 0; } A rotina acima, otimizada, e na arquitetura x86-64 fica assim: compare: xor eax,eax cmp edi,esi mov edx,1 setl al neg eax cmp edi,esi cmovg eax,edx ret No entanto, (a > b) e (a < b) são expressões booleanas e seus resultados só podem ser 0 ou 1... A mesma rotina pode ser escrita, sem nenhuma penalidade, assim: int compare( int a, int b ) { return ( a > b ) - ( a < b ); } Sabendo da regra do 0 ou 1, interpretar a função acima é simples! Veja agora o código gerado: compare: xor eax,eax cmp edi,esi setl dl setg al movzx edx,dl sub eax,edx ret Dá pra ver que a segunda rotina tem menos operações aritméticas e que ela é menor que a anterior e, portanto, ligeiramente mais rápida? Talvez isso não te convença, mas mudemos um pouco a rotina para usar ponteiros, como na rotina necessária para uso da função da stdlib.h, qsort(): int compare1 ( int *a, int *b ) { return ( *a > *b ) ? 1 : ( *a < *b ) ? -1 : 0; } int compare2 ( int *a, int *b ) { return ( *a > *b ) - ( *a < *b ); } Eis ambos os códigos, lado a lado: compare1: compare2: mov edx,[rsi] mov edx,[rsi] cmp [rdi],edx mov ecx,[rdi] mov eax,1 xor eax,eax jg .L1 cmp ecx,edx setl al setl dl movzx eax,al setg al neg eax movzx edx,dl .L1: sub eax,edx ret ret Agora temos um salto condicional "para frente", que viola o algoritmo de branch prediction estático. A rotina de compare2() não tem nenhum salto... Isso, por si só, torna a rotina compare2() de 30% a quase 70% mais rápida que compare1() -- medido! Esse mesmo tipo de macete pode ser feito para eliminar a necessidade de algum if, como em: if ( x > 0 ) y++; // perfeitamente substituível por: y += (x > 0); O código gerado não será muito diferente, mas em certos casos fica melhor... Constantes, entre ' e ', não são do tipo char! Você pode estar acostumado a fazer algo assim para inicializar uma variável do tipo char: char ch = 'a'; De fato, se o tipo char tiver 8 bits de tamanho o código (de 8 bits) do caracter 'a' será colocado na variável a como você queria. Mas, de acordo com a especificação ISO 9989, uma constante entre ' é sempre do tipo int, ou seja, nos x86 ela sempre tem 32 bits de tamanho... O que acontece no fragmento acima é que o int 'a' é convertido para o tipo char antes de ser colocado dentro de a. A especificação ainda nos diz que uma construção como abaixo é perfeitamente válida: int x = 'abcd'; Contanto que o que vá entre ' tenha exatamente o tamanho de um int, o compilador só avisará, mas a construção é legal. Por que o compilador avisa? Criar uma constante desse tipo, multibyte, não é portável. O que está entre ' vai seguir o padrão little endian ou big endian? A ordem vai ser respeitada? No caso, se vocẽ imprimir x, o que obterá? $ gcc -xc -include stdio.h - <<EOF void main( void ) { int x = 'abcd'; printf( "%#x\n", x ); } EOF <stdin>: In function ‘main’: <stdin>:1:29: warning: multi-character character constant [-Wmultichar] $ ./a.out 0x61626364 Eis o aviso (warning) e eis o resultado no GCC. Ele segue a ordem little endian, colocando o último caracter na frente, ou seja, a "string" é invertida. Assim, se eu quiser que x contenha os caracteres para a string "fred", tenho que fazer int x = 'derf'; Mas, de novo, isso não é portável e deve ser evitado. Mas, existe outro motivo pelo uso do int... Uma das primeiras funções para leitura de um stream (arquivo) foi fgetc(), que pode devolver um "caracter" ou EOF... Porque EOF é um símbolo com valor especial, ele não pode ter 8 bits de tamanho. Assim, o valor -1, inteiro, foi escolhido para representar a condição de "fim de arquivo". E um "caracter" passou, por convenção, a ser um int. O mesmo funciona com o retorno de fscanf()... retorno de EOF (-1) ou a quantidade de itens convertidos (>= 0). Há, ainda, mais um motivo... Considere o charset UTF-8... caracteres como 'ç' possuem 2 bytes de tamanho: "\xc3\xa7". Embora a codificação de um caracter, usando UTF-8, possa ter de 1 até 5 bytes, faz todo sentido usar 4 bytes para uma constante entre 's, para a maioria dos caracteres.
Recommended Posts
Archived
This topic is now archived and is closed to further replies.