Código aberto: humor de código, truques de código, código NÃO

Old Clib vs New Clang


Procurando em um software de código aberto diverso, periodicamente encontro todo tipo de coisa interessante: às vezes é apenas um comentário engraçado, às vezes é algo espirituoso em um sentido mais amplo. Coleções semelhantes aparecem periodicamente na "Internet global" e no Habré - por exemplo, há uma pergunta bem conhecida no StackOverflow sobre comentários no código e uma seleção de nomes engraçados de entidades legais e topônimos foi publicada recentemente aqui. Vou tentar estruturar e definir o que gradualmente acumulei. Sob o corte, citações do QEMU, o kernel do Linux e muito mais estão esperando por você.


Kernel Linux


Eu acho que para muitos não é segredo que as cartas da lista de discussão do kernel do Linux periodicamente divergem entre aspas. Então, vamos dar uma olhada melhor no código. E imediatamente, o sistema de montagem do kernel nos surpreende: como você sabe, os projetos criados pela Autoconf têm um Makefile com dois objetivos padrão de limpeza: clean e distclean . Naturalmente, o kernel não é construído usando o Autoconf, e o que vale apenas o menuconfig , então existem mais objetivos aqui: clean , distclean e mrproper - sim, sim, Mr.Proper, um limpador de núcleo duas vezes mais rápido .


Falando no sistema de configuração: uma vez fiquei surpreso quando o encontrei, além de comandos claros como allnoconfig , allyesconfig (suspeito que algo muito de depuração possa ser compilado, então agora não correria o risco de fazer o download em hardware real .. .) e allmodconfig para o destino misterioso allrandconfig . "Eles estão zombando", pensei, depois contei a meu amigo sobre essa observação, à qual ele respondeu que provavelmente era um comando completamente significativo, mas não para montagem real, mas para testar a correção do arranjo de dependências entre as opções - como eu disse. agora, uma espécie de parâmetros de configuração imprecisos.


No entanto, há vida no âmago do sistema de montagem: a documentação às vezes não é apenas técnica, mas também de um tipo, valor artístico. Suponha que você queira alertar os usuários do modo de suspensão sobre sua fragilidade e o risco de perda de dados se determinadas regras não forem seguidas. Eu escrevia tristemente, dizendo ATENÇÃO: <substitua algumas das linhas mais chatas> . Mas o desenvolvedor que escreveu isso fez algo diferente:


 Some warnings, first. * BIG FAT WARNING ********************************************************* * * If you touch anything on disk between suspend and resume... * ...kiss your data goodbye. * * If you do resume from initrd after your filesystems are mounted... * ...bye bye root partition. * [this is actually same case as above] * * ... 

Pequenos truques


Não é de surpreender que nem todos os códigos possam ser compilados com otimizações: quando tentei forçá-los a serem ativados para todos os arquivos de objetos, naturalmente encontrei alguma fonte de entropia ou algo semelhante que #error se a otimização estivesse ativada. Bem, a criptografia é assim. Mas você deseja um código que não será montado se você desativar todas as otimizações, inlining etc.? Como isso é possível? E esta é uma afirmação estática:


 /* SPDX-License-Identifier: GPL-2.0 */ // ... /* * This function doesn't exist, so you'll get a linker error if * something tries to do an invalidly-sized xchg(). */ extern void __xchg_called_with_bad_pointer(void); static inline unsigned long __xchg(unsigned long x, volatile void *ptr, int size) { unsigned long ret, flags; switch (size) { case 1: #ifdef __xchg_u8 return __xchg_u8(x, ptr); #else local_irq_save(flags); ret = *(volatile u8 *)ptr; *(volatile u8 *)ptr = x; local_irq_restore(flags); return ret; #endif /* __xchg_u8 */ // ... default: __xchg_called_with_bad_pointer(); return x; } } 

Aparentemente, supõe-se que, para qualquer uso com argumento constante, essa função se expanda em apenas um ramo do switch e, quando usado com um argumento válido , esse ramo não será o default:
De forma não otimizada, essa função causará um erro de link quase por design ...


Você sabe


  • ... que o kernel possui um compilador JIT de bytecode no modo de usuário? Essa tecnologia é chamada eBPF e é usada para roteamento, rastreamento e muito mais. A propósito, se você não tem medo de ferramentas "nucleares" experimentais, observe o pacote bpftools.
  • ... que o kernel pode demorar cerca de cinco minutos em tempo de processador? Existe uma chamada do sistema sendfile que copia bytes de um descritor de arquivo para outro. Se você informar o mesmo descritor e definir o deslocamento correto no arquivo, ele retrocederá os mesmos dados até copiar 2 GB.
  • ... que existe uma variante do trabalho de hibernação realizado pelo processo do usuário - não ficarei surpreso se você também puder salvá-lo no armazenamento em rede.

QEMU


Em geral, quando li Robert Love sobre o dispositivo do kernel Linux e depois entrei nas fontes da QEMU, tive um certo senso de déjà vu. Havia listas incorporadas nas estruturas por valor (e não como no curso de programação inicial que eles aprendem - por meio de ponteiros) e um certo subsistema RCU (o que é, eu ainda não entendi completamente, mas também existe no kernel) e, provavelmente muito mais parecido.


Qual é a primeira coisa que uma pessoa legal deseja trabalhar em um projeto para conhecer? Provavelmente com o estilo de codificação. E já neste, pode-se dizer, documento cerimonial, vemos:


 1. Whitespace Of course, the most important aspect in any coding style is whitespace. Crusty old coders who have trouble spotting the glasses on their noses can tell the difference between a tab and eight spaces from a distance of approximately fifteen parsecs. Many a flamewar has been fought and lost on this issue. 

Aqui está a eterna pergunta sobre o comprimento máximo da linha:


 Lines should be 80 characters; try not to make them longer. ... Rationale: - Some people like to tile their 24" screens with a 6x4 matrix of 80x24 xterms and use vi in all of them. The best way to punish them is to let them keep doing it. ... 

(Hmm ... É duas vezes maior em cada eixo do que às vezes uso. É um Linux HD?)


Ainda há muito interessante - leia .


E novamente truques


Eles dizem que C é uma linguagem de baixo nível. Mas se é bom ser pervertido, você pode mostrar as maravilhas da geração de código em tempo de compilação sem Scala ou mesmo C ++.


Por exemplo, o arquivo softmmu_template.h está softmmu_template.h na base de códigos QEMU. Quando vi esse nome, pensei que ele deveria ser copiado na minha implementação do TCG do back-end e corrigido até que a implementação correta do TLB saísse dele. Não importa como! Veja como usá-lo corretamente :


accel / tcg / cputlb.h:


 define DATA_SIZE 1 #include "softmmu_template.h" #define DATA_SIZE 2 #include "softmmu_template.h" #define DATA_SIZE 4 #include "softmmu_template.h" #define DATA_SIZE 8 #include "softmmu_template.h" 

Como você pode ver, prestidigitação e não C ++. Mas este é um exemplo bastante simples. Que tal algo mais complicado?


Existe um arquivo assim: tcg / tcg-opc.h . Seu conteúdo é bastante misterioso e se parece com isso:


 ... DEF(mov_i32, 1, 1, 0, TCG_OPF_NOT_PRESENT) DEF(movi_i32, 1, 0, 1, TCG_OPF_NOT_PRESENT) DEF(setcond_i32, 1, 2, 1, 0) DEF(movcond_i32, 1, 4, 1, IMPL(TCG_TARGET_HAS_movcond_i32)) /* load/store */ DEF(ld8u_i32, 1, 1, 1, 0) DEF(ld8s_i32, 1, 1, 1, 0) DEF(ld16u_i32, 1, 1, 1, 0) DEF(ld16s_i32, 1, 1, 1, 0) ... 

De fato, tudo é muito simples - é usado assim:


tcg / tcg.h:


 typedef enum TCGOpcode { #define DEF(name, oargs, iargs, cargs, flags) INDEX_op_ ## name, #include "tcg-opc.h" #undef DEF NB_OPS, } TCGOpcode; 

Ou então:


tcg / tcg-common.c:


 TCGOpDef tcg_op_defs[] = { #define DEF(s, oargs, iargs, cargs, flags) \ { #s, oargs, iargs, cargs, iargs + oargs + cargs, flags }, #include "tcg-opc.h" #undef DEF }; 

É até estranho que, no decorrer de outros casos de uso, não tenha sido encontrado. E observe que, neste caso, não há scripts complicados para geração de código - apenas C, apenas hardcore.


Você sabe


  • ... que o QEMU pode funcionar não apenas no modo de emulação de um sistema completo, mas também executar um processo separado para outra arquitetura que se comunica com o kernel do host?

Java, JVM e tudo-tudo-tudo


O que eu sou sobre o Linux? Vamos falar sobre algo multiplataforma. Sobre a JVM, por exemplo. Bem, sobre o GraalVM, provavelmente, muitos desenvolvedores neste ecossistema já ouviram falar. Se você não ouviu, em poucas palavras: é épico. Então, depois de falar sobre Graal, vamos para a boa e velha JVM.


Às vezes, a JVM precisa parar todos os encadeamentos gerenciados - o estágio de coleta de lixo é tão complicado ou algo mais - mas aqui está o problema, você pode parar os encadeamentos apenas nos chamados pontos seguros. Conforme descrito aqui , uma verificação normal de uma variável global leva muito tempo, incluindo algum tipo de xamanismo com barreiras de memória. O que os desenvolvedores fizeram? Eles se limitaram a uma leitura variável.


Quase como no HQ9 +

Existe uma linguagem tão cômica - HQ9 + . Foi criado como uma "linguagem de programação educacional muito conveniente", ou seja, é muito simples executar as tarefas típicas que os alunos solicitam:


  • o comando 'H' intérprete imprime Olá, Mundo!
  • no comando 'Q' imprime o texto do próprio programa (quine)
  • no '9' ele imprime a letra de 99 garrafas de cerveja
  • por 'i' incrementa a variável i em um
  • ele não pode fazer mais nada, mas por quê?

Como a JVM atinge a meta com uma instrução? Mas é muito simples - se for necessário parar, ele remove a exibição da página de memória com essa variável - os encadeamentos caem no SIGSEGV e a JVM os estaciona e os pausa quando a "manutenção" termina. Lembro-me no StackOverflow quando perguntado em uma entrevista. Como você trava uma JVM? respondeu:


JNI. De fato, com a JNI, travar é o modo padrão de operação. Você tem que trabalhar muito para não travar.

Brincando como uma piada, e às vezes na JVM realmente é.


Bem, desde que mencionei a geração de código no Scala, e agora estamos falando sobre esse ecossistema, aqui está um fato interessante: a geração de código no Scala (aquele que possui macros) está estruturada da seguinte maneira: você escreve código no Scala usando a API compilador e compilá-lo. Então, no próximo início do compilador, você simplesmente passa o gerador de código resultante para o caminho de classe do próprio compilador, e esse, vendo uma diretiva especial, chama, passando as árvores de sintaxe recebidas durante a chamada. Em resposta, ele recebe um AST, que deve ser substituído no local da chamada.


Recursos de ideologias de licenciamento


Eu gosto da ideologia do software livre, mas também possui alguns recursos divertidos.


Uma vez, cerca de dez anos atrás, atualizei meu Debian stable e, pensando na sintaxe de um comando, digitei man <> habitualmente, que recebeu uma descrição exaustiva como “[nome do programa] é um programa com documentação distribuída sob licença GNU GFDL com seções imutáveis, que não são livres de DFSG. " Eles dizem que este programa foi escrito por alguns proprietários maus de alguma FSF ... (Agora a discussão é no google.)


E algumas bibliotecas pequenas, mas importantes, são consideradas por algumas distribuições como software não-livre, porque o autor escreveu para a licença permissiva padrão que esse programa deveria ser usado para o bem e não para o mal . Risos, risos, e eu também provavelmente teríamos medo de levar uma coisa dessas para a produção - você nunca sabe o que o autor pensa sobre o bem e o mal.


Qualquer outro


Características da construção internacional de compiladores durante a Lei Moore


Os duros desenvolvedores de LLVM limitaram o alinhamento suportado:


O alinhamento máximo é 1 << 29.

Como se costuma dizer, isso faz você rir primeiro e depois pensar : o primeiro pensamento - mas quem precisa de alinhamento aos 512 MiB. Então eu li sobre o desenvolvimento do kernel no Rust , e lá eles propõem fazer uma estrutura de "tabela de páginas" alinhada a 4096 bytes. E como você lê a Wikipedia, então geralmente:


Uma hierarquia de mapeamento completa de páginas de 4 KB para todo o espaço de 48 bits levaria um pouco mais de 512 GB de memória (cerca de 0,195% do espaço virtual de 256 TB).

Versão em formato - como armazenar?


Uma vez eu decidi descobrir por que a exportação não funciona em um programa, mas acaba funcionando ... Ou não?


Tendo iniciado os comandos de back-end manualmente, percebi que, em princípio, tudo está em ordem, apenas a versão deve ser transmitida como "2.0", mas apenas "2" sai. Antecipando uma correção trivial editando uma constante de string, encontro a função double getVersion() - mas o que é maior, menor é, mesmo que haja um ponto! No entanto, no final, tudo foi decidido não muito mais complicado do que o esperado, eu precisão de saída apenas aprimorada Encaminhou o tipo de dados e as linhas.


Sobre a diferença entre teóricos e profissionais


Na minha opinião, em algum lugar do Habré eu já vi uma tradução de um artigo sobre o que são as falhas mínimas na inicialização, mas ainda assim um programa compilado em C? int main; - existe um símbolo main e, tecnicamente , você pode transferir o controle para ele. O sirikid notou corretamente que mesmo os bytes int são supérfluos aqui. Em geral, mesmo falando de um programa de 9 bytes de tamanho, é melhor não espalhar as reivindicações de que é o menor ... É verdade que o programa cairá, mas isso é completamente consistente com as regras.


Então, sabemos como descartar o que deve funcionar, mas e quanto ao lançamento de um não lançador?


 $ ldd /bin/ls linux-vdso.so.1 (0x00007fff93ffa000) libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f0b27664000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0b2747a000) libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f0b27406000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f0b27400000) /lib64/ld-linux-x86-64.so.2 (0x00007f0b278e9000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f0b273df000) $ /lib/x86_64-linux-gnu/libc.so.6 

... e libc ele voz humana :


 GNU C Library (Ubuntu GLIBC 2.28-0ubuntu1) stable release version 2.28. Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 8.2.0. libc ABIs: UNIQUE IFUNC ABSOLUTE For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>. 

Programadores jogam golfe


Existe um site inteiro no StackExchange dedicado ao Code Golf - competições com o estilo "Resolva esse problema com uma penalidade mínima, dependendo do tamanho do código-fonte". O formato em si envolve soluções muito sofisticadas, mas às vezes elas se tornam muito sofisticadas. Portanto, em uma das perguntas , uma coleção de brechas proibidas padrão foi coletada. Eu gosto especialmente deste:


Usando o MetaGolfScript
MetaGolfScript é uma família de linguagens de programação. Por exemplo, o programa vazio no MetaGolfScript-209180605381204854470575573749277224 imprime "Olá, mundo!".

Em uma linha



Finalmente, de onde vem o título do artigo? Este é um truque parafraseado da saída do compilador emcc do Emscripten :


 $ emcc --help ... emcc: supported targets: llvm bitcode, javascript, NOT elf (autoconf likes to see elf above to enable shared object support) 

Source: https://habr.com/ru/post/pt437832/


All Articles