fredericopissarra Posted March 4, 2019 at 02:45 PM Share Posted March 4, 2019 at 02:45 PM Aqui quero só apontar alguns conceitos que o estudante pode ter entendido mal. Um deles é a diferença entre tipos inteiros sinalizados e sem sinal. AMBOS são a mesma coisa, embora não pareçam... Existe, é claro uma diferença: A possibilidade de trabalhar com valores negativos. Mas, internamente, esses dois "tipos" comportam-se do mesmo jeito. Por exemplo: Ao subtrair 1 de 0 obteremos, se estivermos trabalhando com tipos sinalizados, -1. Mas, para um tipo sem sinal, obteremos todos os bits do valor setados. Isso é fácil de ver: #include <stdio.h> void main ( void ) { int x = 0; unsigned int y = 0; x--; y--; printf ( "x=%1$d (%1$#x), y=%2$u (%2$#x)\n", x, y ); } Isso imprimirá "x=-1 (0xffffffff), y=4294967295 (0xffffffff)", o que significa que int e unsigned int funcionam do mesmo jeito. De fato, ao subtrair 1 de 0, em binário, obteremos todos os bits setados e o flag CF também setado: $ cat test.c #include <stdio.h> __attribute__((noinline)) int f(int x) { __asm__ __volatile__( "subl $1,%0" : "=r" (x) : "0" (x) ); return x; } void main ( void ) { printf ( "x=%1$d (%1$#x)\n", f(0) ); } $ cc -g -O2 -o test test.c $ gdb -q test Reading symbols from test...done. (gdb) b f Breakpoint 1 at 0x6a0: file test.c, line 6. (gdb) disas f Dump of assembler code for function f: 0x00000000000006a0 <+0>: mov %edi,%eax 0x00000000000006a2 <+2>: sub $0x1,%eax 0x00000000000006a5 <+5>: retq End of assembler dump. (gdb) r Starting program: /mnt/vol2/Work/tmp/test Breakpoint 1, f (x=0) at test.c:6 6 __asm__ __volatile__( (gdb) n 12 } (gdb) info reg rax 0xffffffff 4294967295 rbx 0x0 0 rcx 0x5555555546b0 93824992233136 rdx 0x7fffffffdd08 140737488346376 rsi 0x7fffffffdcf8 140737488346360 rdi 0x0 0 rbp 0x5555555546b0 0x5555555546b0 <__libc_csu_init> rsp 0x7fffffffdc08 0x7fffffffdc08 r8 0x7ffff7dd0d80 140737351847296 r9 0x7ffff7dd0d80 140737351847296 r10 0x2 2 r11 0x7 7 r12 0x555555554590 93824992232848 r13 0x7fffffffdcf0 140737488346352 r14 0x0 0 r15 0x0 0 rip 0x5555555546a5 0x5555555546a5 <f+5> eflags 0x297 [ CF PF AF SF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 (gdb) Troque 'int' por 'unsigned int' e você obterá a mesma coisa. O que o compilador faz é interpretar os tipos 'int' como representandos valores em complemento 2. Assim, o valor absoluto contido na variável tem apenas N-1 bits e depende do MSB (Bit de alta ordem)... Mas, repare, o valor contido no tipo continua sendo um grupo de N bits sem qualquer indicação de "sinal". O sinal aqui é uma representação, cuja semântica é obtida no programa e pelos flags. Os flags, é claro, são usados para comparações. Com valores sinalizados usamos os flags SF e OF, bem como ZF. Com valores sem sinal, usamos os flags CF e ZF... No caso do x86, o flag SF é setado se o resultado da operação resultar num MSB=1 (como 0-1 resulta no bit MSB setado!), e o flag OF é uma indicação de overflow, ou seja, nos extremos das faixas sinalizadas... se passarmos de 0x7fffffff para 0x80000000, então OF=1 e o contrário também... Mas, note que isso não acontece se passarmos de 0 para 0xffffffff e vice versa (como pode ser visto acima). Isso seria um "underflow". Toda comparação é feita por subtração. Se comparamos x com y, se x == y, o ZF será 1, significando que a diferença entre eles é 0. Isso vale para os tipos sinalizados e não sinalizados, mas para comparações de inequalidades (menor que ou maior que), se os tipos forem sinalizados temos que usar AMBOS os flags SF e OF, e também ZF. Repare que, com 0-1 obtivemos SF=1, OF=0 e ZF=0. Uma comparação de "maior que" resulta sempre em SF!=OF e ZF=0. O processador tem um atalho para esse tipo de salto condicional: JL (Jump if less than)... Se quisermos comparar com "maior ou igual" a lógica fica SF!=OF ou ZF=1, que também existe um atalho: JLE. Para comparações "maior que", SF==OF. No caso de valores sem sinal, precisamos apenas do CF e ZF. Como visto acima, se x < y, CF=1 e ZF=0, o processador tem um atalho: JB. Já se quisermos testar se x <= y podemos usar apenas CF, mesmo assim existe um "apelido" para JC: JBE. Por que estou falando disso? Por causa de uma prática que vejo por ai com o uso de ponteiros... Alguns acreditam que existe uma diferença fundamental entre 'char' e 'unsigned char', onde o último representa 1 byte exatamente... AMBOS representam 1 byte exatamente, a diferença está apenas nas comparações entre variáveis desses tipos, mas a cópia e a aritmética inteira entre eles é exatamente a mesma. Declarar um ponteiro do tipo 'unsigned char *' ou 'char *', se não for fazer comparações entre os dados apontados, é exatamente a mesma coisa. O mesmo se aplica a 'short', 'int', 'long', 'long long'! Repare que não há distinção, também, na aritmética com ponteiros: char array[10]; char *p1 = array; unsigned char *p2 = ( unsigned char * ) array; p1++; // aponta para array[1]; p2++ // também aponta para array[1]! Nada te impede de fazer um casting na comparação, se precisar, quando tem arrays para char e quer tratar seus elementos como "bytes": if ( ( unsigned )array[0] < array[1] ) ... Como a promoção de tipos encara o modificador 'unsigned' com maior prioridade do que o 'signed' (porque tem 1 bit de precisão extra), toda a expressão será promovida para 'unsigned'... Outro detalhe é que ponteiros são, por definição, 'unsigned'. Ao comparar dois ponteiros o compilador preferirá usar instruções como JA, JB, JAE ou JBE, no caso de comparações de inequalidades (menor que, maior que e usando ou não o "ou igual"). Alguns podem pensar que a arquitetura x86-64 seja uma exceção a essa regra, já que os endereços têm que ser "canônicos" (ou seja, se o bit 47 do endereço for 1, todos os demais, até o MSB, tem que ser 1 também!). Mas, note que o endereço canônico não é uma representação em "complemento 2" e, por isso, as comparações continuam sendo feitas de maneira 'unsigned' (CF e ZF são usados, não SF e OF). Por exemplo: $ cat test.c int test( void *a, void *b ) { return a < b; } $ cc -O2 -c -o test.o test.c $ objdump -dM intel test.o test.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <test>: 0: 31 c0 xor eax,eax 2: 48 39 f7 cmp rdi,rsi 5: 0f 92 c0 setb al 8: c3 ret Repare no 'setb'...ATENÇÃO: Note que estou sendo enfático em dizer que a comparação entre tipos é diferente. Se você tentar comparar um tipo 'unsigned' com um 'signed', poderá obter resultados indesejados, se não souber o que está fazendo... Como eu disse antes, o compilador C promove o tipo de signed para unsigned... Assim, comparações como: if ( x > -1 ) ... Se x for 'unsigned', são perigosas... Esse -1 será promovido para 0xffffffff, que é o valor máximo de um 'int' (por exemplo) e x jamais poderá ser maior que isso... Ou seja, a expressão sempre será FALSA! A indiferença dos tipos 'unsigned' e 'signed' aqui refere-se ao tamanho do armazenamento... []s Fred Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.