Por que os compiladores são tão confiáveis?

58

Usamos compiladores diariamente, como se a correção deles fosse certa, mas os compiladores também são programas e podem conter bugs. Eu sempre me perguntei sobre essa robustez infalível. Você já encontrou um bug no próprio compilador? O que foi e como você percebeu que o problema estava no próprio compilador?

... e como fazer eles tornam os compiladores tão confiáveis?

    
por EpsilonVector 25.02.2011 / 18:24
fonte

19 respostas

94

Eles são testados exaustivamente por milhares ou milhões de desenvolvedores ao longo do tempo.

Além disso, o problema a ser resolvido é bem definido (por uma especificação técnica muito detalhada). E a natureza da tarefa se presta facilmente a testes de unidade / sistema. Ou seja é basicamente traduzir entrada textual em um formato muito específico para saída em outro tipo de formato bem definido (algum tipo de bytecode ou código de máquina). Por isso, é fácil criar e verificar casos de teste.

Além disso, geralmente os bugs são fáceis de reproduzir: além da informação exata sobre a plataforma e a versão do compilador, geralmente tudo o que você precisa é de um código de entrada. Sem mencionar que os usuários do compilador (sendo os próprios desenvolvedores) tendem a fornecer relatórios de bugs muito mais precisos e detalhados do que qualquer usuário comum de computador: -)

    
por 25.02.2011 / 18:28
fonte
57

Além de todas as ótimas respostas até o momento:

Você tem um "viés de observador". Você não observa bugs e, portanto, assume que não há nenhum.

Eu costumava pensar como você. Então eu comecei a escrever compiladores profissionalmente, e deixe-me dizer, há muitos bugs lá!

Você não vê os bugs porque você escreve um código que é como 99,999% de todo o resto do código que as pessoas escrevem. Você provavelmente escreve código perfeitamente normal, direto e claramente correto que chama métodos e executa loops e não faz nada extravagante ou estranho, porque você é um desenvolvedor normal resolvendo problemas normais de negócios.

Você não vê nenhum erro de compilador porque os erros do compilador não estão nos cenários de código normais simples e fáceis de analisar; os bugs estão na análise de código esquisito que você não escreve.

Eu, por outro lado, tenho o viés de observador oposto. Eu vejo código maluco o dia todo todo dia, então para mim os compiladores parecem estar cheios de bugs.

Se você sentou com a especificação de idioma de qualquer idioma, e tomou qualquer implementação de compilador para aquele idioma, e realmente tentou determinar se o compilador implementou ou não a especificação, concentrando-se em casos de canto obscuros, logo você d estar encontrando erros de compilador com bastante frequência. Deixe-me dar um exemplo, aqui está um bug do compilador C # que encontrei literalmente cinco minutos atrás.

static void N(ref int x){}
...
N(ref 123);

O compilador dá três erros.

  • Um argumento ref ou out deve ser uma variável atribuível.
  • A melhor correspondência para N (ref int x) contém argumentos inválidos.
  • Falta "ref" no argumento 1.

Obviamente, a primeira mensagem de erro está correta e a terceira é um erro. O algoritmo de geração de erro está tentando descobrir por que o primeiro argumento era inválido, olha para ele, vê que é uma constante e não retorna ao código-fonte para verificar se ele foi marcado como "ref"; em vez disso, assume que ninguém seria tolo o suficiente para marcar uma constante como ref e decide que o juiz deve estar faltando.

Não está claro qual é a terceira mensagem de erro correta, mas não é isso. Na verdade, não está claro se a mensagem de erro segundo está correta. A resolução de sobrecarga deve falhar ou o "ref 123" deve ser tratado como um argumento ref do tipo correto? Agora terei que pensar um pouco e conversar com a equipe de triagem para que possamos determinar qual é o comportamento correto.

Você nunca viu esse bug porque provavelmente nunca faria algo tão tolo a ponto de tentar passar 123 por ref. E se você fez, você provavelmente nem perceberia que a terceira mensagem de erro é sem sentido, uma vez que a primeira é correta e suficiente para diagnosticar o problema. Mas eu tento fazer coisas assim, porque estou tentando quebrar o compilador. Se você tentasse, também veria os erros.

    
por 01.03.2011 / 00:26
fonte
49

Você está brincando comigo? Compiladores também têm erros, cargas realmente.

O GCC é provavelmente o mais célebre dos compiladores de código aberto no planeta e dá uma olhada no seu banco de dados de bugs: link

Entre o GCC 3.2 e o GCC 3.2.3, veja quantos bugs foram corrigidos: link

Quanto a outros como o Visual C ++, nem quero começar.

Como você faz compiladores confiáveis? Bem, para começar, eles têm cargas e cargas de testes de unidade. E todo o planeta os usa, então não há escassez de testadores.

Falando sério, os desenvolvedores de compiladores que eu gosto de acreditar são programadores superiores e, embora não sejam infalíveis, eles fazem um grande esforço.

    
por 25.02.2011 / 17:52
fonte
19

Eu encontrei dois ou três no meu dia. A única maneira real de detectar um é olhar o código de montagem.

Embora os compiladores sejam altamente confiáveis por motivos que outros pôsteres apontaram, acho que a confiabilidade do compilador é, com frequência, uma avaliação auto-realizável. Os programadores tendem a ver o compilador como o padrão. Quando algo dá errado, você assume que é sua culpa (porque 99,999% do tempo é) e altera seu código para contornar o problema do compilador, e não o contrário. Por exemplo, o código falhando sob uma configuração de otimização alta é definitivamente um erro do compilador, mas a maioria das pessoas apenas define um pouco mais baixo e segue em frente sem relatar o bug.

    
por 25.02.2011 / 17:41
fonte
13

Os compiladores têm várias propriedades que levam à sua exatidão:

  • O domínio é muito conhecido e pesquisado. O problema é bem definido e as soluções oferecidas são bem definidas.
  • Testes automatizados são suficientes para provar que os compiladores funcionam corretamente
  • Os compiladores têm testes de unidade e automatização muito extensos, normalmente públicos, que se acumularam ao longo do tempo para cobrir mais espaço de erros do que na maioria dos outros programas
  • Os compiladores têm um grande número de olhos observando seus resultados
por 25.02.2011 / 17:26
fonte
12

We use compilers on a daily basis

...and how do they make compilers so reliable?

Eles não. Nós fazemos. Porque todo mundo usa o tempo todo, os erros são encontrados rapidamente.

É um jogo de números. Como os compiladores são usados de forma tão difundida, é altamente provável que qualquer bug será acionado por alguém, mas como há um número tão grande de usuários, é altamente improvável que alguém será você especificamente.

Então, depende do seu ponto de vista: em todos os usuários, os compiladores estão com bugs. Mas é muito provável que alguém tenha compilado um código semelhante antes de você, então se o deles / delas era um bug, ele teria atingido eles, não você, então do seu indivíduo ponto de vista, parece que o bug nunca esteve lá.

Claro, além disso, você pode adicionar todas as outras respostas aqui: os compiladores são bem pesquisados, bem compreendidos. Existe esse mito de que eles são difíceis de escrever, o que significa que apenas programadores muito inteligentes, muito bons, na verdade tentam escrever um, e são extremamente cuidadosos quando o fazem. Eles geralmente são fáceis de testar e fáceis de testar ou teste de estresse. Usuários de compiladores tendem a ser programadores especialistas, levando a relatórios de bugs de alta qualidade. E o contrário: os criadores de compiladores tendem a ser usuários de seu próprio compilador.

    
por 25.02.2011 / 18:29
fonte
11

Além de todas as respostas, gostaria de adicionar:

Eu acredito muitas vezes, os vendedores estão comendo sua própria comida de cachorro. Ou seja, eles estão escrevendo os compiladores em si mesmos.

    
por 25.02.2011 / 17:19
fonte
7

Eu me deparo com erros de compilador com frequência.

Você pode encontrá-los nos cantos mais escuros, onde há menos testadores. Por exemplo, para encontrar erros no GCC, você deve tentar:

  • Construa um compilador cruzado. Você encontrará literalmente dezenas de bugs nos scripts de configuração e construção do GCC. Alguns resultam em falhas de compilação durante a compilação do GCC e outros resultam em falha do compilador cruzado para construir executáveis de trabalho.
  • Crie uma versão do Itanium do GCC usando o bootstrap de perfil. Nas últimas vezes eu tentei isso no GCC 4.4 e 4.5 ele falhou em produzir um handler de exceção de C ++ funcional. A compilação não otimizada funcionou bem. Ninguém parecia interessado em consertar o bug que relatei e desisti de consertá-lo depois de tentar descobrir o que estava quebrando nas especificações de memória do GCC.
  • Tente criar seu próprio GCJ de trabalho com as últimas informações sem seguir um script de criação de distro. Eu te desafio.
por 25.02.2011 / 22:33
fonte
5

Várias razões:

  • Escritores de compiladores " comem sua própria comida de cachorro "
  • Os compiladores são baseados em princípios bem compreendidos de CS.
  • Os compiladores são criados com uma especificação clara .
  • Os compiladores são testados .
  • Os compiladores não são nem sempre muito confiáveis .
por 25.02.2011 / 18:10
fonte
4

Eles geralmente são muito bons em -O0. De fato, se suspeitarmos de um erro do compilador, comparamos -O0 versus qualquer nível que estamos tentando usar. Níveis de otimização mais altos apresentam maior risco. Alguns são até deliberadamente, e rotulados como tal na documentação. Eu encontrei muitos (pelo menos uma centena durante o meu tempo), mas eles estão se tornando muito mais raros recentemente. No entanto, em busca de bons números de referência (ou outras referências importantes para o marketing), a tentação de ultrapassar os limites é grande. Tivemos problemas há alguns anos em que um fornecedor (para não ter um nome) decidiu tornar a violação do padrão de parênteses - em vez de uma opção de compilação especial claramente identificada.

Pode ser difícil diagnosticar um erro do compilador versus uma referência de memória dispersa, uma recompilação com opções diferentes pode simplesmente embaralhar o posicionamento relativo de objetos de dados dentro da memória, assim você não sabe se é o Heisenbug do seu código fonte, ou um compilador de bugs. Também muitas otimizações fazem mudanças legítimas na ordem das operações, ou até mesmo simplificações algébricas para sua álgebra, e estas terão propriedades diferentes com relação ao arredondamento de ponto flutuante e sob / estouro. É difícil separar esses efeitos de erros REAIS. A computação de ponto flutuante do núcleo duro é difícil por este motivo, porque erros e sensibilidade numérica não costumam ser facilmente desenredados.

    
por 25.02.2011 / 18:14
fonte
4

Os bugs do compilador não são tão raros. O caso mais comum é que um compilador relate um erro no código que deve ser aceito ou que um compilador aceite um código que deveria ter sido rejeitado.

    
por 25.02.2011 / 18:45
fonte
3

Sim, encontrei um bug no compilador do ASP.NET ontem:

Quando você usa modelos strongmente tipados em exibições, há um limite para quantos parâmetros os modelos podem conter. Obviamente, ele não pode levar mais de 4 parâmetros de modelo, de modo que ambos os exemplos abaixo o tornam demais para o compilador:

ViewUserControl<System.Tuple<type1, type2, type3, type4, type5>>

Não compilaria como está, mas será se type5 for removido.

ViewUserControl<System.Tuple<MyModel, System.Func<type1, type2, type3, type4>>>

Seria compilar se type4 fosse removido.

Note que System.Tuple tem muitas sobrecargas e pode levar até 16 parâmetros (é uma loucura, eu sei).

    
por 25.02.2011 / 17:13
fonte
3

Have you ever encountered a bug in the compiler itself? What was it and how did you realize the problem was in the compiler itself?

Sim!

Os dois mais memoráveis foram os dois primeiros que encontrei. Ambos estavam no compilador Lightspeed C para Macs 680x0 em 1985-7.

O primeiro foi onde, em algumas circunstâncias, o operador de pós-incremento de inteiro não fez nada - em outras palavras, em uma parte específica do código, "i ++" simplesmente não fez nada para "i". Eu estava puxando meu cabelo até que eu olhei para uma desmontagem. Então eu fiz o incremento de uma maneira diferente e enviei um relatório de bug.

O segundo foi um pouco mais complicado, e foi realmente um "recurso" mal considerado que deu errado. Os primeiros Macs tinham um sistema complicado para fazer operações de disco de baixo nível. Por alguma razão eu nunca entendi - provavelmente tendo a ver com a criação de executáveis menores - ao invés do compilador apenas gerar as instruções de operação do disco no local no código objeto, o compilador Lightspeed chamaria uma função interna, que em tempo de execução gerava a operação do disco instruções na pilha e saltou para lá.

Isso funcionou muito bem em 68000 CPUs, mas quando você executava o mesmo código em um CPU 68020, muitas vezes fazia coisas estranhas. Descobriu-se que um novo recurso do 68020 era um cache de instruções de 256 bytes de instrução primitiva. Sendo nos primeiros dias com caches de CPU, não havia noção de que o cache estava "sujo" e precisava ser recarregado; Eu acho que os projetistas de CPU da Motorola não pensaram em código auto-modificador. Portanto, se você fez duas operações de disco próximas o suficiente em sua sequência de execução e o tempo de execução do Lightspeed criou as instruções reais no mesmo local da pilha, a CPU erroneamente acha que teve um cache de instruções acionado e executou a primeira operação de disco duas vezes.

Mais uma vez, percebemos que isso foi um pouco difícil com um desassemblador, e muito passo-a-passo em um depurador de baixo nível. Minha solução alternativa era prefixar cada operação de disco com uma chamada para uma função que fazia 256 instruções "NOP", que inundavam (e, portanto, limpavam) o cache de instruções.

Ao longo dos 25 anos desde então, tenho visto cada vez menos erros no compilador ao longo do tempo. Eu acho que há algumas razões para isso:

  • Há um conjunto cada vez maior de testes de validação para compiladores.
  • Os compiladores modernos normalmente são divididos em duas ou mais partes, uma das quais gera código independente de plataforma (por exemplo, LLVM visando o que você pode considerar uma CPU imaginária) e outra que traduz isso em instruções para o hardware de destino real. Em compiladores multiplataforma, a primeira parte é usada em todos os lugares, por isso recebe toneladas de testes do mundo real.
por 25.02.2011 / 22:16
fonte
3

Encontrei um erro flagrante no Turbo Pascal 5,5 anos atrás. Um erro presente na versão anterior (5.0) nem na próxima (6.0) do compilador. E um que deveria ter sido fácil de testar, já que não era um cornercase (apenas uma chamada que não é comumente usada).

Em geral, certamente os construtores de compiladores comerciais (em vez de projetos de hobby) terão procedimentos de QA e testes muito extensos em vigor. Eles sabem que seus compiladores são seus projetos emblemáticos e que as falhas vão parecer muito ruins para eles, pior do que seriam em outras empresas que fabricam a maioria dos outros produtos. Desenvolvedores de software são um grupo implacável, nossos fornecedores de ferramentas nos decepcionam, é provável que procuremos alternativas em vez de esperar por uma correção do fornecedor, e é muito provável que comunicamos esse fato a nossos colegas que podem muito bem seguir nossa exemplo. Em muitas outras indústrias, esse não é o caso, então a perda potencial para um fabricante de compiladores como resultado de um bug sério é muito maior do que a de um fabricante de software de edição de vídeo.

    
por 01.03.2011 / 08:26
fonte
2

Quando o comportamento do seu software é diferente quando compilado com -O0 e com -O2, então você encontrou um erro no compilador.

Quando o comportamento do seu software é diferente do esperado, é provável que o bug esteja no seu código.

    
por 25.02.2011 / 17:40
fonte
2

Erros de compilador acontecem, mas você tende a encontrá-los em cantos estranhos ...

Houve um bug estranho no compilador VAX VMS C da Digital Equipment Corporation nos anos 90

(Eu estava usando uma cebola no meu cinto, como era a moda na época)

Um ponto-e-vírgula estranho em qualquer lugar antes de um loop for seria compilado como o corpo do loop for.

f(){...}
;
g(){...}

void test(){
  int i;
  for ( i=0; i < 10; i++){
     puts("hello");
  }
}

No compilador em questão, o loop é executado apenas uma vez.

f(){...}
g(){...}

void test(){
  int i;
  for ( i=0; i < 10; i++) ;  /* empty statement for fun */

  {
     puts("hello");
  }
}

Isso me custou muito tempo.

A versão mais antiga do compilador PIC C que nós (costumava) infligir em estudantes com experiência de trabalho não poderia gerar código que usasse corretamente a interrupção de alta prioridade. Você teve que esperar 2-3 anos e atualizar.

O compilador MSVC 6 tinha um bug bacana no linker, seria falha de segmentação e morreria de tempos em tempos sem nenhum motivo. Uma compilação limpa geralmente corrigia (mas suspiro nem sempre).

    
por 28.02.2011 / 23:31
fonte
2

Em alguns domínios, como software de aviônica, existem requisitos de certificação extremamente altos, tanto no código e hardware, quanto no compilador. Sobre esta última parte, há um projeto que visa criar um compilador C formalmente verificado, chamado Compcert . Em teoria, esse tipo de compilador é tão confiável quanto eles são.

    
por 23.10.2018 / 03:27
fonte
1

Eu vi vários bugs do compilador, relatei alguns deles (especificamente, em F #).

Dito isso, acho que os erros do compilador são raros porque as pessoas que escrevem compiladores geralmente se sentem muito à vontade com os conceitos rigorosos da ciência da computação que os tornam realmente conscientes sobre as implicações matemáticas do código.

A maioria deles está presumivelmente muito familiarizada com coisas como cálculo lambda, verificação formal, semântica denotacional etc. - coisas que um programador médio como eu mal consegue compreender.

Além disso, geralmente há um mapeamento bastante direto da entrada para a saída em compiladores, portanto depurar uma linguagem de programação é provavelmente muito mais fácil do que depurar, digamos, um mecanismo de blog.

    
por 25.02.2011 / 22:32
fonte
1

Eu encontrei um bug no compilador C # não muito tempo atrás, você pode ver como Eric Lippert (que está no time de design do C #) descobriu qual era o bug aqui .

Além das respostas já fornecidas, gostaria de adicionar mais algumas coisas. Designers de compiladores são freqüentemente programadores extremamente bons. Compiladores são muito importantes: a maioria das programações é feita usando compiladores, então é imperativo que o compilador seja de alta qualidade. Portanto, é do interesse de empresas que fazem compiladores colocar suas melhores pessoas nela (ou pelo menos, muito boas: as melhores podem não gostar do design do compilador). A Microsoft gostaria muito que seus compiladores C e C ++ funcionassem corretamente, ou o resto da empresa não poderia fazer o trabalho deles.

Além disso, se você está construindo um compilador realmente complexo, você não pode simplesmente cortar tudo juntos. A lógica por trás dos compiladores é altamente complexa e fácil de formalizar. Portanto, esses programas geralmente serão construídos de uma maneira muito "robusta" e genérica, o que tende a resultar em menos bugs.

    
por 23.05.2017 / 14:40
fonte