Jump to content

fredericopissarra

Membros
  • Content Count

    301
  • Joined

  • Last visited

  • Country

    Brazil

Everything posted by fredericopissarra

  1. Você obtem o erro: Nao é? É porque 'dados' é um array. []s Fred
  2. PS: O código acima usa SysV x86-64 ABI (só funciona em 'unixes'). O do Windows é diferente (vide Addendum).
  3. Sempre topo com essa questão em foruns e mídias como o Discord: Como usar funções da biblioteca padrão de C em códigos puramente escritos em assembly? É “fácil”, mas eu não recomendo. Primeiro mostrarei como, usando o NASM e o GCC e depois digo o por quê da não recomendação. Eis um “hello, world” simples: ; test.asm ; Compile com: ; nasm -felf64 -o test.o test.asm bits 64 ; A glibc exige "RIP relative addressing". ; De qualquer maneira, isso é sempre uma boa ; prática no modo x86-64. default rel section .rodata msg: db `hello, world`,0 section .text ; Este símbolo está na glibc! extern puts global main main: ; Chama puts(), passando o ponteiro da string. lea rdi,[msg] call puts wrt ..plt ; return 0; xor eax,eax ret O código acima é diretamente equivalente a: #include <stdio.h> int main( void ) { puts( "hello, world" ); return 0; } Para compilar, linkar e testar: $ nasm -felf64 -o test.o test.asm $ gcc -o test test.o $ ./test hello, world Simples assim. O gcc deve ser usado como linker porque ele sabe onde a libc está. O wrt ..plt é um operador do NASM chamado With Reference To. Ele diz que o símbolo está localizado em uma section específica. No caso a section ..plt, que contém os saltos para as funções da libc (late binding). Isso é necessário, senão obterá um erro de linkagem porque o linker não encontrará o símbolo… PLT é a sigla de “Procedure Linkage Table“. É uma tabela com ponteiros para as funções carregadas dinamicamente da libc. Eis o porque de minha não recomendação: O compilador nem sempre usa o nome “oficial” da função, que pode ser um #define num header e, ás vezes, existem argumentos adicionais “invisíveis” para certas funções “intrínsecas”, mas o principal argumento contra essa prática é que seu código não ficará menor ou mais rápido só porque você está usando assembly. Por exemplo, ambos os códigos em C e Assembly, acima, fazem exatamente a mesma coisa e têm o mesmíssimo tamanho final (6 KiB, extirpando os códigos de debbugging e stack protection, no caso de C). De fato, é provável que você crie um código em assembly pior do que o compilador C criaria… Addedum: Não recomendo, particularmente, que se tente escrever códigos em assembly que usem a libc no ambiente Windows. O problema é que Windows coloca algum código adicional nas funções. Por exemplo, A pilha deve ser alterada para acomodar SEH (Structured Exception Handling), no caso de main, é necessário chamar uma função chamada _main… Coisas assim são problemáticas para o novato. Fonte: Lá no #BitIsMyth: https://wp.me/pudbF-1RF
  4. PS: Usei um buffer de 24 bytes para mantê-lo alinhado na pilha, um buffer de 20 bytes seria suficiente, já que ceil(log10(2⁶³-1)) = 19.
  5. Recomendo algo assim: #include <unistd.h> void print_decimal( long n ) { char buffer[24]; // buffer local que conterá a string. char *p; // ponteiro para o buffer. unsigned long m; // O valor "sem sinal" de n. unsigned long r; // resto. _Bool signaled; size_t size; p = buffer + sizeof buffer - 1; // aponta para o fim do buffer. // Verifica sinal. signaled = 0; m = n; // O caso de n==0 falha, então precisamos disso. if ( ! n ) { size = 1; *p-- = '0'; goto print; } if ( n < 0 ) { signaled = !signaled; m = -n; } size = 0; while ( m ) { r = m % 10; m /= 10; *p-- = '0' + r; size++; } if ( signaled ) { *p-- = '-'; size++; } print: write( STDOUT_FILENO, p+1, size ); } int main( void ) { print_decimal(-12); putchar('\n'); } Convertido para asm: ; NASM code. bits 64 default rel section .text global print_decimal print_decimal: sub rsp, 40 ; Aloca espaço na pilha para o buffer. test rdi, rdi ; testa se n é 0 ou negativo. je .zero_case js .negative xor r9d, r9d ; Usa R9 como booleano (signaled). .prepare: xor ecx, ecx ; RCX é size lea rsi, [rsp+23] ; Aponta para o final do buffer. mov r8, -3689348814741910323 ; Constante usada no "macete" da multiplicação por 1/10, abaixo. jmp .divide .loop: mov rcx, rdx .divide: mov rax, rdi sub rsi, 1 mul r8 shr rdx, 3 ; Obtém o quociente da divisão por 10 em RAX mov rax, rdx lea rdx, [rdx+rdx*4] ; Calcula o resto em RDX. add rdx, rdx sub rdi, rdx mov rdx, rdi mov rdi, rax ; O quociente é o novo dividendo. add edx, '0' mov [rsi+1], dl lea rdx, [rcx+1] test rax, rax ; Dividendo é 0? jne .loop ; Não? permanece no loop. test r9d, r9d ; Tem sinal? je .print ; Não? salta para impressão. mov rax, rsi lea rdx, [rcx+2] sub rsi, 1 mov BYTE [rax], '-' .print: add rsi, 1 ; de volta para o primeiro caracter do buffer. mov edi, 1 ; STDOUT_FILENO. mov eax, edi ; SysV ABI write syscall syscall add rsp, 40 ; Dealloca espaço para o buffer. ret .zero_case: mov BYTE [rsp+23], '0' mov edx, 1 lea rsi, [rsp+22] jmp .print .negative: neg rdi mov r9d, 1 jmp .prepare Dá pra melhorar? Yep... meu ponto é que criar uma rotina em ASM não necessariamente a faz mais rápida (essa ai é mais rápida que a sua - medida!), nem menor.
  6. A ideia de empilhar os restos foi interessante, mas tem um problema: Vocẽ está empilhando QWORDs, não BYTES. Pra quê alocar o "tamanho" na pilha... Existem 16 registradores de uso geral disponíveis. Existem alguns "acertos" a serem feitos no código... Por exemplo: XOR EDX,EDX (sim EDX, não RDX) é mais performático e menor que MOV RDX,0, mas, como o @Felipe.Silva disse, estender o sinal é melhor. E você acabará com um problema com valores negativos (acho que não testou esses, né?)... -12, por exemplo, resultará num resto de -2 na primeira divisão, o que empilhará '0'-2 ou o caracter '.'. Ainda... multiplicações são sempre mais rápidas que divisões. Veja isso: https://is.gd/rB9Vlb Como calcular o resto... Ora... se tem o quociente Q, o resto de A/10 é A - (Q*10) "Q*10" pode ser fácilmente calculado com 'LEA RDX,[RAX+RAX*4] / ADD EDX,EDX' (unsigned).
  7. You should not USE you system as root, period.
  8. Experimente rodar ./configure antes do make. O script configure é criado pelo autoconf e CONFIGURA seu código, criando o arquivo config.h, por exemplo, antes da compilação.
  9. Como ele monta todo o header "na mão", o formato para compilação com o NASM (ele não usa MASM) deve ser bin: nasm -fbin mini.asm -o mini.exe
  10. tsk, tsk, tsk... pedindo pros outros fazerem seu trabalho escolar... que vergonha!
  11. Estranho... o código está certo e o checksum PRECISA estar em BIG ENDIAN... vou investigar... Thanks.
  12. @mandelacruz, lembro a você que "herrar é umano" e, assim como Fernando disse, a iniciativa de aprender é sempre louvável! Meu comentário deve-se apenas ao fato de que algumas informações são facilmente encontradas... [[]]ão Fred
  13. Wikipedia é seu amigo: https://en.wikipedia.org/wiki/Insertion_sort Em pt-br não é tão bom: https://pt.wikipedia.org/wiki/Insertion_sort
  14. Era o que eu tinha em mente mesmo! 🙂
  15. PS: "CAST" é uma daquelas palavras em inglês difíceis de fixar... "CAST" significa elenco (teatro), mas também "jogar" (como em "cast a spell" ou "jogar um feitiço"). Mas, também é o nome que se dá ao suporte usado quando quebramos um braço ou uma perna (um "molde")... Esse último parece ser o neologismo para "casting", no contexto de C...
  16. De novo, a dúvida ainda está confusa... "casting de função para string"? Em primeiro lugar, não existem "strings" em C (mas sim arrays de chars, onde o último char é um '\0')... Em segundo lugar, o identificador de uma função é um ponteiro, bem como o identificador de um array é convertido para um ponteiro. No seu exemplo, eu suponho que a função crc32() tenha o protótipo: uint32_t crc32( unsigned char *ptr, size_t size ); Quando o argumento ptr poderia ser, muito bem, definido como um ponteiro para void e, neste caso, qualquer casting de ponteiro na passagem deste argumento para a função seria supérfluo (void * aponta para qualquer coisa). Este é o mesmo caso de chamadas à função malloc(), por exemplo: char *p; p = malloc( 16 ); // aloca 16 bytes e devolve o ponteiro. Casting aqui é desnecessário porque malloc() tem o protótipo: void *malloc( size_t size ); Como o @Marioh disse ai em cima, casting é uma maneira de reinterpretar um tipo. Por exemplo: int x = 1, y = 2; float z; // O casting aqui é "obrigatório" porque senão a divisão será feita // com INTEIROS, resultando num INTEIRO que depois será convertido para // float... Com o casting, a divisão será entre valores em PONTO FLUTUANTE. z = (float) x / y; // z = 0.5; z = x / y; // isso resulta em 0.0! Casting pode ser usado para forçar a barra da atualização de um ponteiro também: char *p; ... // inicializa p em algum lugar... ... *(int *)p++ = 1; // aqui p[0]=1, p[1] até p[3]=0 (little endian). // mas, p será avançado em 4 posições... (sizeof(int)). Quando fazemos a conversão de tipos de menor precisão para maior, não precisamos de castring também. A promoção de tipos funciona direitinho... Para fazer a "demoção" de tipos o castring pode ser necessário para evitar avisos do compilador: char a; int b = 10; a = (char)b; // casting para evitar (?) warnings... nem sempre evita! b = a; // castring desnecessário. Agora... no seu exemplo, você está passando um ponteiro para uma função (checa) para a função crc32(). O que é válido, mas LER a memória onde a função foi codificada é um "comportamento indefinido" (undefined behavior) - escrever nessa memória, provavelmente causará um SEGMENTATION FAULT - um erro. Note que, no seu caso, o identificador checa equivale ao ponteiro: _Bool (*checa)( char *, int ); Se seu crc32() for definido como: uint32_t crc32( unsigned char *, size_t ); Então o pontiero terá que sofrer casting para evitar avisos... Note que isso pode não funcionar em todas as plataformas. Algumas assumem ponteiros para região de código com tamanho diferente da ponteiros para região de dados... Outras não permitem isso...
  17. Prefira sempre compilar seus códigos com a opção -O2 (pelo menos). Isso criará código mais "enxuto". A função checa(), por exemplo, ficará assim: ; Entrada: RDI = nome, ESI = serial ; Saída AL (0 = false, 1 = true). checa: movsx eax, BYTE PTR [rdi] ; Lê o primeiro byte. xor edx, edx ; edx = serialcorreto = 0 test al, al ; fim de string? je .endOfString ; sim, salta para a comparação. .loop: add rdi, 1 ; aponta para o próximo byte add edx, eax ; acumula o checksum. movsx eax, BYTE PTR [rdi] ; lê o próximo byte. test al, al ; fim de string? jne .loop ; não, volta ao loop. .endOfString: cmp esi, edx ; serial == serialcorreto? sete al ; ajusta AL. ret No entanto, não entendi qual é a dúvida, especificamente...
  18. Exemplo: #include <stdio.h> #include <string.h> int main( void ) { char v, *p; char buffer[16]; // Lê 1 char de stdin, mas ignora todos os "separadores" (isspace()) subsequentes... scanf( "%c ", &v ); // Lê 16 chars à partir do primeiro caracter válido do stream stdin. fgets( buffer, sizeof buffer, stdin ); // Livra-se do '\n' no final da string, se houver. if ( p = strchr( buffer, '\n' ) ) *p = '\0'; printf( "'%c' : \"%s\"\n", v, buffer ); } Testando: Ahhhhhhhhhh... NÃO use gets()... é uma função obsoleta!
  19. Manda ver... mas, ainda não vejo muita utilidade em adicionar payloads no T50... Como o Nelson insiste: Acho que seria mais útil adicionar protocolos... Aliás... algo que dará trabalho para fazer: Adicionar suporte a IPv6!
  20. Sorry... só vi agora. Fiquei internado num hospital na última semana (não... não foi o coronga!)... O detalhe é que scanf varre um stream em busca do formato desejado, mas deixa para trás o restante, uma vez atendido o formato. Ao usar "%c" você pede a conversão de um único caractere, todos os demais que o seguem, "ficam para trás"... Acontece que stdin normalmente precisa de um '\n', quando o redirecionamento é feito para o teclado e '\n' é o final de linha interpretado pelo fgets() também... Não... usar fflush com streams de entrada não resolve, uma vez que, como @Marioh apontou corretamente, isso é undefined behavior. De fato, o comportamento, em alguns OSs, é simplesmente ignorar a ordem de "despejo". Você pode tentar um scanf() diferente: scanf("%c ", &value_menu); Note o espaço depois do "%c". Isso faz com que quaisquer caracteres "separadores" sejam varridos, mas ignorados.
  21. O problema com rand() é o mesmo problema com a maioria dos PRNG (Pseudo Random Number Generators)... Eles não são, de fato, aleatórios. A não ser por efeitos físicos, como decaimento radioativo, flutuações quânticas, etc, não há como obter valores realmente aleatórios - pode-se até mesmo usar meios físicos não tão especializados como contar a quantidade de batimentos de asas de um colibri se alimentando em 1 segundo ou alguma outra grandeza que pareça aleatória (algumas certificate authorities usam meios assim!)... rand() geralmente é implementado usando-se o Linear Congruential Generator, que nada mais é do que uma equação linear: Onde X0 é a "semente" informada via função srand(). Mas, esse esquema tem um problema... Se m for descartado ou for o tamanho da "palavra", então os bits inferiores tendem a ser menos aleatórios que os bits superiores (veja Knuth)... Felizmente a glibc tenta resolver isso com uma série de equações lineares e uma "mistura" binária "esperta": // rand_r() é a versão therad-safe de rand() e a base do mesmo. int rand_r (unsigned int *seed) { unsigned int next = *seed; int result; next *= 1103515245; next += 12345; result = (unsigned int) (next / 65536) % 2048; next *= 1103515245; next += 12345; result <<= 10; result ^= (unsigned int) (next / 65536) % 1024; next *= 1103515245; next += 12345; result <<= 10; result ^= (unsigned int) (next / 65536) % 1024; *seed = next; return result; } Aqui temos 3 valores pseudo aleatórios obtidos com uma única semente que são "misturadas" com outro método PRNG chamado linar feedback shift registers. O quão aleatório isso é, não faço ideia... Mas, o problema permanece: Se eu sei qual é a semente inicial e a equação que é usada para obter os valores subsequentes, eles deixam de ser pseudo aleatórios para serem previsíveis! Geralmente escolhemos uma semente que também pareça aleatória. Uma das formas é usar o epoch timestamp, obtidas em qualquer sistema baseado em UNIX com a função time(): // time() retorna o número de segundos ocorridos // desde 1º de janeiro de 1970, às 0:00, UTC. srand( time( NULL ) ); Mas um sujeito chato e metódico pode obter esse valor de antemão... Uma solução é confiar na "aleatoriedade" de certas APIs e dispositivos contidos no seu sistema operacional para obter uma semente aleatória e depois usar rand() para obter os demais valores mais rapidamente: // Inicializa a semente com um valor "aleatório" no UNIX: void setup_seed( void ) { unsigned int seed; int fd; ssize_t size; if ( ( fd = open( "/dev/urandom", O_RDONLY ) ) < 0 ) { perror( "open" ); exit( EXIT_FAILURE ); } errno = 0; size = read( fd, &seed, sizeof seed ); if ( errno || size < sizeof seed) { fputs( "read error\n", stderr ); close( fd ); exit( EXIT_FAILURE ); } srand( seed ); close( fd ); } Ler /dev/urandom é, com toda certeza, mais lento do que usar rand(), mas resolve o problema da semente original. Não resolve o problema da aleatóriedade previsível do LCG ou qualquer outro PRNG, mas minimiza a coisa toda nos dando valores que "parecem" aleatórios.
  22. Yep... C++11 tem uma série de templates para "uniformização" de distribuições de inteiros e um monte de templates com "random engines". Mas, se você não souber usá-los, vai piorara a coisa... E mais: EU NÃO GOSTO DE C++!
  23. Pode-se imaginar uma maneira, menos eficiente em termos de performance, para eliminar o problema da distribuição não uniforme trabalhando com ponto flutuante. Infelizmente isso tem alguns problemas... Se RAND_MAX tem 31 bits de tamanho, usaremos o tipo double, que tem 53, para garantir a precisão e fazer algo assim: #define RANGE 6 int r; // r estará entre 0 e (RANGE - 1). r = rand() / ( ( double )RAND_MAX / RANGE + 1 ); A distribuição dos valores resultantes estará em "clusters"... No caso, cada um dos 6 valores estará dentro de uma faixa de 0.1666 (1/6)... 0 está na faixa [0;0.1666...); 1, [0.1666...;0.33333...); ... Acontece que rand() pode devolver qualquer valor entre 0 e RAND_MAX, ou seja, valores entre [0; 0.1666...*RAND_MAX), neste exemplo, serão necessariamente 0... Ou seja, qualquer valor de rand() retornado entre 0 e 357913941 será zero! Você acaba de obter um RNG bem menos aleatório que o original.
  24. Um exemplo de geração de ticket para MEGA SENA tendando corrigir a discrepância da não uniformidade de distribuição, por rejeição: /* Sorteio MEGA SENA, corrigindo a distribuição não uniforme. */ #include <stdio.h> #include <stdlib.h> #if defined(__x86_64) || defined(__i386) #include <immintrin.h> #endif // Usar rand() não é uma solução ideal, mas pode ser preciso, // se __RDRND__ não estiver definido. #ifndef __RDRND__ #include <unistd.h> #include <fcntl.h> #include <time.h> #endif /* Para obter aleatoriedade uniforme e garantir que o valor seja verdadeiramente aleatório, uso a instrução RDRAND, disponível nos processadores Intel/AMD. Usei _rdrand16_step() na esperança que a entropia não seja prejudicada entre chamadas. Note, também, que essa rotina retorna o resto da divisão por 64 (6 bits). Isso garante que a distribuição seja uniforme e, mais adiante, simplesmente discarto os valores maiores que 60. */ #ifdef __RDRND__ unsigned short get_random_64( void ) { unsigned short r; unsigned int retries; retries = 10; do if ( _rdrand16_step( &r ) ) return r & 0x3f; // o mesmo que 'r % 64'. while ( --retries ); fputs( "\n\033[31;1mERROR\033[m: Could not get random value.\n", stderr ); exit( EXIT_FAILURE ); } #else // Usa rand(), se RDRAND não estiver disponível. unsigned short get_random_64( void ) { return rand() % 0x3f; } // Obtem uma semente mais aleatória que time(NULL)... // Não funciona no Windows! unsigned int get_random_seed( void ) { int fd; unsigned int r; fd = open( "/dev/urandom", O_RDONLY ); if ( fd < 0 ) { fputs( "\n\033[31;1mERROR\033[m: Cannot open random device!\n", stderr ); exit( EXIT_FAILURE ); } if ( read( fd, &r, sizeof r ) != sizeof r ) { close( fd ); fputs( "\n\033[31;1mERROR\033[m: Error reading from random device.\n", stderr ); exit( EXIT_FAILURE ); } close( fd ); return r; } #endif int main( void ) { /* LEMBRE-SE: O array começa no índice 0! */ _Bool values[60] = {0}; int count; unsigned int r; /* Alimenta a semente, se __RDRND__ não estiver definido. */ #ifndef __RDRND__ srand( get_random_seed() ); #endif fputs( "Discarded: ", stdout ); count = 6; do { r = get_random_64(); // Somente entre 0 e 59 são aceitos. // NÃO é uma solução ideal o descarte dos valores // do último bloco, incompleto, mas é uma solução! if ( r > 59 ) { printf( "%2u ", r ); continue; } // Se o valor já foi sorteado, tenta de novo... if ( values[r] ) continue; // Marca como sorteado. values[r] = 1; // Obtivemos o valor, continua enquanto ainda precisamos // de mais... count--; } while ( count ); // Apresenta o ticket de maneira ordenada. fputs( "\nTicket: ", stdout ); r = 0; while ( r < sizeof values / sizeof values[0] ) { if ( values[r] ) printf( "%2u ", r + 1 ); ++r; } putchar('\n'); return EXIT_SUCCESS; } Funciona em Linux (qualquer plataforma), mas para melhores resultados na aleatoriedade, compile para Intel/AMD com: $ gcc -O2 -march=native -o mega mega.c
  25. Outro detalhe importante para se lembrar sobre aleatoriedade é que ela não significa que você não possa obter um determinado valor mais vezes do que outros. Se fato, uma sequência do tipo (1,2,2,3,1,5,4) continua sendo aleatória, mesmo que 1 e 2 sejam repetidos... O problema todo está na distribuição das chances de obtenção dos valores... Outro exemplo é a MEGA SENA... todo mundo faz jogos com valores "aparentemente" aleatórios, mas se esquecem que, por exemplo, a sequência (1,2,3,4,5,6) tem a mesma chance de ser sorteada que qualquer outra, se considerarmos a aleatoriedade perfeita (uniformemente distribuída).
×
×
  • Create New...