Jump to content

fredericopissarra

Membros
  • Content Count

    235
  • Joined

  • Last visited

Everything posted by fredericopissarra

  1. Yep... a opção -S serve para gerar o código em ASM a partir do código em C, então o programa em test.c tem que ser compilável. Você pode usar -fverbose-asm se quiser os aqueles montes de comentários, mas para análise do código gerado ele não é necessário.
  2. Não estou dizendo que sua aproximação está errada ou que seja ruim. Só o que estou dizendo é que, talvez, ela possa falhar em algum momento. Limitar os testes a uma faixa que "dá certo" não quer dizer que o algoritmo vai funcionar para toda a faixa... A otimização que postei (que não foi minha, vi num forum e achei interessante) funciona para qualquer valor N, inteiro, de qualquer precisão... O sujeito que bolou isso percebeu que quando um valor é primo e é maior que 4, ele pode ser expresso por 6n+/-k (onde k={0..5}, ou seja, k=n mod 6)... mas, k não pode ser 0, 2, 3 ou 4 (senão teremos valor par [k=0,2,4] ou divisível por 3 [k=3]). Assim, k só pode ser 1 ou 5. Agora, tomemos n=1 e obteremos 5 (6*1-1) e 7 (6*1+1), e 1 (6*1-5) e 11 (6*1+5). Esses são candidatos a primazia... Se você fizer n=2 (n+1), teremos 11 (6*2-1), 13 (6*2+1), 7 (6*2-5) e 17 (6*2+5). Claramente (e podemos comprovar isso por congruência) testes com k=5 são cobertos pelos mesmos testes onde k=1 e podemos descartar k=5. Dai, o testes de primazia só precisam ser feitos com 6n-1 e 6n+1. Por isso o loop começa em 5, avançamos k em 2 unidades (de -1 para +1), testamos o próximo e testamos de novo... avançamos 4 unidades (por causa do 6n) e fazemos tudo de novo... O que descrevi acima é uma prova por "indução matemática" e é garantido que o resultado seja o mesmo para qualquer N... Teríamos que provar, de maneira semelhante, que sua aproximação é válida para qualquer N inteiro positivo e é isso que eu não tenho certeza (porque não pensei à respeito) que esteja correto. Quanto ao crivo de Erastótenes. Basicamente ele realiza as divisões apenas com os valores prímos previamente encontrados, no loop, mas precisa armazená-los num grande array. Esse é o método mais "rápido" (e um pouquinho mais complicado), mas potencialemente usa muita memória para isso. No caso, mesmo não usando arrays, a cadeia de testes ficaria muito grande e é bom lembrar que cada divisão é LENTA. Uma rotina com 2 divisões não é cria lá grande perda de velocidade, mas uma com 8 divisões dentro do loop tem o potencial de ser bem lenta. [[]]ão Fred
  3. Well... Talvez, temos que verificar (a validade do algoritmo anterior é um cadinho mais complicada do que eu postei - desse ai, eu não sei)... Mas, de qualquer maneira, temos que parar em algum lugar, não? Senão o algoritmo degenera para o crivo de Erastótenes.
  4. Só um aviso aos amigos... esse livro não é a versão definitiva. Note que é a versão 0.33.9 (nem sequer é a versão 1). Ele não contém tudo o que eu gostaria de falar sobre a mesclagem de C e ASM na arquitetura x86-64 e talvez seja prolixo em alguns pontos (também não foi tão bem revisado assim)... Anyway, espero que seja útil pra alguém... 😉 Ele e o "Linguagem..." foram ensaios para a confecção de um livro mais e mais completo que ainda estou escrevendo (e esse novo livro eu pretendo publicar)...
  5. Só um aviso aos amigos... esse livro não é a versão definitiva. Note que é a versão 0.8.5 (nem sequer é a versão 1). Ele não contém tudo o que eu gostaria de falar sobre ASM e talvez seja prolixo em alguns pontos (também não foi tão bem revisado assim)... Anyway, espero que seja útil pra alguém... 😉 Ele e o "Dicas" foram ensaios para a confecção de um livro mais e mais completo que ainda estou escrevendo (e esse novo livro eu pretendo publicar)...
  6. Já eu, @bratergames, nunca parei de estudar e estou com os ARM da vida, mas não "apenas"... 🙂 Tio Fred só sumiu do facebook (nunca mais entro naquela bosta) e do discord do mente binária...
  7. Artigo original: https://is.gd/55lrpk Alguns de vocês já devem ter lido por aqui e feito experiências com relação ao “endereço” base de um array. Considere o seguinte array: int a[] = { 1, 2, 3, 4 }; O identificador a é, de forma bastante comum, encarado como o ponteiro para o início do array. De fato, a especificação ISO 9989 nos diz que o operador [] pode ser, seguramente, interpretado como a = *(a + i). Mas, há uma distinção sutil aqui: O identificador a não é um ponteiro! Ele é interpretado como sendo um… “Porra, tio Fred, assim você dá um nó na minha cachola!”. O problema aparece quando usamos o operador & (endereço-de): #include <stdio.h> int a[] = { 1, 2, 3 4 }; int main( void ) { printf( "a=%p, &a=%p\n", a, &a ); } Ao compilar e executar isso você verá que os endereços impressos são os mesmos. Como pode um ponteiro apontar para uma coisa e um ponteiro para um ponteiro apontar para a mesma coisa? Não pode! O fato é que a, usando usado em expressões, é convertido em um ponteiro, mas ele não é um. O símbolo a aqui é um array “identificado” pelo nome a. Parece que não há diferença, não é? Então, vejamos esse outro exemplo: Se a e &a forem, de fato, o mesmo ponteiro, apontar para o próximo item do array deveria fornecer o mesmo endereço, não é? #include <stdio.h> int a[] = { 1, 2, 3 4 }; int main( void ) { printf( "a=%p, &a=%p\n", a, &a ); printf( "next(a)=%p, next(&a)=%p\n", a + 1, &a + 1 ); } Compilando e executando você obtém algo assim: $ cc -O2 -o test test.c $ ./test a=0x563c50e5a010, &a=0x563c50e5a010 next(a)=0x563c50e5a014, next(&a)=0x563c50e5a020 Ué?! Por que &a+1 resultou em um endereço diferente de a+1? Note que em nossa tentativa de obter o endereço do próximo item com o ponteiro, no primeiro caso obtemos, corretamente, o endereço 4 bytes além da base, mas no segundo (usando &a), obtemos o endereço além do final do array (16 bytes além da base)… Isso ocorre porque os tipos dos ponteiros convertidos à partir de a são diferentes… O primeiro ponteiro, a+1 é do tipo int *, o segundo, do tipo int (*)[4]. Ou seja, a e &a fornecem o mesmo endereço porque estamos obtendo o endereço do identificador a, que aponta para o início do array. No primeiro caso (a) o identificador é convertido para um ponteiro, no segundo (&a) obtemos o “endereço-do array identificado por a. Sim, você pode assumir que o identificador de um array seja um ponteiro para sua base, se usar apenas o nome do identificador como ponteiro (não o seu endereço), mas, além do problema acima, existe outro: O identificador de um array definido carrega consigo, em tempo de compilação, o tamanho do array. Se você usar o operador sizeof com o identificador a, obterá 16 (4 vezes o tamanho de um int), no exemplo acima… Se a fosse apenas um ponteiro, obteria 4 (i386) ou 8 (x86-64) que, aliás, é o que obterá se tentar obter sizeof(&a).
  8. PS: Eu não pensei nesse terceiro método. Ele me foi apresentado num forum, onde duvidei bastante da "correção" matemática dele. Mas, acaba que o troço tá certo mesmo... Vivendo e aprendendo...
  9. Artigo original: https://is.gd/sDQ7Oa Esse é um dos exercícios mais comuns na área acadêmica: Dizer se um número é primo ou não. Existe a maneira tradicional, a maneira rápida e a maneira super rápida… As duas primeiras eram minhas velhas conhecidas e já falei delas aqui, eis um resumão: O método tradicional é dividir todos os valores inferiores ao número n, sob teste, até chegarmos a 2 e se o resto de qualquer uma das divisões der zero, isso indica uma divisibilidade e, portanto, o valor não é primo: int isPrime( unsigned int n ) { unsigned int i; // casos especiais... 3 e 2 são primos, mas 1 e 0 não são! if ( n <= 3 ) return ! ( n < 2 ); for ( i = 2; i < n; i++ ) if ( ( n % i ) == 0 ) return 0; return 1; } O método rápido reduz o a quantidade de iterações do loop porque só precisamos verificar a divisibilidade de valores inferiores ou iguais à raiz quadrada de n. Isso é baseado na propriedade comutativa e do princípio fundamental da aritmética: 2*3 é a msma coisa que 3*2, então não precisamos testar os dois casos. Ainda, qualquer valor pode ser fatorado em primos… Outro detalhe importante para o teste é que só existe um valor par primo: 2, todos os demais são ímpares: int isPrime( unsigned int x ) { unsigned int i, s; if ( n <= 3 ) return ! ( n < 2 ); // Apenas 2 é um valor par primo! if ( n % 2 == 0 ) return 0; // Só precisaremos testart até a raiz quadrada... s = sqrt(n); // Não precisamos testar a divisibilidade por nenhum valor par. for ( i = 3; i <= s; i += 2 ) if ( ( n % i ) == 0 ) return 0; return 1; } Quando você acha que achou o código mais rápido possível algum detalhe te supreende. O código acima pode ser acelerado um bocado se pensamos que se o valor n é divisível por 6, então ele é divisível por 3 e por 2 e, portanto, não é primo. De outro modo, podemos retirar valores que, divididos por 6, resultam em restos 2, 3 ou 4, já que, com toda certeza, eles não são primos (ou são divisíveis por 2 ou 3): int isPrime(unsigned int n) { unsigned int i, s; if (n <= 3) return ! ( n < 2 ); // Mais um caso especial... Se for divisível por 2 ou 3, não é primo! if ( ( n % 2 ) == 0 || ( n % 3 ) == 0) return 0; s = sqrt(n); for ( i = 5; i <= s; i += 4 ) { if ( ( n % i ) == 0) return 0; i += 2 if ( ( n % i ) == 0) return 0; } return 1; } O código acima executa o loop cerca de 3 vezes mais rápido que o código anterior e é matematicamente correto. Claro, existe o problema de que duas divisões são feitas no loop, mas o compilador tende a otimizar isso… PS: Lembrando que existe um outro método, rápido, mas que consome muita memória: Trata-se do “Crivo de Eastótenes” que encontra os valores primos tentando as divisões com os valores primos anteriormente encontrados (até a raiz quadrada de n, como acima)… O “Crivo” é o método mais rápido, mas como eu disse, tem o potencial de consumir MUITA memória, porque precisamos manter um array com os valores previamente encontrados.
  10. Version v0.33.9

    310 downloads

    Livro gratuito, de mais de 200 páginas, sobre Assembly 64-bits e C, além de outros assuntos. É repleto de dicas, teoria e prática. 🙂 Segue o conteúdo resumido: Introdução Capítulo 1: Introdução ao processador Capítulo 2: Interrompemos nossa programação Capítulo 3: Resolvendo dúvidas frequentes sobre a Linguagem C Capítulo 4: Resolvendo dúvidas sobre a linguagem Assembly Capítulo 5: Misturando C e Assembly Capítulo 6: Ferramentas Capítulo 7: Medindo performance Capítulo 8: Otimizações “automáticas” Capítulo 9: Caches Capítulo 10: Memória Virtual Capítulo 11: Threads Capítulo 12: Ponto flutuante Capítulo 13: Instruções Estendidas Capítulo 14: Dicas e macetes Capítulo 15: Misturando Java e C Capítulo 16: Usando Python como script engine Apêndice A: System calls Apêndice B: Desenvolvendo para Windows usando GCC Apêndice C: Built-ins do GCC Apêndice D: Módulos do Kernel
  11. Version v0.8.5

    260 downloads

    Livro gratuito, de aproximadamente 100 páginas, sobre Assembly 32 e 64-bits. Segue o conteúdo resumido: Introdução Capítulo 1 - Conceitos Básicos Capítulo 2 - A linguagem Assembly Capítulo 3 – Assembly e outras linguagens Capítulo 4 – Instruções Capítulo 5 – Ponto flutuante Capítulo 6 – SIMD Vetorizado Capítulo 7 – Performance
  12. 158 downloads

    Curso completo de Assembly 16-bits. Apesar de antigo, pode ajudar a entender as bases de Assembly. Vale a lida! 🙂 Segue o programa com 26 aulas: Embasamento Aritimetica binária Registradores Pilha e Flags Instruções de armazenamento e blocos Instruções Lógicas Instruções Aritiméticas Instruções de comparação Saltos Interrupções Shifts Mais instruções de comparação Usando assembly no TURBO PASCAL Usando assembly em C Usando o TURBO ASSEMBLER Mais TASM Entendendo o EMM386 Usando o EMM386 O processador 386 strlen e strcpy em Assembly Aritimética em ponto-fixo Mexendo com a VGA Latches e bitmasks Mexendo ainda mais na VGA O modo de escrita 1 da VGA O modo de escria 3 da VGA O modo de escrita 2 da VGA
  13. Artigo original: https://is.gd/28eIsS Num outro artigo (este aqui) mostrei como obter o valor representado pela estrutura de um tipo ponto flutuante (float) e vice-versa, mas não mostrei um código fonte de exemplo. Eis aqui um, mas com falhas, porque não leva em consideração valores muito grandes, muito pequenos, subnormais, infinitos e NaNs. Ele converte um valor em ponto fixo (limitado), onde a parte inteira i e a parte fracionária f do valor é fornecida à função. Como está a parte fracionária jamais poderia ser 0.01 ou 0.00034, etc. Apenas valores como: 3.14, 100.4003, etc podem ser fornecidos. A rotina converte o valor f na componente fracionária e junta as duas numa grande variável de 64 bits, deslocando os bits até isolar o bit de mais alta ordem no bit 32 (o 1.0. implícito num float) e daí obtemos o valor em ponto flutuante. Deixo o código para a análise de vocês: #include <stdio.h> #include <assert.h> // União com a estrutura de um float, para faciliar // as coisas... union fp_u { float f; struct { unsigned int m:23; unsigned int e:8; unsigned int s:1; } __attribute__((packed)); }; // Calcula o log, na base 10, de x. static int log10_( unsigned int x ) { int n = 0; while ( x >= 10 ) { x /= 10; n++; } return n; } // Calcula 10^x (x precisa ser menor que 10). static unsigned long long pow10_( unsigned int x ) { unsigned long long n = 1; assert( x < 10 ); while ( x-- ) n *= 10; return n; } // Converte um ponto fixo no formato (i,f) para float. // Essa rotina tem multiplas falhas: // A parte fracionária não permite valores como 0.01, por exemplo. // Não verifico por subnormais, nans e infinitos. // Não trato valores negativos. float fixed2float( unsigned int i, unsigned int f ) { unsigned int m; unsigned long long n; int e = 0; union fp_u fp = { .f = 0.0 }; // OK... 0.0f é apenas 0x00000000. // 0.0 é um caso especial. if ( i != 0 && f != 0 ) { // Precisamos transformat a parte fracionária na represetação binária // do número fixo. Poderíamos fazer isso com ponto fluautuante, mas o objetivo // aqui é evitar ponto flutuante, não é? // // A mágica aqui é que na representação de número fixo (iiii.ffff) a parte fracionária // é (f / 2³²), mas temos apenas f inteiro. Então, coloco f na parte inteira (multiplicando por // 2³², e divido pela grandeza do valor (10^(log(f)+1)). // // Isso garante que m terá apeans 32 bits de tamanho final e todo o cálculo é inteiro (nada de // ponto flutuante aqui). m = ( ( unsigned long long )f << 32 ) / ( pow10_( log10_( f ) + 1 ) ); // Agora que temos a representação fracionária correta, basta colocar os dois valores // nas posições corretas. A parte inteira são os 32 bits superiores, a parte fracionária, os // 32 bits inferiores. n = m + ( ( unsigned long long )i << 32 ); // Um float normalizado sempre tem 1 implícito. Assim, se qualquer um dos 31 bits supeiores for 1, // então temos que fazer deslocamentos para a direita, senão, para a esquerda, até que os 32 bits // superiores contenha apenas 1. // // OBS: ~0ULL << 33 == 0xfffffffe00000000ULL, // ~0ULL << 32 == 0xffffffff00000000ULL e, é claro, // 1ULL << 32 = 0x0000000100000000ULL. if ( n & ( ~0ULL << 33 ) ) { // Ao deslocar para a direita, incrementa o expoente. // OBS: Dá pra melhorar isso contanto a quantidade de 'leading zeros' e deslocando a // quantidade necessária de uma vez só... while ( ( n & ( ~0ULL << 32 ) ) != ( 1ULL << 32 ) ) { n >>= 1; e++; } } else { // Ao descolar para a esquerda, decrementa o expoente. // OBS: Dá pra melhorar isso contanto a quantidade de 'leading zeros' e deslocando a // quantidade necessária de uma vez só... while ( ! ( n & ( 1ULL << 32 ) ) ) { n <<= 1; e--; } } fp.m = n >> 9; // Joga fora os 9 bits inferiores (precisams de apenas 23 bits). fp.e = 127+e; // Calcula o expoente do float. fp.s = 0; // não lido com sinal, por enquanto! // Acerta o arredondamento. Precisamos arredondar a "mantissa" se o 9º bit for 1. if ( n & 0x100 ) fp.m++; // Se a "mantissa" chegou a zero, temos que delocar mais um bit, decrementando o expoente. if ( ! fp.m ) fp.e--; } return fp.f; } // Um pequeno teste. int main( void ) { printf( "%.20f = %.20f\n", 3.1416f, fixed2float( 3, 1416 ) ); } Compilando e executando: $ cc -o test test.c $ ./test 3.14159989356994628906 = 3.14159989356994628906
  14. Outra coisa importante sobre "notação científica" e sobre ponto flutuante no padrão IEEE 754 é que o número de algarismos é FIXO... Se alguém te pede PI com 4 algarismos você tem que, obrigatóriamente escrever 3.142*10⁰ (o último algarismo têm que ser arredondado)... A mesma coisa acontece com ponto flutuante: O valor armazenado na estrutura tem número fixo de bits (24 para float, 53 para double, 64 para long double e 113 para __float128, se existir). Isso tem uma consequência importante... O tipo float, mesmo podendo representar valores na ordem de 2¹²⁷ (ou 10³⁸), não permite o armazenamento, com precisão, mais de 24 bits... Um valor como 123456789 (da ordem de 10⁸) não pode ser armazenado de forma exata, só aproximada. Eis um exemplo: #include <stdio.h> int main( void ) { int x = 123456789; float y = x; printf( "%d -> %f\n", x, y ); } O resultado será "123456789 -> 123456792.000000". Isso porque 123456789 (0x75BCD15) tem 27 bits de tamanho (3 a mais que cabem num float). Daí, os 3 bits inferiores são perdidos e um arredondamento é feito no 4º bit (por isso ficou maior)... Note que um int, em teoria, permite o armazenamento de valores da ordem de 10⁹, bem menos que um float, mas isso não significa que é possível armazenar o valor de um int de forma EXATA num float. Evide misturá-los, se possível. Então, ao lidar com ponto flutuante, não somente o tipo (subnormal, normalizado, nans, infinitos) são importantes, mas também a precisão binária.
  15. Sobre a notação científica. Quem quer que tenha lidado com eletricidade ou eletrônica pode discordar do conceito, já que para facilitar a escrita de sub-unidades (mili, micro, pico... ou quilo, mega, giga) é costumeito usar a parte inteira com mais de um algarismo... Em cálculos é comum usar 20.0*10⁻³ A para escrever, depois, 20 mA. Mas isso não está normalizado... Ou seja, não é "notação científica"... A "norma" é clara... o algarismo inteiro é sempre um só e sempre diferente de zero.
  16. Perguntaram isso hoje... Em float.h temos uma série de constantes léxicas que definem valores máximos e mínimos para vários aspectos relativos a ponto flutnate. Um deles é DBL_MIN que quem não leu a especificação ISO 9989 ou não compreende, de fato, o que é ponto flutuante, assume que seja o menor valor possível para o tipo double. Não é... DBL_MIN é o menor valor normalizado possível. O padrão IEEE 754 define 4 "tipos" de valores para ponto flutuante: subnormais, normalizados, "não é um número" e infinitos. Geralmente queremos ficar dentro da faixa dos valores normalizados. Mas, que "norma" é essa? Lembram-se da "notação científica" da época das aulas de Física na escola? Eles são definidos como tendo um número bem definido de algarismos onde o primeiro deles, a parte inteira, é sempre escrita com um algarismo diferene de zero, segue uma potência de 10 que funciona como escala... Assim, 3002 pode ser escrito como 3.002*10³, em notação científica.... Essa "norma" não permite que a parte inteira seja zero... A mesma coisa acontece com ponto flutuante binário. O número é estruturado de acordo com a equação: Onde F é um inteiro de 52 bits de tamanho, E é um inteiro de 11 bits de tamanho e S tem 1 bit. Repare o 1 somado a fração. Isso significa que, na equação acima, o valor sempre tem esse 1 como valor inteiro (implícito) na estrutura do double... Mas a equação acima só é usada se 0<E<2047. Se E==0 então temos um valor subnormal, onde o 1.0, implícito, não está lá. De fato, a parte inteira é 0, neste caso e a equação muda: Aqui, F continua tendo 52 bits e a fração continua sendo menor que 1, mas note que agora temos uma escala fixa. Sim, o valor 0.0 é um subnormal especial (onde F=0)... Ele é especial porque cálculos involvendo 0.0 não causam problemas de performance. Lidar com subnormais num cálculo, em ourto caso, sempre causa problemas de performance (a instrução que lida com um valor desses gasta mais tempo para terminar o processamento). Voltando: O valor DBL_MIN geralmente é definido como 2.2250738585072014E-308 e obter esse valor é simples. Observando a equação dos doubles normalizados, se tivermos F=0 e E=1, teremos apenas 2⁻¹⁰²² como valor final: $ bc -q scale=350 2^-1022 .0000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000022250738585072013830902327173324\ 04064219215 Mas, como disse antes, existem valores menores que esse ainda... O menor valor possível, subnormal, diferente de zero, para um double é quando F=1 e E=0, o que nos dá aproximadamente 4.9406564584124654E-324, obtido com esse pequeno programa: #include <stdio.h> int main( void ) { unsigned long long x = 1; // F=1, E=0, S=0 double *p = (double *)&x; printf( "%.16e\n", *p ); } O mesmo valor pode ser obtido com o bc usando a equalção para valores subnormais: $ bc -q scale=350 (1/2^52)*2^-1022 .0000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000004940656458412465\ 44176568792 Que, é claro, é a mesma coisa que 2⁻¹⁰⁷⁴. Os valores NaN e infinitos têm E=2047 e não nos interessam no contexto deste texto...
  17. Em primeiro lugar, recomendo que aprenda a modularizar seu código; em segundo, a comentá-lo e; em terceiro, a evitar fazer as mesmas coisas mais de uma vez... Eis um esqueleto do que seu código poderia ser (incompleto - porque não vou fazer seu dever de casa!): #include <stdio.h> /* Por conveniência, essa enumeração será usada para selecionar a operação */ enum operacao_e { CONVERT = 1, SUM, SUBTRACT, DIVIDE, MULTIPLY }; static int get_choice( char ** ); static void convert( void ); static void sum( void ); static void subtract( void ); static void divide( void ); static void multiply( void ); int main( void ) { static char *operacoes[] = { "Conversão", "Soma", "Divisao", "Subtração", "Multiplicação", NULL }; int operacao; puts( "CALCULADORA DE BASES NUMERICAS\n" "Digite Ctrl+C para sair do programa." ); while ( 1 ) { operacao = get_choice( operacoes ); switch ( operacao ) { case CONVERT: convert(); break; case SUM: sum(); break; case SUBTRACT: subtract(); break; case DIVIDE: divide(); break; case MULTIPLY: multiply(); break; default: fputs( "***ERRO***: Opção inválida.\n", stderr ); } } /* Nunca vai chegar aqui! */ } /* Mostra a lista de opções e permite a seleção de uma. Retorna 0 se houve algum erro. */ int get_choice( char **choicesp ) { int numchoices, i; numchoices = 0; puts( "" ); while ( *choicesp ) printf( "%d - %s\n", 1 + numchoices++, *choicesp++ ); fputs( "Resposta: ", stdout ); fflush( stdout ); i = 0; if ( scanf( "%d", &i ) != 1 ) return 0; if ( i < 1 || i > numchoices ) return 0; return i; } /* Aqui você deverá colocar código similar, em cada uma das funções */ void convert( void ) {} void sum( void ) {} void subtract( void ) {} void divide( void ) {} void multiply( void ) {}
  18. Pelo que vi, isso garante que a página onde a sessão de código (.text e outras semelhantes) seja carregadas será isolada das páginas de dados e pilha. Isso permite o uso do bit NX na tabela de página, protegendo o código e evitando que você crie um "shell code"... Quanto ao preenchimento, provavelmente isso não tem nenhum problema, já que, mesmo que a imagem binária seja pequena, o mínimo que será alocado na memória será algumas páginas de 4 KiB (pelo menos algumas para código: SEU código + códigos de inicialização da libc como todos aqueles ?crt*.o; algumas para dados (acho que a libc aloca, pelo menos 128 KiB; e pelo menos uma para a pilha)...
  19. Ninguém vai descer dos céus para salvar o seu código se você mesmo não verificar os possíveis erros... Quase todas as funções da biblioteca padrão de C te informam condições de erro. Por exemplo, scanf(). scanf() serve para "varrer" (scan) um stream, obedecendo um "formato" (o "f" no final do nome da função) realizando conversões de itens selecionados para variáveis apontadas na lista de argumentos... A função sempre retorna a quantidade de conversões que conseguiu fazer ou -1, em caso de erros "catastróficos", digamos assim (ela retornará 0 se não conseguiu converter nada)... Outra função muito usada é printf(), que sempre retorna a quantidade de caracteres "impressos" (aliás: printf vem de "print formated", porque usa a string de formatação antes dos argumentos contendo os valores), ou -1 em caso de erro "catastrófico"... A exceção é snprintf(), que sempre retorna a quantidade de caracteres que "poderiam" ser impressos. Por exemplo: char s[5]; int len; // O tamanho, no segundo argumento, INCLUI o '\0' final. // O retorno EXCLUI o '\0'... Aqui, len será 9. len = snprintf( s, sizeof s, "Frederico" ); printf( "%s (would print %d chars).\n", s, len ); Exceto pelo caso do scanf(), geralmente não prestamos muita atenção ao printf() e assumimos que ele sempre imprimirá o que mandamos. É uma suposição bastante segura, mas assumir isso com coisas como arquivos e sockets não é. Discos podem falhar, cabos de rede podem ser partidos, ruidos podem aparecer na linha e conexões podem cair... Assim, a verificação das confições de erro das funções que lidem com arquivos e sockets é impressindível. Além das funções retornarem valores especiais para indicarem erros, é prudente verificar o conteúdo da "variável" global errno (e incluir o header file errno.h). Por exemplo, a função send() pode retornar -1 em caso de erro e errno ser ajustado para ECONNRESET (Connection reset by peer) ou outro erro qualquer... Da mesma forma, connect() pode retorna -1 e errno ser setado para ECONNREFUSED ou ENETUNREACH... Outras funções como socket(), bind(), accept(), recv(), ... também têm suas condições de erro e devem ser respeitadas. No caso de arquivos a mesma coisa ocorre.... open(), read() e write() também retornam -1 e setam errno de acordo... Uma observação sobre o uso de errno: Em caso de sucesso as funções da libc não a resetam (ajustando-a para zero). É seu trabalho fazer isso, por exemplo: errno = 0; if ( send( fd, buffer, bufsize, 0 ) == -1 ) { // trata erro aqui. } // se não houve erro, errno será zero aqui! Outro detalhe de errno: Ele é "thread safe"... Cada thread tem o seu próprio errno, independente. []s Fred
  20. $ strip -s program Ou "linkar" com a opção -s.
  21. Não sei se já postei isso por aqui, mas aqui vai uma dica rápida para aquelas funções que precisamos criara para comparações, usadas em funções como qsort() e bsearch(). Ambas têm o protótipo: void qsort( void *base, size_t nmemb, size_t size, int (*comp)(const void *, const void *) ); void *bsearch( const void *key, const void *base, size_t nmemb, size_t size, int (*comp)(const void *, const void *) ); Esse ponteiro para a função de comparação espera uma função que retorne <0, 0 ou >0. No caso do qsort() poderemos ficar tentados a escrever uma rotina assim, para ordenação ascendente de um buffer contendo int's: int mycomp( const void *a, const void *b ) { if ( *(const int *)a < *(const int *)b ) return -1; else if ( *(const int *)a > *(const int *)b ) return 1; else return 0; } Ou algo similar... É uma boa rotina, mas essa é melhor: int mycomp( const void *a, const void *b ) { return ( *(const int *)a > *(const int *)b ) - ( *(const int *)a < *(const int *)b ); } Repare que todo resultado de expressão contendo comparações é sempre 0 ou 1 ( e do tipo int )... Ao comparar se (a > b) obtemos 0 ou 1. Mesma coisa para a comparação (a < b)... E, não parece, mas esse último código é um cadinho mais rápido (e menor) que o anterior... Se quiser fazer a rotina mais legível: int mycomp( const void *a, const void *b ) { const int *a_ = a, *b_ = b; return ( *a_ > *b_ ) - ( *a_ < *b_ ); }
  22. Chegando atrasado aqui, mas ai vai um complemento à resposta do @Felipe.Silva: O GCC, sem otimizações ativadas, gera código bem LERDO e bagunçado. Recomendo usar a opção -O2 também: $ gcc -O2 -masm=intel -S prog.c # Ele vai gerar o prog.s Ainda... sem dizer nada, o GCC tende a colocar código para "proteção" da pilha. Para criar uma listagem em asm mais pura prefiro adicionar a opção -fno-stack-protector: $ gcc -O2 -masm=intel -S -fno-stack-protector prog.c # Ele vai gerar o prog.s Eis um exemplo simples: #include <stddef.h> #include <stdint.h> uint64_t sum( uint32_t *p, size_t size ) { uint64_t s; s = 0; while ( size-- ) s += *p++; return s; } Compilado sem otimizações (retiradas as diretivas): sum: push rbp mov rbp, rsp mov QWORD PTR -24[rbp], rdi mov QWORD PTR -32[rbp], rsi mov QWORD PTR -8[rbp], 0 jmp .L2 .L3: mov rax, QWORD PTR -24[rbp] lea rdx, 4[rax] mov QWORD PTR -24[rbp], rdx mov eax, DWORD PTR [rax] mov eax, eax add QWORD PTR -8[rbp], rax .L2: mov rax, QWORD PTR -32[rbp] lea rdx, -1[rax] mov QWORD PTR -32[rbp], rdx test rax, rax jne .L3 mov rax, QWORD PTR -8[rbp] pop rbp ret Compilado com otimização -O2: sum: test rsi, rsi je .L4 xor edx, edx xor eax, eax .L3: mov ecx, DWORD PTR [rdi+rdx*4] add rdx, 1 add rax, rcx cmp rdx, rsi jne .L3 rep ret .L4: xor eax, eax ret Note que a segunda compilação não usa o stack frame e usa os registradores ao máximo (com apenas 1 referência à memória)... Acho a segunda mais fácil de entender que a primeira.
  23. Eu tava ppensando em algo assim: Só que mais "simples" (e bataro)... Tem uns modelos da Tectronix de $800.
  24. /* boo.c Compile com: gcc -o boo boo.c */ #include <stdio.h> int main( void ) { static char tbl[] = { 28, ' ', 4, 'o', 12, '$', 4, 'o', 1, '\n', 24, ' ', 2, 'o', 24, '$', 1, 'o', 1, '\n', 21, ' ', 2, 'o', 30, '$', 1, 'o', 9, ' ', 1, 'o', 1, '$', 3, ' ', 2, '$', 1, ' ', 1, 'o', 1, '$', 1, '\n', 5, ' ', 1, 'o', 1, ' ', 1, '$', 1, ' ', 2, 'o', 8, ' ', 1, 'o', 36, '$', 1, 'o', 7, ' ', 2, '$', 1, ' ', 2, '$', 1, ' ', 2, '$', 1, 'o', 1, '$', 1, '\n', 2, ' ', 2, 'o', 1, ' ', 1, '$', 1, ' ', 1, '$', 1, ' ', 1, '"', 1, '$', 6, ' ', 1, 'o', 9, '$', 4, ' ', 13, '$', 4, ' ', 9, '$', 1, 'o', 7, ' ', 3, '$', 1, 'o', 2, '$', 1, 'o', 1, '$', 1, '\n', 2, ' ', 1, '"', 6, '$', 1, 'o', 1, '$', 5, ' ', 1, 'o', 9, '$', 6, ' ', 11, '$', 6, ' ', 10, '$', 1, 'o', 4, ' ', 8, '$', 1, '\n', 4, ' ', 7, '$', 4, ' ', 11, '$', 6, ' ', 11, '$', 6, ' ', 23, '$', 1, '\n', 4, ' ', 23, '$', 4, ' ', 13, '$', 4, ' ', 14, '$', 2, ' ', 3, '"', 3, '$', 1, '\n', 5, ' ', 1, '"', 3, '$', 4, '"', 49, '$', 5, ' ', 1, '"', 3, '$', 1, '\n', 6, ' ', 3, '$', 3, ' ', 1, 'o', 50, '$', 5, ' ', 1, '"', 3, '$', 1, 'o', 1, '\n', 5, ' ', 1, 'o', 2, '$', 1, '"', 3, ' ', 51, '$', 7, ' ', 3, '$', 1, 'o', 1, '\n', 5, ' ', 3, '$', 4, ' ', 45, '$', 1, '"', 1, ' ', 1, '"', 6, '$', 5, 'o', 4, '$', 1, 'o', 1, '\n', 4, ' ', 1, 'o', 3, '$', 4, 'o', 5, '$', 2, ' ', 37, '$', 3, ' ', 1, 'o', 17, '$', 1, '\n', 4, ' ', 8, '$', 1, '"', 4, '$', 3, ' ', 34, '$', 5, ' ', 4, '$', 8, '"', 1, '\n', 3, ' ', 4, '"', 7, ' ', 4, '$', 4, ' ', 1, '"', 28, '$', 1, '"', 6, ' ', 1, 'o', 3, '$', 1, '\n', 14, ' ', 1, '"', 3, '$', 1, 'o', 5, ' ', 3, '"', 18, '$', 1, '"', 2, '$', 1, '"', 9, ' ', 3, '$', 1, '\n', 16, ' ', 3, '$', 1, 'o', 10, ' ', 1, '"', 2, '$', 2, '"', 6, '$', 4, '"', 11, ' ', 1, 'o', 3, '$', 1, '\n', 17, ' ', 4, '$', 1, 'o', 32, ' ', 1, 'o', 3, '$', 1, '"', 1, '\n', 18, ' ', 1, '"', 4, '$', 1, 'o', 6, ' ', 1, 'o', 6, '$', 1, 'o', 1, '"', 4, '$', 1, 'o', 8, ' ', 1, 'o', 4, '$', 1, '\n', 20, ' ', 1, '"', 5, '$', 2, 'o', 5, ' ', 2, '"', 4, '$', 1, 'o', 5, '$', 1, 'o', 3, ' ', 1, 'o', 4, '$', 2, '"', 1, '\n', 23, ' ', 2, '"', 5, '$', 4, 'o', 2, ' ', 1, '"', 3, '$', 1, 'o', 9, '$', 3, '"', 1, '\n', 26, ' ', 2, '"', 7, '$', 2, 'o', 1, ' ', 10, '$', 1, '\n', 34, ' ', 4, '"', 11, '$', 1, '\n', 38, ' ', 12, '$', 1, '\n', 39, ' ', 10, '$', 1, '"', 1, '\n', 40, ' ', 1, '"', 3, '$', 4, '"', 1, '\n', 0 }; char *p = tbl; char c, len; while ( *p ) { len = *p++; c = *p++; while ( len-- ) { #ifdef __WIN32 if ( c == '\n' ) { fputs( "\r\n", stdout ); continue; } #endif putchar( c ); } } } Antes que perguntem... roubei isso de um "easter egg" do VIM.
×
×
  • Create New...