1352 lines
47 KiB
Org Mode
1352 lines
47 KiB
Org Mode
|
#+title: 9 -- Conversão de strings numéricas para inteiros
|
|||
|
#+author: Blau Araujo
|
|||
|
#+email: cursos@blauaraujo.com
|
|||
|
|
|||
|
#+options: toc:3
|
|||
|
|
|||
|
* Objetivos
|
|||
|
|
|||
|
- Diferenciar as expressões numéricas em texto dos números que representam.
|
|||
|
- Conhecer a relação entre tipos numéricos e tamanhos de dados.
|
|||
|
- Criar sub-rotinas reaproveitáveis para conversões de caracteres para números.
|
|||
|
|
|||
|
* Todos os dados externos são recebidos como caracteres
|
|||
|
|
|||
|
Se consideramos como /dados externos/:
|
|||
|
|
|||
|
- Argumentos de linha de comando;
|
|||
|
- Variáveis exportadas para o ambiente do processo;
|
|||
|
- Dados digitados pelo usuário;
|
|||
|
- Dados lidos por redirecionamentos e pipes;
|
|||
|
- Dados lidos de arquivos em geral...
|
|||
|
|
|||
|
Todos eles têm em comum o fato de serem recebidos nos nossos programas
|
|||
|
como cadeias de caracteres, mesmo que representem números. Consequentemente,
|
|||
|
para utilizá-los em operações numéricas, será necessário convertê-los nos
|
|||
|
valores que representam.
|
|||
|
|
|||
|
** Entendendo o problema
|
|||
|
|
|||
|
Para entendermos melhor o problema, vamos analisar o que acontece no programa
|
|||
|
abaixo (=exemplo.c=) com o GDB.
|
|||
|
|
|||
|
Arquivo: [[exemplos/09/exemplo.c][exemplo.c]]
|
|||
|
|
|||
|
#+begin_src c :tangle exemplos/09/exemplo.c
|
|||
|
#include <stdio.h>
|
|||
|
|
|||
|
int main(void) {
|
|||
|
|
|||
|
int a = 123;
|
|||
|
char *b = "123";
|
|||
|
int *c = (int *)b; // Casting do valor no endereço 'b' para inteiro
|
|||
|
|
|||
|
printf("Inteiro a: %d\n", a);
|
|||
|
printf("String b: %s\n", b);
|
|||
|
printf("Inteiro c: %d\n", *c); // Imprime o valor no endereço 'c'
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
#+end_src
|
|||
|
|
|||
|
*Compilação e execução:*
|
|||
|
|
|||
|
#+begin_example
|
|||
|
:~$ gcc -g exemplo.c
|
|||
|
:~$ ./a.out
|
|||
|
Inteiro a: 123
|
|||
|
String b: 123
|
|||
|
Inteiro c: 3355185
|
|||
|
#+end_example
|
|||
|
|
|||
|
Note que o ponteiro =c=, que recebeu endereço dos bytes da string no ponteiro
|
|||
|
=b= (="123"=), aponta para um valor inteiro totalmente diferente do que está
|
|||
|
representado pelos dígitos da string (o inteiro =3355185=).
|
|||
|
|
|||
|
*Abertura e execução com o GDB:*
|
|||
|
|
|||
|
Para examinar os conteúdo da memória em hexadecimal, nós vamos marcar a
|
|||
|
unção =main= como ponto de parada, executar o programa e avançar a execução
|
|||
|
em 3 linhas, para que as variáveis sejam inciadas, o nos levará ao ponto
|
|||
|
anterior à chamada da função =printf=:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
:~$ gdb a.out
|
|||
|
Reading symbols from a.out...
|
|||
|
(gdb) break main
|
|||
|
Breakpoint 1 at 0x1141: file exemplo.c, line 5.
|
|||
|
(gdb) run
|
|||
|
Starting program: /home/blau/git/pbn/curso/exemplos/09/a.out
|
|||
|
[Thread debugging using libthread_db enabled]
|
|||
|
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
|
|||
|
|
|||
|
Breakpoint 1, main () at exemplo.c:5
|
|||
|
warning: Source file is more recent than executable.
|
|||
|
5 int a = 123;
|
|||
|
(gdb) next 3
|
|||
|
9 printf("Inteiro a: %d\n", a);
|
|||
|
(gdb)
|
|||
|
#+end_example
|
|||
|
|
|||
|
O que queremos observar é a diferença entre os valores numéricos dos três
|
|||
|
dados na memória, como se todos eles fossem inteiros (formato =w= no GDB):
|
|||
|
|
|||
|
#+begin_example
|
|||
|
(gdb) x /1wx &a
|
|||
|
0x7fffffffdecc: 0x0000007b
|
|||
|
(gdb) x /1wx b
|
|||
|
0x555555556004: 0x00333231
|
|||
|
(gdb) x /1wx c
|
|||
|
0x555555556004: 0x00333231
|
|||
|
#+end_example
|
|||
|
|
|||
|
Veja que os bytes da string apontada por 'b' são, como esperado, os mesmos
|
|||
|
do inteiro apontado por 'c', mas ambos são totalmente diferentes do valor
|
|||
|
em hexadecimal do inteiro em =a= (=123=).
|
|||
|
|
|||
|
** A ordem dos bytes
|
|||
|
|
|||
|
Nós também podemos examinar o conteúdo byte a byte desses dados a partir de
|
|||
|
seus endereços iniciais:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
(gdb) x /4bx &a
|
|||
|
0x7fffffffdecc: 0x7b 0x00 0x00 0x00
|
|||
|
(gdb) x /4bx b
|
|||
|
0x555555556004: 0x31 0x32 0x33 0x00
|
|||
|
(gdb) x /4bx c
|
|||
|
0x555555556004: 0x31 0x32 0x33 0x00
|
|||
|
#+end_example
|
|||
|
|
|||
|
Isso ajuda a demonstrar que os bytes de inteiros, como os de qualquer outro
|
|||
|
tipo diferente de =char=, são escritos em ordem inversa (/little endian/) na
|
|||
|
memória. Portanto, quando os bytes da string foram interpretados como os
|
|||
|
4 bytes de um inteiro, eles também foram invertidos:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
(gdb) x /4bx c
|
|||
|
0x555555556004: 0x31 0x32 0x33 0x00
|
|||
|
(gdb) x /1wx c
|
|||
|
0x555555556004: 0x00333231
|
|||
|
#+end_example
|
|||
|
|
|||
|
O tipo =char= é composto por apenas um byte e a ordem /little endian/ só afeta
|
|||
|
a disposição de bytes: é por isso que somente os dados de tipos com dois ou
|
|||
|
mais bytes são invertidos. Além disso, na montagem dos dados na memória,
|
|||
|
o que importa é o tamanho de cada dado, não o que eles formam. Então, como
|
|||
|
strings são formadas por cadeias de caracteres terminadas com o caractere
|
|||
|
nulo (='\0'=), a ordem de seus bytes não é invertida.
|
|||
|
|
|||
|
** Dígitos na tabela ASCII
|
|||
|
|
|||
|
Os valores dos bytes da string ="123"= (além do terminador nulo ='\0'=, de valor
|
|||
|
=0x00=) foram determinados pela convenção de representação de caracteres da
|
|||
|
tabela ASCII:
|
|||
|
|
|||
|
| Caractere | Hexadecimal | Octal | Decimal |
|
|||
|
|-----------+-------------+-------+---------|
|
|||
|
| =\0= | 0x00 | 0 | 0 |
|
|||
|
| =0= | 0x30 | 060 | 48 |
|
|||
|
| =1= | 0x31 | 061 | 49 |
|
|||
|
| =2= | 0x32 | 062 | 50 |
|
|||
|
| =3= | 0x33 | 063 | 51 |
|
|||
|
| =4= | 0x34 | 064 | 52 |
|
|||
|
| =5= | 0x35 | 065 | 53 |
|
|||
|
| =6= | 0x36 | 066 | 54 |
|
|||
|
| =7= | 0x37 | 067 | 55 |
|
|||
|
| =8= | 0x38 | 070 | 56 |
|
|||
|
| =9= | 0x39 | 071 | 57 |
|
|||
|
|
|||
|
Em comparação aos valores dos caracteres em outras bases, a representação em
|
|||
|
hexadecimal é bastante conveniente, pois basta subtrair =0x30= para saber qual
|
|||
|
é o dígito correspondente.
|
|||
|
|
|||
|
** Considerações sobre os tipos numéricos
|
|||
|
|
|||
|
Em baixo nível, nós trabalhamos diretamente com tamanhos de dados. Os tipos
|
|||
|
das linguagens de alto nível, como a linguagem C, são apenas abstrações para
|
|||
|
ajudar a definir o tamanho dos dados e as regras de seus usos. Sendo assim,
|
|||
|
existe uma equivalência entre as formas de representar tamanhos de dados em
|
|||
|
Assembly com os tipos, por exemplo, da linguagem C.
|
|||
|
|
|||
|
Considerando a arquitetura x86_64:
|
|||
|
|
|||
|
| Tipo em C | Tamanho | Tamanho em Assembly |
|
|||
|
|-----------+---------+---------------------|
|
|||
|
| =char= | 1 byte | /byte/ |
|
|||
|
| =short= | 2 bytes | /word/ |
|
|||
|
| =int= | 4 bytes | /double-word/ |
|
|||
|
| =long= | 8 bytes | /quad-word/ |
|
|||
|
| =float= | 4 bytes | /double-word/ |
|
|||
|
| =double= | 8 bytes | /quad-word/ |
|
|||
|
|
|||
|
#+begin_quote
|
|||
|
Esses tamanhos são especificados na ABI do System V AM64, usada pelo Linux
|
|||
|
em arquitetura x86_64 e garantidos, na prática, pelo GCC e em sistemas
|
|||
|
baseados na =glibc=.
|
|||
|
#+end_quote
|
|||
|
|
|||
|
Em Assembly, portanto, definir uma lista de /double-words/ (=dd=) tem o mesmo
|
|||
|
efeito de definir um vetor de inteiros na linguagem C, inclusive na ordem
|
|||
|
em que os bytes são dispostos na memória. Ou então, o dado em um registrador
|
|||
|
de 64 bits, se tomado como um todo, terá o tamanho de uma /quad-word/ e será
|
|||
|
equivalente ao tipo =long= da linguagem C.
|
|||
|
|
|||
|
** Pesos posicionais
|
|||
|
|
|||
|
Falando apenas de inteiros sem sinal por enquanto, cada byte de uma string
|
|||
|
numérica decimal representa um algarismo do número multiplicado pelo seu
|
|||
|
/peso posicional/, por exemplo:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
0x31 0x32 0x33 => '1' '2' '3'
|
|||
|
|
|||
|
0x31 - 0x30 * 100 = 100 (centenas)
|
|||
|
0x32 - 0x30 * 10 = 20 (dezenas)
|
|||
|
0x33 - 0x30 * 1 = 3 (unidades)
|
|||
|
|
|||
|
100 + 20 + 3 = 123
|
|||
|
#+end_example
|
|||
|
|
|||
|
Manualmente, este é um procedimento de conversão bastante simples, mas a sua
|
|||
|
implementação em uma rotina de baixo nível não é tão intuitiva, embora também
|
|||
|
não seja nada muito complicado. O /"pulo do gato"/ está em alcançar os pesos
|
|||
|
posicionais de cada algarismo progressivamente:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
031 0x32 033 => '1' '2' '3'
|
|||
|
|
|||
|
val = 0x31 - 0x30 = 1
|
|||
|
val = (val * 10) + (0x32 - 0x30) = 10 + 2 = 12
|
|||
|
val = (val * 10) + (0x33 - 0x30) = 120 + 3 = 123
|
|||
|
#+end_example
|
|||
|
|
|||
|
Ou, de uma forma mais regular:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
val = 0
|
|||
|
val = (val * 10) + (0x31 - 0x30) = 0 + 1 = 1
|
|||
|
val = (val * 10) + (0x32 - 0x30) = 10 + 2 = 12
|
|||
|
val = (val * 10) + (0x33 - 0x30) = 120 + 3 = 123
|
|||
|
#+end_example
|
|||
|
|
|||
|
Esse procedimento, facilmente implementável em Assembly, contempla tudo que
|
|||
|
vimos sobre o problema:
|
|||
|
|
|||
|
- Percorre os bytes da string na ordem em que estão na memória;
|
|||
|
- Converte os dígitos ASCII para os valores que representam;
|
|||
|
- Aplica os pesos posicionais corretos a cada algarismo encontrado.
|
|||
|
|
|||
|
Com isso, nós já podemos escrever uma rotina de conversão para strings que
|
|||
|
representam números decimais sem sinal de todos os tipos compatíveis com
|
|||
|
=int= (=char=, =short= e =long=, por exemplo).
|
|||
|
|
|||
|
* Conversão para inteiros sem sinal em alto nível
|
|||
|
|
|||
|
Em linguagem C, sem utilizar as funções especializadas da biblioteca padrão
|
|||
|
e com base no procedimento que elaboramos, nós poderíamos implementar uma
|
|||
|
função de conversão para inteiros sem sinal deste modo:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
unsigned int str_to_uint(char *str) {
|
|||
|
int dig; // Recebe o dígito convertido
|
|||
|
unsigned int conv = 0; // Recebe a parcial da conversão
|
|||
|
|
|||
|
for (int i = 0; str[i] != '\0'; i++) {
|
|||
|
if (str[i] < '0' || str[i] > '9') break;
|
|||
|
dig = str[i] - '0';
|
|||
|
conv = (conv * 10) + dig;
|
|||
|
}
|
|||
|
return conv;
|
|||
|
}
|
|||
|
#+end_src
|
|||
|
|
|||
|
Embora funcional para a maioria dos casos, essa abordagem tem alguns problemas:
|
|||
|
|
|||
|
- Não verifica se o valor convertido excede o limite máximo para inteiros sem
|
|||
|
sinal da plataforma;
|
|||
|
- Não verifica se o ponteiro para a string é nulo (=NULL=);
|
|||
|
- Retorna =0= tanto para a conversão da string ="0"= quanto para uma string sem
|
|||
|
dígitos nos primeiros caracteres.
|
|||
|
|
|||
|
Todos esses problemas poderiam ser solucionados com verificações e o retorno
|
|||
|
de um valor de erro: mas que valor seria esse? Tipicamente, utilizaríamos =-1=,
|
|||
|
mas o retorno da função tem que ser =unsigned=. Também poderíamos sacrificar um
|
|||
|
número válido, como o valor do próprio limite máximo de inteiros sem sinal,
|
|||
|
definido na =glibc= em =UINT_MAX=, no cabeçalho =limits.h=, mas isso requereria
|
|||
|
uma nota sobre essa limitação.
|
|||
|
|
|||
|
Uma terceira opção, que me parece bem mais razoável, seria incluir um segundo
|
|||
|
parâmetro para receber o estado de término da função:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
unsigned int str_to_uint(char *str, int *err);
|
|||
|
#+end_src
|
|||
|
|
|||
|
Deste modo, a função poderia ser utilizada assim:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
char *str_num = "123"; // String a ser convertida.
|
|||
|
int status; // Recebe o estado de término da função.
|
|||
|
|
|||
|
unsigned int num = str_to_uint(str_num, &status);
|
|||
|
if (!status) ...; // Consequências do erro...
|
|||
|
#+end_src
|
|||
|
|
|||
|
Para os casos de uso onde o tratamento de erros não for importante, nós
|
|||
|
podemos dar a opção de passar =NULL= como argumento de =*err= e trabalhar a
|
|||
|
implementação para definir um estado padrão =0= (erro), condicionado ao
|
|||
|
ponteiro recebido, e alterá-lo para =1= somente antes de terminar com
|
|||
|
sucesso:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
unsigned int str_to_uint(char *str, int *err /* nullable */) {
|
|||
|
// Inicia a função com estado de erro, mas só se '*err' não for NULL...
|
|||
|
if (err) *err = 0;
|
|||
|
|
|||
|
// Outras verificações e rotina de conversão...
|
|||
|
|
|||
|
// Altera o estado antes de terminar com sucesso (1)...
|
|||
|
if (err) *err = 1;
|
|||
|
return conv;
|
|||
|
}
|
|||
|
#+end_src
|
|||
|
|
|||
|
Assim, caso =*err= seja =NULL=, nenhum estado de término será definido.
|
|||
|
|
|||
|
** Erro de ponteiro nulo
|
|||
|
|
|||
|
Caso o endereço da string (=*str=) seja passada como um ponteiro nulo, nós
|
|||
|
simplesmente terminaremos a função com retorno =0= sem alterar o valor de
|
|||
|
=*err=, que também é =0= (erro):
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
// Inicia a função com estado de erro, mas só se '*err' não for NULL...
|
|||
|
if (err) *err = 0;
|
|||
|
|
|||
|
// Termina com erro (0) e valor 0 se str==NULL...
|
|||
|
if (str == NULL) return 0;
|
|||
|
#+end_src
|
|||
|
|
|||
|
** Erro de byte inicial fora faixa de dígitos
|
|||
|
|
|||
|
Apesar de ser uma verificação redundante, por ser repetida mais adiante no
|
|||
|
código, ela é necessária para determinar quando a função retorna =0= por ter
|
|||
|
convertido apenas uma string ="0"= ou devido a não haver dígitos para converter
|
|||
|
no início da string, ou seja:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
String: "abc123" --> Converte para 0 com estado 1
|
|||
|
String: "" --> Converte para 0 com estado 1
|
|||
|
String: "0" --> Converte para 0 com estado 1
|
|||
|
Erros --> Retornam conversão para 0, mas com estado 0
|
|||
|
#+end_example
|
|||
|
|
|||
|
A ideia é que os dois primeiros casos (="abc123"= e =""=) retornem =0= om estado
|
|||
|
de término =0= (erro), portanto...
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
// Inicia a função com estado de erro, mas só se '*err' não for NULL...
|
|||
|
if (err) *err = 0;
|
|||
|
|
|||
|
// Termina com erro (0) e valor 0 se str==NULL...
|
|||
|
if (str == NULL) return 0;
|
|||
|
|
|||
|
// Termina com erro (0) e valor 0 se primeiro byte não for um dígito...
|
|||
|
if (str[0] < '0' || str[0] > '9') return 0;
|
|||
|
#+end_src
|
|||
|
|
|||
|
Nós podemos, inclusive, combinar as duas verificações numa só:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
// Inicia a função com estado de erro, mas só se '*err' não for NULL...
|
|||
|
if (err) *err = 0;
|
|||
|
|
|||
|
// Termina com erro (0) e valor 0 se str==NULL
|
|||
|
// ou se o primeiro byte não for um dígito...
|
|||
|
if (str == NULL || str[0] < '0' || str[0] > '9') return 0;
|
|||
|
#+end_src
|
|||
|
|
|||
|
** Erro de estouro do limite do tipo
|
|||
|
|
|||
|
Nós podemos utilizar o valor de =UINT_MAX= como referência para determinar se
|
|||
|
o próximo valor parcial do ciclo de conversões excederá ou não o limite
|
|||
|
máximo para números inteiros sem sinal. A cada ciclo, a conversão é feita
|
|||
|
com:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
valor = (valor * 10) + dígito;
|
|||
|
#+end_example
|
|||
|
|
|||
|
Para determinar se isso excederá o valor de =UNIT_MAX=, basta calcular qual
|
|||
|
seria o valor corrente máximo para que a próxima conversão resulte em algo
|
|||
|
dentro do limite:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
valor * 10 + dígito < UINT_MAX
|
|||
|
valor * 10 < UINT_MAX - dígito
|
|||
|
valor < (UINT_MAX - dígito) / 10
|
|||
|
#+end_example
|
|||
|
|
|||
|
No código, isso pode ser escrito no loop de conversão como:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
// Converte o dígito corrente...
|
|||
|
dig = str[i] - '0';
|
|||
|
|
|||
|
// Termina com erro e valor 0 se a próxima conversão exceder UINT_MAX...
|
|||
|
if (conv > (UINT_MAX - dig) / 10) return 0;
|
|||
|
|
|||
|
// Processa a parcial da conversão...
|
|||
|
conv = (conv * 10) + dig;
|
|||
|
#+end_src
|
|||
|
|
|||
|
** Implementação final em C
|
|||
|
|
|||
|
Aqui está a implementação final, com as verificações e os procedimentos
|
|||
|
atualizados:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
#include <limits.h> // Requerido para obter UINT_MAX da plataforma
|
|||
|
|
|||
|
unsigned int str_to_uint(char *str, int *err /* nullable */) {
|
|||
|
|
|||
|
if (err) *err = 0; // Estado padrão é de erro (0 = falso)!
|
|||
|
|
|||
|
// Termina com erro e valor 0 se str==NULL ou se '0'>str[0]>'9'...
|
|||
|
if (str == NULL || str[0] < '0' || str[0] > '9') return 0;
|
|||
|
|
|||
|
int dig; // Recebe o dígito convertido
|
|||
|
unsigned int conv = 0; // Recebe a parcial da conversão
|
|||
|
|
|||
|
for (int i = 0; str[i] != '\0'; i++) {
|
|||
|
// Se o caractere não for um dígito, termina a conversão...
|
|||
|
if (str[i] < '0' || str[i] > '9') break;
|
|||
|
|
|||
|
// Converte o dígito corrente...
|
|||
|
dig = str[i] - '0';
|
|||
|
|
|||
|
// Termina com erro e valor 0 se a próxima conversão exceder UINT_MAX...
|
|||
|
if (conv > (UINT_MAX - dig) / 10) return 0;
|
|||
|
|
|||
|
// Processa a parcial da conversão...
|
|||
|
conv = (conv * 10) + dig;
|
|||
|
}
|
|||
|
|
|||
|
if (err) *err = 1; // Altera estado para sucesso (1 = verdadeiro)
|
|||
|
return conv;
|
|||
|
}
|
|||
|
#+end_src
|
|||
|
|
|||
|
** Exemplo de uso
|
|||
|
|
|||
|
Arquivo: [[exemplos/09/uint.c][uint.c]]
|
|||
|
|
|||
|
#+begin_src c :tangle exemplos/09/uint.c
|
|||
|
/*
|
|||
|
* Arquivo : uint.c
|
|||
|
* Compilação: gcc -Wall uint.c -o uintc
|
|||
|
*/
|
|||
|
#include <stdio.h>
|
|||
|
#include <limits.h> // Requerido para obter UINT_MAX da plataforma
|
|||
|
|
|||
|
unsigned int str_to_uint(char *str, int *err /* nullable */);
|
|||
|
|
|||
|
int main(int argc, char **argv) {
|
|||
|
if (argc == 1) {
|
|||
|
fprintf(stderr, "Uso: %s NÚMERO\n", argv[0]);
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
int status;
|
|||
|
unsigned int num = str_to_uint(argv[1], &status);
|
|||
|
|
|||
|
if (status) {
|
|||
|
printf("String: %s\nNúmero: %u\n", argv[1], num);
|
|||
|
} else {
|
|||
|
fprintf(stderr, "Erro de conversão!\n");
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
unsigned int str_to_uint(char *str, int *err /* nullable */) {
|
|||
|
if (err) *err = 0; // Estado padrão é de erro (0 = falso)!
|
|||
|
|
|||
|
// Termina com erro e valor 0 se str==NULL ou se '0'>str[0]>'9'...
|
|||
|
if (str == NULL || str[0] < '0' || str[0] > '9') return 0;
|
|||
|
|
|||
|
int dig; // Recebe o dígito convertido
|
|||
|
unsigned int conv = 0; // Recebe a parcial da conversão
|
|||
|
|
|||
|
for (int i = 0; str[i] != '\0'; i++) {
|
|||
|
// Se o caractere não for um dígito, termina a conversão...
|
|||
|
if (str[i] < '0' || str[i] > '9') break;
|
|||
|
// Converte o dígito corrente...
|
|||
|
dig = str[i] - '0';
|
|||
|
// Termina com erro e valor 0 se a próxima conversão exceder UINT_MAX...
|
|||
|
if (conv > (UINT_MAX - dig) / 10) return 0;
|
|||
|
// Processa a parcial da conversão...
|
|||
|
conv = (conv * 10) + dig;
|
|||
|
}
|
|||
|
|
|||
|
if (err) *err = 1; // Altera estado para sucesso (1 = verdadeiro)
|
|||
|
return conv;
|
|||
|
}
|
|||
|
#+end_src
|
|||
|
|
|||
|
Compilação e testes:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
:~$ gcc -Wall uint.c -o uintc
|
|||
|
$ ./uintc
|
|||
|
Uso: ./uintc NÚMERO
|
|||
|
:~$ ./uintc 123
|
|||
|
String: 123
|
|||
|
Número: 123
|
|||
|
:~$ ./uintc 123abc
|
|||
|
String: 123abc
|
|||
|
Número: 123
|
|||
|
:~$ ./uintc $((2**32))
|
|||
|
Erro de conversão!
|
|||
|
:~$ ./uintc $((2**32 - 1))
|
|||
|
String: 4294967295
|
|||
|
Número: 4294967295
|
|||
|
:~$ ./uintc ''
|
|||
|
Erro de conversão!
|
|||
|
:~$ ./uintc abc
|
|||
|
Erro de conversão!
|
|||
|
#+end_example
|
|||
|
|
|||
|
* Conversão para inteiros sem sinal em baixo nível
|
|||
|
|
|||
|
Em Assembly, o procedimento de conversão ainda é o mesmo:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
val = 0
|
|||
|
val = (val * 10) + (0x31 - 0x30) = 0 + 1 = 1
|
|||
|
val = (val * 10) + (0x32 - 0x30) = 10 + 2 = 12
|
|||
|
val = (val * 10) + (0x33 - 0x30) = 120 + 3 = 123
|
|||
|
#+end_example
|
|||
|
|
|||
|
Que pode ser implementado, de forma genérica, com uma sub-rotina assim:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
_str_to_uitn:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Entrada: RSI = Endereço da string (char * str)
|
|||
|
; Saída : RAX = Valor convertido (unsigned int)
|
|||
|
; ------------------------------------------------------
|
|||
|
; Preparação...
|
|||
|
; ------------------------------------------------------
|
|||
|
push rbx ; Salva conteúdo de rbx
|
|||
|
xor rax, rax ; rax = 0 (acumula a conversão)
|
|||
|
xor rbx, rbx ; rbx = 0 (recebe os caracteres no loop)
|
|||
|
; ------------------------------------------------------
|
|||
|
.conv_loop:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Condições de término do loop...
|
|||
|
; ------------------------------------------------------
|
|||
|
mov bl, [rsi] ; Lê o primeiro byte
|
|||
|
test bl, bl ; Compara primeiro byte com o terminador '\0'
|
|||
|
je .done ; Se 0x00, termina o loop
|
|||
|
cmp bl, 0x30 ; Compara primeiro byte com '0'
|
|||
|
jl .done ; Se menor, termina o loop
|
|||
|
cmp bl, 0x39 ; Compara primeiro byte com '9'
|
|||
|
jg .done ; Se maior, termina o loop
|
|||
|
; ------------------------------------------------------
|
|||
|
; Conversão...
|
|||
|
; ------------------------------------------------------
|
|||
|
sub rbx, 0x30 ; Subtrai '0' do byte (dígito convertido)
|
|||
|
imul rax, rax, 10 ; rax *= 10 (deslocamento do peso posicional)
|
|||
|
add rax, rbx ; rax += dígito convertido
|
|||
|
inc rsi ; Avança rdi para o próximo byte
|
|||
|
jmp .conv_loop ; Volta ao início do loop de conversão
|
|||
|
; ------------------------------------------------------
|
|||
|
.done:
|
|||
|
; ------------------------------------------------------
|
|||
|
pop rbx ; Restaura rbx
|
|||
|
ret
|
|||
|
#+end_src
|
|||
|
|
|||
|
Isso equivale à primeira versão da função em C e traz os mesmos problemas:
|
|||
|
|
|||
|
- Não verifica se o valor convertido excede o limite máximo para inteiros sem
|
|||
|
sinal da plataforma;
|
|||
|
- Não verifica se o ponteiro para a string é nulo (=NULL=);
|
|||
|
- Retorna =0= tanto para a conversão da string ="0"= quanto para uma string sem
|
|||
|
dígitos nos primeiros caracteres.
|
|||
|
|
|||
|
Além disso, se quisermos adaptar a sub-rotina para uso em um programa em C
|
|||
|
(em um objeto compartilhado, por exemplo) ela precisa seguir as convenções
|
|||
|
da ABI. Do jeito que está, ela já poderia ser utilizada, porque recebe o
|
|||
|
primeiro argumento em =rdi= e retorna o valor convertido em =rax=, mas ainda
|
|||
|
precisamos decidir como implementar a sinalização de erros. Na função em C,
|
|||
|
isso é feito com um ponteiro opcional (/nullable/) para a variável que receberá
|
|||
|
o estado de erro:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
unsigned int str_to_uint(char *str, int *err /* nullable */);
|
|||
|
#+end_src
|
|||
|
|
|||
|
A ABI determina que o segundo argumento deve ser recebido em =rdx=, então a
|
|||
|
assinatura da sub-rotina passaria a ser:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
_str_to_uitn:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Entrada:
|
|||
|
; RSI = Endereço da string (char * str)
|
|||
|
; RDX = Endereço da variável de erro (int *err)
|
|||
|
; Saída :
|
|||
|
; RAX = Valor convertido (unsigned int)
|
|||
|
; Se sucesso: *err = 1 (verdadeiro)
|
|||
|
; Se erro : *err = 0 (falso)
|
|||
|
; ------------------------------------------------------
|
|||
|
#+end_src
|
|||
|
|
|||
|
Ainda na função em C, nós decidimos definir o estado de =*err= logo no início
|
|||
|
e só alterá-lo antes do retorno com sucesso. Como o argumento pode receber
|
|||
|
um ponteiro nulo (=NULL=), o estado de término foi definido condicionalmente:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
if (err) *err = 0; // Estado padrão é de erro (0 = falso)!
|
|||
|
// ...
|
|||
|
if (err) *err = 1; // Altera estado para sucesso (1 = verdadeiro)
|
|||
|
return conv;
|
|||
|
#+end_src
|
|||
|
|
|||
|
Além disso, sempre que a função terminava com erro, o valor retornado era
|
|||
|
definido com =0=, e isso também deve ser implementado na sub-rotina:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
_str_to_uitn:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Entrada:
|
|||
|
; RSI = Endereço da string (char * str)
|
|||
|
; RDX = Endereço da variável de erro (int *err /* nullable */)
|
|||
|
; Saída :
|
|||
|
; RAX = Valor convertido (unsigned int)
|
|||
|
; Se sucesso: *err = 1 (verdadeiro)
|
|||
|
; Se erro : *err = 0 (falso)
|
|||
|
; ------------------------------------------------------
|
|||
|
; Definição condicional do estado inicial de erro...
|
|||
|
; ------------------------------------------------------
|
|||
|
test rdx, rdx ; Se *err = NULL, rdx = 0
|
|||
|
jz .skip_error ; Se for 0, pula a definição do erro
|
|||
|
mov dword [rdx], 0 ; *err = 0 => estado inicial é de erro (falso)
|
|||
|
; ------------------------------------------------------
|
|||
|
.skip_error:
|
|||
|
; ------------------------------------------------------
|
|||
|
|
|||
|
; ...
|
|||
|
|
|||
|
; ------------------------------------------------------
|
|||
|
.error:
|
|||
|
; ------------------------------------------------------
|
|||
|
xor rax, rax ; Retorna 0 em caso de erro
|
|||
|
jmp .done
|
|||
|
; ------------------------------------------------------
|
|||
|
.success:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Redefinição condicional do estado final de sucesso...
|
|||
|
; ------------------------------------------------------
|
|||
|
test rdx, rdx ; Se *err = NULL, rdx = 0
|
|||
|
jz .done ; Se for 0, pula a definição de sucesso
|
|||
|
mov dword [rdx], 1 ; *err = 1 => estado final é de sucesso (verdadeiro)
|
|||
|
; ------------------------------------------------------
|
|||
|
.done:
|
|||
|
; ------------------------------------------------------
|
|||
|
pop rbx ; Restaura rbx
|
|||
|
ret
|
|||
|
#+end_src
|
|||
|
|
|||
|
Com o procedimento de tratamento de erros definido, nós podemos prosseguir
|
|||
|
com a implementação das validações necessárias.
|
|||
|
|
|||
|
** Erro de ponteiro nulo
|
|||
|
|
|||
|
A primeira verificação que faremos é para o caso de =rsi= ter recebido um
|
|||
|
ponteiro nulo na chamada:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
; ------------------------------------------------------
|
|||
|
; Teste de ponteiro nulo...
|
|||
|
; ------------------------------------------------------
|
|||
|
test rsi, rsi ; verifica se enderçeo é nulo (0)
|
|||
|
jz .error ; se for, termina retornando -1 (erro)
|
|||
|
#+end_src
|
|||
|
|
|||
|
Deste modo, se =rsi= receber =0= (=NULL=, numa chamada em C), a sub-rotina salta
|
|||
|
para o rótulo local =.error= e termina retornando =0= em =rax= e =1= no endereço
|
|||
|
em =rdx=, se =*err= estiver definido.
|
|||
|
|
|||
|
** Erro de byte inicial fora da faixa de dígitos
|
|||
|
|
|||
|
Para que o primeiro byte seja verificado, ele precisa ser lido. Portanto,
|
|||
|
a verificação da faixa de dígitos deve ser feita logo após a preparação
|
|||
|
dos registradores utilizados, antes do /loop/ de conversão:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
; ------------------------------------------------------
|
|||
|
; Preparação...
|
|||
|
; ------------------------------------------------------
|
|||
|
push rbx ; salva rbx na pilha
|
|||
|
xor rax, rax ; rax = 0 (acumula o resultado)
|
|||
|
xor rbx, rbx ; rbx = 0 (recebe os caracteres no loop)
|
|||
|
mov bl, [rsi] ; lê o primeiro byte
|
|||
|
; ------------------------------------------------------
|
|||
|
; Validação do primeiro byte...
|
|||
|
; ------------------------------------------------------
|
|||
|
cmp bl, 0x30 ; compara com menor byte válido
|
|||
|
jl .error ; se menor, termina retornando -1
|
|||
|
cmp bl, 0x39 ; compara com maior byte válido
|
|||
|
jg .error ; se maior, termina retornando -1
|
|||
|
#+end_src
|
|||
|
|
|||
|
Assim, a sub-rotina salta para =.error= se o byte for menor que =0x30= (caractere
|
|||
|
='0'=) ou maior que =0x39= (caractere ='9'=).
|
|||
|
|
|||
|
** Erro de estouro do limite do tipo
|
|||
|
|
|||
|
Como vimos, o cálculo do maior valor que pode ser multiplicado por 10 na
|
|||
|
conversão é feito com:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
(UINT_MAX - dígito) / 10
|
|||
|
#+end_example
|
|||
|
|
|||
|
Contudo, essa verificação é um pouco mais complicada em Assembly, por alguns
|
|||
|
motivos:
|
|||
|
|
|||
|
- Nós não temos um limite definido para a plataforma, como a constante
|
|||
|
simbólica =UINT_MAX=, da =glibc=.
|
|||
|
- É possível definir =UINT_MAX= com uma macro no Assembly, mas não podemos
|
|||
|
contar com isso no caso de reutilização da sub-rotina, a menos que ela
|
|||
|
esteja em uma biblioteca que também contenha definições de macros.
|
|||
|
- Também teremos que utilizar registradores auxiliares adicionais, porque,
|
|||
|
numa divisão, =rax= recebe o quociente e =rdx= o resto, e ambos estão em
|
|||
|
uso.
|
|||
|
|
|||
|
Mas /"complicada"/ não é /"impossível"/, e nós podemos assumir alguns critérios:
|
|||
|
|
|||
|
- A ABI define que inteiros terão 4 bytes (32 bits) em arquiteturas x86_64
|
|||
|
e as implementações da linguagem C no GCC e na Glibc garantem isso.
|
|||
|
- O valor de =UINT_MAX= pode ser armazenado em =rcx= como =0xffffffff=, o maior
|
|||
|
número sem sinal que pode ser escrito com 4 bytes.
|
|||
|
- Os registradores =r8= e =r10= podem ser usados para salvar e restaurar os
|
|||
|
dados em =rax= e =rdx=.
|
|||
|
- O registrador =r9= pode ser usado para auxiliar nos cálculos.
|
|||
|
|
|||
|
Portanto, esta é uma implementação possível:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
; ------------------------------------------------------
|
|||
|
; Limite válido: conv < (UINT_MAX - dígito) / 10
|
|||
|
; ------------------------------------------------------
|
|||
|
mov r8, rax ; Salva parcial da conversão em r8
|
|||
|
mov r10, rdx ; Salva ponteiro de erro em r10
|
|||
|
|
|||
|
mov eax, -1 ; rax = 0x00000000ffffffff (UINT_MAX)
|
|||
|
sub eax, ebx ; rax = UINT_MAX - dígito (dividendo)
|
|||
|
|
|||
|
xor rdx, rdx ; prepara rdx para receber o resto da divisão
|
|||
|
mov r9, 10 ; divisor
|
|||
|
div r9 ; limite(rax) = (UINT_MAX - dígito) / 10
|
|||
|
|
|||
|
cmp r8, rax ; se parcial > limite, teremos um estouro
|
|||
|
jg .error ; se maior, termina com erro
|
|||
|
|
|||
|
mov rdx, r10 ; restaura rdx (pontiero para erros)
|
|||
|
mov rax, r8 ; restaura rax (parcial da conversão)
|
|||
|
#+end_src
|
|||
|
|
|||
|
** Implementação final em Assembly
|
|||
|
|
|||
|
Com alguns ajustes estéticos e na documentação, aqui está a implementação
|
|||
|
completa...
|
|||
|
|
|||
|
Arquivo: [[exemplos/09/uint.asm][uint.asm]]
|
|||
|
|
|||
|
#+begin_src asm :tangle exemplos/09/uint.asm
|
|||
|
; ----------------------------------------------------------
|
|||
|
; Arquivo : uint.asm
|
|||
|
; Montagem: nasm -g -f elf64 uint.asm
|
|||
|
; Ligação : ls uint.o -o uint
|
|||
|
; ----------------------------------------------------------
|
|||
|
section .data
|
|||
|
; ----------------------------------------------------------
|
|||
|
num_str db "123", 0 ; string numérica
|
|||
|
; ----------------------------------------------------------
|
|||
|
section .bss
|
|||
|
; ----------------------------------------------------------
|
|||
|
num resd 1 ; 4 bytes para o valor UINT32
|
|||
|
status resd 1 ; 4 bytes para o estado de término
|
|||
|
; ----------------------------------------------------------
|
|||
|
section .text
|
|||
|
global _start
|
|||
|
; ----------------------------------------------------------
|
|||
|
_start:
|
|||
|
; ----------------------------------------------------------
|
|||
|
; Teste de chamada (verificar com o GDB)...
|
|||
|
; ----------------------------------------------------------
|
|||
|
mov rsi, num_str ; copia endereço da string em rsi
|
|||
|
mov rdx, status ; copia o endereço do estado de erro em rdx
|
|||
|
call _str_to_uint ; chama a sub-rotina de conversão
|
|||
|
mov dword [num], eax ; copia o resultado como int para [num]
|
|||
|
; ----------------------------------------------------------
|
|||
|
_exit:
|
|||
|
; ----------------------------------------------------------
|
|||
|
mov rax, 60
|
|||
|
mov rdi, 0
|
|||
|
syscall
|
|||
|
; ----------------------------------------------------------
|
|||
|
; Sub-rotinas...
|
|||
|
; ----------------------------------------------------------
|
|||
|
_str_to_uint:
|
|||
|
; ----------------------------------------------------------
|
|||
|
; Converte string numérica em inteiro sem sinal (uint32_t).
|
|||
|
; Entradas:
|
|||
|
; RSI = Endereço da string (char *str)
|
|||
|
; RDX = Endereço para estado de erro (int *err /* nullable */)
|
|||
|
; Saída:
|
|||
|
; RAX = valor convertido (uint32_t) ou 0, no caso de erro
|
|||
|
; Sucesso: *rdx = 1
|
|||
|
; Erro: *rdx = 0
|
|||
|
; ----------------------------------------------------------
|
|||
|
; Definição condicional do estado inicial de erro...
|
|||
|
; ------------------------------------------------------
|
|||
|
test rdx, rdx ; Se *err = NULL, rdx = 0
|
|||
|
jz .skip_error ; Se for 0, pula a definição do erro
|
|||
|
mov dword [rdx], 0 ; *err = 0 => estado inicial é de erro (falso)
|
|||
|
; ------------------------------------------------------
|
|||
|
.skip_error:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Teste de ponteiro nulo...
|
|||
|
; ------------------------------------------------------
|
|||
|
test rsi, rsi ; verifica se enderçeo é nulo (0)
|
|||
|
jz .error ; se for, termina retornando -1 (erro)
|
|||
|
; ------------------------------------------------------
|
|||
|
; Preparação...
|
|||
|
; ------------------------------------------------------
|
|||
|
push rbx ; salva rbx na pilha
|
|||
|
xor rax, rax ; rax = 0 (acumula o resultado)
|
|||
|
xor rbx, rbx ; rbx = 0 (recebe os caracteres no loop)
|
|||
|
mov bl, [rsi] ; lê o primeiro byte
|
|||
|
; ------------------------------------------------------
|
|||
|
; Validação do primeiro byte...
|
|||
|
; ------------------------------------------------------
|
|||
|
cmp bl, 0x30 ; compara com menor byte válido
|
|||
|
jl .error ; se menor, termina retornando -1
|
|||
|
cmp bl, 0x39 ; compara com maior byte válido
|
|||
|
jg .error ; se maior, termina retornando -1
|
|||
|
.conv_loop:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Condições de término da conversão ...
|
|||
|
; ------------------------------------------------------
|
|||
|
test bl, bl ; verifica se o byte é o terminador 0x00
|
|||
|
je .success ; se for, termina com sucesso
|
|||
|
cmp bl, 0x30 ; compara o byte em rbx com o menor dígito
|
|||
|
jl .success ; se for menor, termina com sucesso
|
|||
|
cmp bl, 0x39 ; compara o byte em rbx com o maior dígito
|
|||
|
jg .success ; se for maior, termina com sucesso
|
|||
|
; ------------------------------------------------------
|
|||
|
; Conversão do dígito corrente...
|
|||
|
; ------------------------------------------------------
|
|||
|
sub rbx, 0x30 ; converte o dígito para seu valor numérico
|
|||
|
; ------------------------------------------------------
|
|||
|
; Limite válido: conv < (UINT_MAX - dígito) / 10
|
|||
|
; ------------------------------------------------------
|
|||
|
mov r8, rax ; Salva parcial da conversão em r8
|
|||
|
mov r10, rdx ; Salva ponteiro de erro em r10
|
|||
|
|
|||
|
mov eax, -1 ; eax = 0xffffffff (UINT_MAX - 32 bits)
|
|||
|
sub eax, ebx ; rax = UINT_MAX - dígito (dividendo)
|
|||
|
|
|||
|
xor rdx, rdx ; prepara rdx para receber o resto da divisão
|
|||
|
mov r9, 10 ; divisor
|
|||
|
div r9 ; limite(rax) = (UINT_MAX - dígito) / 10
|
|||
|
|
|||
|
cmp r8, rax ; se parcial > limite, teremos um estouro
|
|||
|
jg .error ; se maior, termina com erro
|
|||
|
|
|||
|
mov rdx, r10 ; restaura rdx (pontiero para erros)
|
|||
|
mov rax, r8 ; restaura rax (parcial da conversão)
|
|||
|
; ------------------------------------------------------
|
|||
|
; Processa a parcial da conversão...
|
|||
|
; ------------------------------------------------------
|
|||
|
imul rax, rax, 10 ; rax *= 10 (deslocamento do peso posicional)
|
|||
|
add rax, rbx ; rax += novo algarismo
|
|||
|
inc rsi ; avança para o próximo byte
|
|||
|
mov bl, [rsi] ; carrega o dígito corrente em rbx
|
|||
|
jmp .conv_loop
|
|||
|
; ------------------------------------------------------
|
|||
|
.error:
|
|||
|
; ------------------------------------------------------
|
|||
|
xor rax, rax ; Retorna 0 em caso de erro
|
|||
|
jmp .done
|
|||
|
; ------------------------------------------------------
|
|||
|
.success:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Redefinição condicional do estado final de sucesso...
|
|||
|
; ------------------------------------------------------
|
|||
|
test rdx, rdx ; Se *err = NULL, rdx = 0
|
|||
|
jz .done ; Se for 0, pula a definição de sucesso
|
|||
|
mov dword [rdx], 1 ; *err = 1 => estado final é de sucesso (verdadeiro)
|
|||
|
; ------------------------------------------------------
|
|||
|
.done:
|
|||
|
; ------------------------------------------------------
|
|||
|
pop rbx ; Restaura rbx
|
|||
|
ret
|
|||
|
#+end_src
|
|||
|
|
|||
|
** Teste com o GDB
|
|||
|
|
|||
|
Nosso objetivo é testar se a string em =num_str= é convertida corretamente
|
|||
|
pela análise dos valores em =rax= e =rdx= (=status=) após a chamada da sub-rotina
|
|||
|
neste trecho do programa:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
; ----------------------------------------------------------
|
|||
|
section .data
|
|||
|
; ----------------------------------------------------------
|
|||
|
num_str db "123", 0 ; string numérica
|
|||
|
; ----------------------------------------------------------
|
|||
|
section .bss
|
|||
|
; ----------------------------------------------------------
|
|||
|
num resd 1 ; 4 bytes para o valor UINT32
|
|||
|
status resd 1 ; 4 bytes para o estado de término
|
|||
|
; ----------------------------------------------------------
|
|||
|
section .text
|
|||
|
global _start
|
|||
|
; ----------------------------------------------------------
|
|||
|
_start:
|
|||
|
; ----------------------------------------------------------
|
|||
|
; Teste de chamada (verificar com o GDB)...
|
|||
|
; ----------------------------------------------------------
|
|||
|
mov rsi, num_str ; copia endereço da string em rsi
|
|||
|
mov rdx, status ; copia o endereço do estado de erro em rdx
|
|||
|
call _str_to_uint ; chama a sub-rotina de conversão
|
|||
|
mov dword [num], eax ; copia o resultado como int para [num]
|
|||
|
#+end_src
|
|||
|
|
|||
|
*Montagem e execução no GDB:*
|
|||
|
|
|||
|
#+begin_example
|
|||
|
:~$ nasm -g -f elf64 uint.asm
|
|||
|
:~$ ld uint.o -o uint
|
|||
|
:~$ gdb ./uint
|
|||
|
Reading symbols from ./uint...
|
|||
|
(gdb) break _start
|
|||
|
Breakpoint 1 at 0x401000: file uint.asm, line 22.
|
|||
|
(gdb) run
|
|||
|
Starting program: /home/blau/git/pbn/curso/exemplos/09/uint
|
|||
|
|
|||
|
Breakpoint 1, _start () at uint.asm:22
|
|||
|
22 mov rsi, num_str ; copia endereço da string em rsi
|
|||
|
(gdb)
|
|||
|
#+end_example
|
|||
|
|
|||
|
*Execução e verificações antes da chamada:*
|
|||
|
|
|||
|
#+begin_example
|
|||
|
(gdb) n 2
|
|||
|
24 call _str_to_uint ; chama a sub-rotina de conversão
|
|||
|
(gdb) info registers rax rdx rsi
|
|||
|
rax 0x0 0
|
|||
|
rdx 0x402008 4202504
|
|||
|
rsi 0x402000 4202496
|
|||
|
(gdb) x /1wx &num_str
|
|||
|
0x402000 <num_str>: 0x00333231
|
|||
|
(gdb) x /1s &num_str
|
|||
|
0x402000 <num_str>: "123"
|
|||
|
(gdb) x /1wx &num_
|
|||
|
No symbol "num_" in current context.
|
|||
|
(gdb) x /1wx &num
|
|||
|
0x402004 <num>: 0x00000000
|
|||
|
(gdb) x /1wx &status
|
|||
|
0x402008 <status>: 0x00000000
|
|||
|
#+end_example
|
|||
|
|
|||
|
*Execução e verificações após a chamada:*
|
|||
|
|
|||
|
#+begin_example
|
|||
|
(gdb) n
|
|||
|
25 mov dword [num], eax ; copia o resultado para [num]
|
|||
|
(gdb) n
|
|||
|
_exit () at uint.asm:29
|
|||
|
29 mov rax, 60
|
|||
|
(gdb) info registers rax rdx rsi
|
|||
|
rax 0x7b 123
|
|||
|
rdx 0x402008 4202504
|
|||
|
rsi 0x402003 4202499
|
|||
|
(gdb) x /1wx &num
|
|||
|
0x402004 <num>: 0x0000007b
|
|||
|
(gdb) print (int)num
|
|||
|
$1 = 123
|
|||
|
(gdb) x /1wx &status
|
|||
|
0x402008 <status>: 0x00000001
|
|||
|
#+end_example
|
|||
|
|
|||
|
* Conversão para inteiros com sinal
|
|||
|
|
|||
|
No Assembly x86_64, números com sinal são representados usando o método do
|
|||
|
/complemento de dois/. Tomando um inteiro de 32 bits como exemplo, se o bit
|
|||
|
mais significativo (MSB, o bit mais a esquerda) for =0=, o número é interpretado
|
|||
|
como positivo ou zero; se for =1=, o número é negativo. Em outras palavras, o
|
|||
|
bit mais a esquerda de um número é o seu sinal.
|
|||
|
|
|||
|
O complemento de dois de um número binário é obtido pela inversão de todos
|
|||
|
os seus bits (com uma operação =NOT= bit a bit) seguida da soma de 1:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
00000000 00000000 00000000 00000101 = 5 (0x0005)
|
|||
|
NOT -> 11111111 11111111 11111111 11111010 = -6 (0xFFFA)
|
|||
|
+1 -> 11111111 11111111 11111111 11111011 = -5 (0xFFFB)
|
|||
|
#+end_example
|
|||
|
|
|||
|
** Interpretação do sinal
|
|||
|
|
|||
|
Se o MSB de um número for =1=, o que acontecerá sempre que o byte (não bit)
|
|||
|
mais significativo de seu valor em hexadecimal iniciar com =8= ou mais, o
|
|||
|
número poderá ser interpretado como negativo ou positivo -- tudo depende
|
|||
|
do contexto. Por exemplo, o número 2.147.483.648, se escrito com 4 bytes
|
|||
|
em hexa, será =0x80000000= e seu equivalente binário será:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
10000000 00000000 00000000 00000000 = 0x80 00 00 00 = 2.147.483.648
|
|||
|
#+end_example
|
|||
|
|
|||
|
Digamos que esse número seja escrito em =edx= (32 bits) e seja utilizado em
|
|||
|
uma chamada de função que espera um argumento inteiro sem sinal. Neste caso,
|
|||
|
o valor será tomado como positivo, mesmo que o binário inicie com =1=. Porém,
|
|||
|
considere esta situação:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
mov eax, 0x80000000
|
|||
|
cmp eax, 0
|
|||
|
#+end_src
|
|||
|
|
|||
|
A instrução =cmp= calcula =eax - 0= e configura as flags de acordo com o valor
|
|||
|
em =eax= e o resultado da operação:
|
|||
|
|
|||
|
- Flag =SF=: foi definida com =1= porque o MSB do resultado é =1= (negativo).
|
|||
|
- Flag =OF=: como não houve /overflow/ na operação, seu valor foi definido como =0=.
|
|||
|
- Flag =CF=: como a operação não provocou um "vai um" ou um empréstimo fora
|
|||
|
do limite representável em 32 bits (capacidade de =eax=), seu valor foi
|
|||
|
definido como =0=.
|
|||
|
|
|||
|
Neste ponto, a interpretação do sinal do valor em =eax= vai depender do que
|
|||
|
faremos com o resultado da comparação. Observe esta possibilidade:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
mov eax, 0x80000000
|
|||
|
cmp eax, 0 ; resulta 0x80000000 -> SF=1 e OF=0
|
|||
|
jl .menor ; Se SF != OF, o salto acontece
|
|||
|
; ...
|
|||
|
.menor:
|
|||
|
; ...
|
|||
|
#+end_src
|
|||
|
|
|||
|
A instrução =jl= (/jump if less/) avalia se as flags =SF= e =OF= são diferentes: se
|
|||
|
forem, o salto é feito. Portanto, =jl= levaria em conta o sinal do resultado
|
|||
|
da subtração e salto aconteceria porque =eax < 0=. Mas, e neste caso?
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
mov eax, 0x80000000
|
|||
|
cmp eax, 0 ; resulta 0x80000000 -> SF=1 e OF=0
|
|||
|
jb .menor ; Se CF = 1, o salto acontece
|
|||
|
; ...
|
|||
|
.menor:
|
|||
|
; ...
|
|||
|
#+end_src
|
|||
|
|
|||
|
Já a instrução =jb= (/jump if below/) só salta se =CF=1=, mas o resultado da
|
|||
|
subtração (=eax-0=) não provocou um "vai um" ou um empréstimo fora do limite
|
|||
|
do registrador: portanto, seu valor será =0= e o salto não acontecerá. Isso
|
|||
|
significa que, como =jb= não leva em conta o sinal, =0x80000000= foi interpretado
|
|||
|
como um valor acima de zero.
|
|||
|
|
|||
|
Por outro lado, se utilizássemos =rax= em vez de =eax=...
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
mov rax, 0x80000000 ; 0x0000000080000000
|
|||
|
cmp rax, 0 ; CF=0 SF=0 e OF=0
|
|||
|
jb .menor ; CF=0, não salta
|
|||
|
jl .menor ; SF=OF, não salta
|
|||
|
; ...
|
|||
|
.menor:
|
|||
|
; ...
|
|||
|
#+end_src
|
|||
|
|
|||
|
Desta forma, não há ambiguidades quanto à interpretação do sinal.
|
|||
|
|
|||
|
** Uma nota sobre as flags
|
|||
|
|
|||
|
É importante entender corretamente o conceito por detrás da /carry flag/,
|
|||
|
porque, em operações sem sinal, é ela que indica se um número é menor do
|
|||
|
que outro.
|
|||
|
|
|||
|
Então, vejamos: =10= é menor do que =14=?
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
mov al, 10 ; 10 = 1010 = 0x0A
|
|||
|
cmp al, 14 ; 14 = 1110 = 0xFE
|
|||
|
#+end_src
|
|||
|
|
|||
|
A instrução =cmp= fará a subtração...
|
|||
|
|
|||
|
#+begin_example
|
|||
|
Aqui, temos que fazer um empréstimo e o MSB de 10 será 0...
|
|||
|
↓
|
|||
|
1010
|
|||
|
- 1110
|
|||
|
------
|
|||
|
00
|
|||
|
|
|||
|
Agora é o MSB que requer um empréstimo!
|
|||
|
↓
|
|||
|
0110
|
|||
|
- 1110
|
|||
|
------
|
|||
|
?100
|
|||
|
#+end_example
|
|||
|
|
|||
|
Numa situação desas, onde não há mais de onde emprestar um bit =1= mais a
|
|||
|
esquerda, o processador efetua a subtração, como se houvesse de onde tirar
|
|||
|
o bit 1 de que precisa, sobe a flag =CF= (indicando que houve um empréstimo
|
|||
|
fora do limite dos operandos) e a flag =SF= (indicando que o resultado é
|
|||
|
negativo). Ao mesmo tempo, =OF= e =ZF= receberiam =0=, porque não houve estouro
|
|||
|
do limite do valor representável com sinal e o resultado não foi zero.
|
|||
|
|
|||
|
Na operação acima, nós teríamos:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
CF=1
|
|||
|
↓
|
|||
|
(1)0110
|
|||
|
- 1110
|
|||
|
------
|
|||
|
1100 = -4 ← ZF=0 (não é zero)
|
|||
|
↑
|
|||
|
SF=1
|
|||
|
OF=0 (é possível escrever em 4 bits)
|
|||
|
#+end_example
|
|||
|
|
|||
|
#+begin_quote
|
|||
|
O processador não possui circuitos capazes de efetuar subtrações e, por isso,
|
|||
|
não executaria a operação desta forma, e sim calculando o complemento de dois
|
|||
|
de =-14= (=0010=) para, em seguida, somar com =10=: ~1010+0010 = 1100 (-4)~.
|
|||
|
#+end_quote
|
|||
|
|
|||
|
** Limites de inteiros com sinal
|
|||
|
|
|||
|
Assumindo que inteiros são escritos com 4 bytes, o maior valor absoluto
|
|||
|
que podemos representar ainda é =0xFFFFFFFF=, mas ele teria que ser visto
|
|||
|
como um número negativo. Calculando seu complemento de dois...
|
|||
|
|
|||
|
#+begin_example
|
|||
|
11111111 11111111 11111111 11111111 (0xFFFFFFFF)
|
|||
|
NOT -> 00000000 00000000 00000000 00000000
|
|||
|
+1 -> 00000000 00000000 00000000 00000001 => (1)
|
|||
|
#+end_example
|
|||
|
|
|||
|
Portanto, levando em conta o sinal, =0xFFFFFFFF= é =-1=.
|
|||
|
|
|||
|
Como o bit mais significativo representa o sinal, o primeiro dígito do byte
|
|||
|
mais significativo do maior número positivo que pode ser escrito em hexa tem
|
|||
|
que ser =7=:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
Maior byte positivo: 0x7f = 0111 1111 = 127
|
|||
|
#+end_example
|
|||
|
|
|||
|
Se somarmos =1= a =0x7F=, nós chegaremos a =0x80= (128), que será interpretado como
|
|||
|
um número negativo:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
0x7F -> 0111 1111
|
|||
|
+1 -> 1000 0000 = 0x80 = -128
|
|||
|
↑ ↑
|
|||
|
SF=1 -------------´
|
|||
|
#+end_example
|
|||
|
|
|||
|
Então, se o inteiro com sinal tiver 1 byte, seus limites serão:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
0x7F = 0111 1111 = +127
|
|||
|
0x7E = 0111 1110 = +126
|
|||
|
...
|
|||
|
0x01 = 0000 0001 = +1
|
|||
|
0x00 = 0000 0000 = 0
|
|||
|
0xFF = 1111 1111 = -1
|
|||
|
...
|
|||
|
0x81 = 1000 0001 = -127
|
|||
|
0x80 = 1000 0000 = -128
|
|||
|
#+end_example
|
|||
|
|
|||
|
Transpondo o princípio para o tamanho de um inteiro na arquitetura x86_64,
|
|||
|
os limites são:
|
|||
|
|
|||
|
#+begin_example
|
|||
|
0x7FFFFFFF -> 2.147.483.647
|
|||
|
0x80000000 -> -2.147.483.648
|
|||
|
#+end_example
|
|||
|
|
|||
|
** Procedimento de conversão para inteiros com sinal
|
|||
|
|
|||
|
As diferenças em relação à conversão de strings para inteiros sem sinal são:
|
|||
|
|
|||
|
- Antes da conversão, nós precisamos detectar a presença dos caracteres
|
|||
|
de sinal (=+= e =-=);
|
|||
|
- Depois do valor absoluto encontrado, nós teremos que verificar se ele está
|
|||
|
na faixa de valores inteiros com sinal (entre =0x7FFFFFFF= e =0x80000000=);
|
|||
|
- No final, se o número passar na verificação, nós teremos que calcular seu
|
|||
|
complemento de dois, o que pode ser feito multiplicando o valor por =-1= ou,
|
|||
|
de forma ainda mais compacta e eficiente, utilizando a instrução =neg=.
|
|||
|
|
|||
|
Todo o restante do procedimento é idêntico, tanto que podemos reutilizar
|
|||
|
as mesmas funções de antes para a etapa de conversão.
|
|||
|
|
|||
|
** Conversão em alto nível
|
|||
|
|
|||
|
Com base no procedimento geral, esta seria uma forma de implementar a
|
|||
|
conversão de inteiros com sinal em linguagem C reutilizando a função que
|
|||
|
criamos para converter inteiros sem sinal:
|
|||
|
|
|||
|
#+begin_src c
|
|||
|
#include <limits.h> // Requerido para obter INT_MAX e INT_MIN da plataforma
|
|||
|
|
|||
|
int str_to_sint(char *str, int *err) {
|
|||
|
if (err) *err = 0; // Flag de estado de término: 0 = erro
|
|||
|
if (str == NULL) return 0; // Teste do ponteiro para a string
|
|||
|
|
|||
|
// Verificação e atrubuição do sinal da string...
|
|||
|
char sig = '+'; // Sinal padrão
|
|||
|
if (str[0] == '+' || str[0] == '-') {
|
|||
|
sig = str[0];
|
|||
|
str++; // Avança o ponteiro da string
|
|||
|
}
|
|||
|
|
|||
|
// Chama a função de conversão de inteiros sem sinal...
|
|||
|
unsigned conv = str_to_uint(str, err);
|
|||
|
if (!*err) return 0;
|
|||
|
|
|||
|
// Verificação da faixa de inteiros com sinal...
|
|||
|
if (sig == '-') {
|
|||
|
// INT_MIN=-2147483648, que em unsigned é 2147483648
|
|||
|
if (conv > (unsigned)INT_MAX + 1) return 0;
|
|||
|
if (err) *err = 1; // Flag de estado de término: 1 = sucesso
|
|||
|
return -(int)conv;
|
|||
|
} else {
|
|||
|
if (conv > INT_MAX) return 0;
|
|||
|
if (err) *err = 1; // Flag de estado de término: 1 = sucesso
|
|||
|
return (int)conv;
|
|||
|
}
|
|||
|
}
|
|||
|
#+end_src
|
|||
|
|
|||
|
#+begin_quote
|
|||
|
Agora cabe a você, como parte dos exercícios propostos, elaborar e executar
|
|||
|
os testes e os ajustes, se forem necessários.
|
|||
|
#+end_quote
|
|||
|
|
|||
|
** Conversão em baixo nível
|
|||
|
|
|||
|
Esta seria uma solução equivalente implementada como uma sub-rotina que
|
|||
|
depende de =_str_to_uint=, criada e testada anteriormente:
|
|||
|
|
|||
|
#+begin_src asm
|
|||
|
; ----------------------------------------------------------
|
|||
|
_str_to_sint:
|
|||
|
; ----------------------------------------------------------
|
|||
|
; Converte string numérica com sinal em inteiro (int32_t).
|
|||
|
; Entradas:
|
|||
|
; RSI = Endereço da string (char *str)
|
|||
|
; RDX = Endereço para estado de erro (int *err /* nullable */)
|
|||
|
; Saída:
|
|||
|
; RAX = valor convertido (int32_t)
|
|||
|
; Sucesso: *rdx = 1
|
|||
|
; Erro: *rdx = 0
|
|||
|
; ----------------------------------------------------------
|
|||
|
push rbx ; salva rbx
|
|||
|
push rcx ; salva rcx
|
|||
|
xor rcx, rcx ; rcx = 0 (sinal: 0 = positivo, 1 = negativo)
|
|||
|
; ------------------------------------------------------
|
|||
|
; Testa ponteiro nulo
|
|||
|
; ------------------------------------------------------
|
|||
|
test rsi, rsi
|
|||
|
jz .error
|
|||
|
; ------------------------------------------------------
|
|||
|
; Verifica o sinal na string
|
|||
|
; ------------------------------------------------------
|
|||
|
mov bl, [rsi]
|
|||
|
cmp bl, '-' ; caractere de sinal negativo?
|
|||
|
jne .check_plus
|
|||
|
inc rcx ; rcx = 1 => negativo
|
|||
|
inc rsi ; avança o ponteiro
|
|||
|
jmp .convert
|
|||
|
.check_plus:
|
|||
|
cmp bl, '+' ; caractere de sinal positivo?
|
|||
|
jne .convert
|
|||
|
inc rsi ; ignora '+'
|
|||
|
.convert:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Chama _str_to_uint
|
|||
|
; ------------------------------------------------------
|
|||
|
call _str_to_uint ; RSI = str, RDX = err, RAX = resultado (uint32_t)
|
|||
|
; ------------------------------------------------------
|
|||
|
; Verifica se _str_to_uint retornou erro
|
|||
|
; ------------------------------------------------------
|
|||
|
test rdx, rdx
|
|||
|
jz .check_sign
|
|||
|
cmp dword [rdx], 0
|
|||
|
je .error
|
|||
|
.check_sign:
|
|||
|
test rcx, rcx ; se rcx = 1, é número negativo
|
|||
|
jz .check_range_pos
|
|||
|
; ------------------------------------------------------
|
|||
|
; Verifica se valor cabe em int32_t negativo
|
|||
|
; ------------------------------------------------------
|
|||
|
cmp eax, 0x80000000
|
|||
|
ja .error ; se maior que INT_MAX + 1 -> erro
|
|||
|
|
|||
|
neg eax ; aplica o sinal negativo
|
|||
|
jmp .success
|
|||
|
.check_range_pos:
|
|||
|
; ------------------------------------------------------
|
|||
|
; Verifica se valor cabe em int32_t positivo
|
|||
|
; ------------------------------------------------------
|
|||
|
cmp eax, 0x7fffffff
|
|||
|
ja .error ; maior que INT_MAX -> erro
|
|||
|
.success:
|
|||
|
test rdx, rdx
|
|||
|
jz .done
|
|||
|
mov dword [rdx], 1 ; *err = 1
|
|||
|
jmp .done
|
|||
|
.error:
|
|||
|
xor eax, eax ; valor de erro = 0
|
|||
|
test rdx, rdx
|
|||
|
jz .done
|
|||
|
mov dword [rdx], 0 ; *err = 0
|
|||
|
.done:
|
|||
|
pop rcx
|
|||
|
pop rbx
|
|||
|
ret
|
|||
|
#+end_src
|
|||
|
|
|||
|
#+begin_quote
|
|||
|
Também faz parte dos exercícios propostos, elaborar e executar os testes
|
|||
|
e os ajustes, se forem necessários.
|
|||
|
#+end_quote
|
|||
|
|
|||
|
* Exercícios propostos
|
|||
|
|
|||
|
1. Altere o valor em =num_str=, nos testes da sub-rotina =_str_to_uint=, e
|
|||
|
analise os resultados com o GDB.
|
|||
|
2. Em C, crie as funções =str_to_ulong= (string para inteiros longos sem sinal)
|
|||
|
e =str_to_slong= (string para inteiros longos com sinal).
|
|||
|
3. Em Assembly, crie as sub-rotinas =_str_to_ulong= e =_str_to_slong=.
|
|||
|
|