Por que a programação C precisa de um compilador e os scripts de shell não?

8

Escrevi um script bash e o executei sem compilá-lo primeiro. Funcionou perfeitamente. Pode funcionar com ou sem permissões, mas quando se trata de programas em C, precisamos compilar o código-fonte. Por quê?

Mongrel
fonte
9
Isso significa que C é uma linguagem compilada e Bash é uma linguagem interpretada. Nada mais nada menos.
Radon Rosborough
10
Estou votando para encerrar esta questão como fora de tópico, porque ela não está relacionada aos U&L e, mesmo que fosse, é muito ampla para podermos discuti-la sem cessar, sem fornecer uma resposta satisfatória, sem falar de uma resposta que se encaixa no ideia geral de U&L ser uma base de conhecimento para resolver problemas.
Countermode
4
@countermode Você empilha (troca | transborda) pessoas e seus votos íntimos com gatilho. A questão não é ampla: revela um entendimento faltante muito específico: a diferença entre linguagens compiladas e interpretadas. Isso leva alguns parágrafos para explicar, e outros parágrafos para apontar as (des) vantagens de cada um, além de uma nota de rodapé para resumir que C e bash tinham objetivos diferentes, portanto, eles escolheram as diferentes abordagens.
Mraceur
2
@mtraceur Desculpe, não quero ofender ninguém. A partir de votos apertados, isso é um pouco injusto. São necessários cinco votos para encerrar uma pergunta, e se alguém votar para não encerrá-la, ficarei bem com ela. Você está absolutamente certo sobre a questão, mas não tenho certeza se essas perguntas pertencem aos U&L de acordo com unix.stackexchange.com/help/on-topic .
Countermode #:
2
@traceur, se você acha que não é amplo, eu não concordo com você. Há muito a dizer sobre a diferença entre compilador / intérprete / etc. e idiomas interpretados / compilados. Existem caevats fáceis de acertar. Além disso, a questão se encaixa melhor em "Ciência da computação" ou "Programadores" SE.
MatthewRock

Respostas:

23

Isso significa que os scripts de shell não são compilados, são interpretados: o shell interpreta os scripts um comando de cada vez e descobre sempre como executar cada comando. Isso faz sentido para scripts shell, pois eles passam a maior parte do tempo executando outros programas de qualquer maneira.

Os programas C, por outro lado, geralmente são compilados: antes que possam ser executados, um compilador os converte em código de máquina por inteiro, de uma vez por todas. Havia intérpretes C no passado (como o intérprete C da HiSoft no Atari ST), mas eles eram muito incomuns. Atualmente, os compiladores C são muito rápidos; TCC é tão rápido que você pode usá-lo para criar "scripts C", com um #!/usr/bin/tcc -runshebang, assim você pode criar programas C que funcionam da mesma forma como scripts shell (na perspectiva dos usuários).

Algumas linguagens geralmente têm intérprete e compilador: BASIC é um exemplo que vem à mente.

Você também pode encontrar os chamados compiladores de scripts de shell, mas os que eu vi são apenas ofuscantes wrappers: eles ainda usam um shell para realmente interpretar o script. Como o mtraceur aponta que um compilador de script de shell adequado certamente seria possível, mas não muito interessante.

Outra maneira de pensar sobre isso é considerar que o recurso de interpretação de script de um shell é uma extensão do seu recurso de manipulação de linha de comando, o que naturalmente leva a uma abordagem interpretada. C, por outro lado, foi projetado para produzir binários independentes; isso leva a uma abordagem compilada. Os idiomas geralmente compilados tendem a gerar intérpretes também, ou pelo menos analisadores de linha de comando (conhecidos como REPLs, loops de leitura e avaliação de impressão ; um shell é um REPL).

Stephen Kitt
fonte
1
Muitos "compiladores" para linguagens de script são apenas wrappers. Lembro-me de um compilador BASIC que apenas concatenaria o interpretador executável com um código-fonte compactado.
Dmitry Grigoryev
@DmitryGrigoryev Eu posso facilmente acreditar nisso! Para o BASIC, eu estava pensando em compiladores como o Turbo Basic. Eu acho que havia alguns compiladores reais para arquivos BAT do DOS, mas posso estar enganado! Há também a abordagem de código p comum ...
Stephen Kitt
1
Observe que um compilador "true" para o shell é totalmente possível. É apenas geralmente não vale o esforço / complexidade, uma vez que uma variante ingênuo seria apenas produzir um programa que geralmente exige um monte de execve, open, close, read, write, e pipesyscalls, intercaladas com alguns getenv, setenve operações hashmap / matriz interna (para variáveis não-exportados ), etc. Bourne shell e derivados também são linguagens de programação que não beneficiam tanto de ajustes do compilador de baixo nível como o código re-ordenação, etc.
mtraceur
@StephenKitt você se importaria se eu fizesse uma nova pergunta relacionada a um comentário de sua resposta. Embora você responda explique muito. mas também levanta uma nova pergunta para mim.
Mongrel
Sim, obviamente, eu abriria uma nova pergunta. Pensei que você gostaria que eu perguntasse no chat. porque está relacionado à sua resposta.
Mongrel
6

Considere o seguinte programa:

2 Mars Bars
2 Milks
1 Bread
1 Corn Flakes

No bashcaminho, você passeia pela loja procurando bares de Marte, finalmente os localiza e depois procura leite, etc. Isso funciona porque você está executando um programa complexo chamado "Comprador experiente" que pode reconhecer um pão quando você vê um e todas as outras complexidades das compras. bashé um programa bastante complexo.

Como alternativa, você pode entregar sua lista de compras a um compilador de compras. O compilador pensa por um tempo e fornece uma nova lista. Esta lista é LONGA , mas consiste em instruções muito mais simples:

... lots of instructions on how to get to the store, get a shopping cart etc.
move west one aisle.
move north two sections.
move hand to shelf three.
grab object.
move hand to shopping cart.
release object.
... and so on and so forth.

Como você pode ver, o compilador sabe exatamente onde está tudo na loja, de modo que toda a fase "procurando coisas" não é necessária.

Este é um programa por si só e não precisa de "Comprador experiente" para executar. Tudo o que precisa é de um ser humano com "Sistema operacional humano básico".

Voltando aos programas de computador: bashé "Comprador experiente" e pode pegar um script e apenas fazê-lo sem compilar nada. O compilador de CA produz um programa autônomo que não precisa mais de ajuda para ser executado.

Tanto intérpretes quanto compiladores têm suas vantagens e desvantagens.

Stig Hemmer
fonte
3
Boa analogia ... também explica por que você pode usar a mesma lista de compras, mas não o mesmo código de máquina em uma arquitetura diferente (sic!) - todo o material está em locais diferentes etc. Você pode precisar de um "comprador experiente" diferente quem conhece o supermercado diferente.
Peter - Restabelece Monica
6

Tudo se resume à diferença técnica entre como o programa que você pode ler / gravar como humano é convertido nas instruções da máquina que seu computador entende - e as diferentes vantagens e desvantagens de cada método é a razão pela qual algumas linguagens são escritas para a necessidade de compiladores , e alguns são escritos para serem interpretados.

Primeiro, a diferença técnica

(Observação: estou simplificando muito aqui para abordar a questão. Para uma compreensão mais aprofundada, as notas técnicas na parte inferior da minha resposta elaboram / refinam algumas das simplificações aqui, e os comentários sobre essa resposta foram alguns esclarecimentos e discussões úteis também.)

Existem basicamente duas categorias gerais de linguagens de programação:

  1. Outro programa (o "compilador") lê seu programa, determina quais etapas o código solicita e, em seguida, grava um novo programa no código da máquina (a "linguagem" que o seu computador entende) que executa essas etapas.
  2. Outro programa (o "intérprete") lê o seu programa, determina quais etapas o código solicita e, em seguida, executa essas etapas . Nenhum novo programa é criado.

C está na primeira categoria (o compilador C traduz o idioma C no código de máquina do computador : o código da máquina é salvo em um arquivo e, quando você executa esse código, faz o que deseja).

bash está na segunda categoria (o intérprete do bash lê o idioma do bash e o intérprete do bash faz o que você deseja: portanto, não existe um "módulo do compilador", o intérprete faz a interpretação e a execução, enquanto um compilador lê e traduz) .

Você já deve ter percebido o que isso significa:

Com C, você executa a etapa "interpretar" uma vez e , sempre que precisar executar o programa, basta instruir o computador a executar o código da máquina - o computador pode executá-lo diretamente, sem precisar "pensar".

Com o bash, você precisa executar a etapa "interpretar" toda vez que executa o programa - seu computador está executando o interpretador bash e o interpretador bash faz um "pensamento" extra para descobrir o que é necessário fazer para cada comando, sempre .

Portanto, os programas C levam mais CPU, memória e tempo para se preparar (a etapa de compilação), mas menos tempo e trabalho para executar. programas bash levam menos CPU, memória e tempo para se preparar, mas mais tempo e trabalho para executar. Você provavelmente não percebe essas diferenças na maioria das vezes porque os computadores são muito rápidos hoje em dia, mas isso faz diferença, e essa diferença aumenta quando você precisa executar programas grandes ou complicados ou muitos pequenos programas.

Além disso, como os programas C são convertidos em código de máquina (o "idioma nativo") do computador, você não pode pegar um programa e copiá-lo em outro computador com um código de máquina diferente (por exemplo, Intel de 64 bits no Intel 32 bits, ou da Intel para ARM ou MIPS ou o que for). Você precisa gastar tempo para compilá-lo novamente para essa outra linguagem de máquina . Mas um programa bash pode ser movido para outro computador que tenha o interpretador bash instalado e ele funcionará perfeitamente.

Agora, a parte porquê da sua pergunta

Os fabricantes de C estavam escrevendo um sistema operacional e outros programas em hardware de várias décadas atrás, o que era bastante limitado pelos padrões modernos. Por várias razões, converter os programas no código de máquina do computador era a melhor maneira de atingir esse objetivo na época. Além disso, eles estavam fazendo o tipo de trabalho em que era importante que o código que escrevessem fosse executado com eficiência .

E os criadores do shell e do bash Bourne queriam o oposto: eles queriam escrever programas / comandos que pudessem ser executados imediatamente - na linha de comando, em um terminal, você quer apenas escrever uma linha, um comando e tê-lo executar. E eles queriam que os scripts que você escrevesse funcionassem em qualquer lugar em que o programa / interpretador de shell estivesse instalado.

Conclusão

Portanto, em resumo, você não precisa de um compilador para o bash, mas precisa de um para o C, porque esses idiomas são convertidos em ações reais do computador de maneira diferente e a maneira diferente de fazer isso foi escolhida porque os idiomas tinham objetivos diferentes.

Outros detalhes / notas técnicas / avançadas

  1. Você realmente pode criar um intérprete C ou um compilador bash. Não há nada que impeça que isso seja possível: são apenas esses idiomas que foram criados para diferentes propósitos. Geralmente, é mais fácil reescrever o programa em outro idioma do que escrever um bom intérprete ou compilador para uma linguagem de programação complexa. Especialmente quando essas línguas têm algo específico em que eram boas e foram projetadas com uma certa maneira de trabalhar em primeiro lugar. C foi projetado para ser compilado; portanto, faltam muitas abreviações convenientes que você deseja em um shell interativo, mas é muito bom para expressar manipulação muito específica e de baixo nível de dados / memória e interagir com o sistema operacional , que são tarefas que você costuma fazer quando deseja escrever um código compilado com eficiência. Enquanto isso, o bash é muito bom na execução de outros programas,

  2. Detalhes mais avançados: na verdade, existem linguagens de programação que são uma mistura de ambos os tipos (elas traduzem o código-fonte "na maior parte do caminho", para que possam interpretar a maior parte da interpretação / "pensamento" uma vez e fazer apenas um pouco da interpretação / "pensamento"). Java, Python e muitas outras linguagens modernas são realmente essas misturas: eles tentam obter alguns dos benefícios de portabilidade e / ou desenvolvimento rápido das linguagens interpretadas e um pouco da velocidade das linguagens compiladas. Existem várias maneiras possíveis de combinar essas abordagens, e diferentes idiomas o fazem de maneira diferente. Se você quiser se aprofundar neste tópico, pode ler sobre linguagens de programação compilando em "bytecode" (que é como compilar em sua própria "linguagem de máquina" inventada)

  3. Você perguntou sobre o bit de execução: na verdade, o bit executável existe apenas para informar ao sistema operacional que esse arquivo pode ser executado. Suspeito que o único motivo pelo qual os scripts bash funcionem para você sem a permissão de execução seja porque você os está executando dentro de um shell bash. Normalmente, o sistema operacional, quando solicitado a executar um arquivo sem o bit de execução definido, retornará apenas um erro. Porém, alguns shells como o bash verão esse erro e executam o arquivo de qualquer maneira, basicamente imitando as etapas que o sistema operacional normalmente executaria (procure a linha "#!" No início do arquivo e tente para executar esse programa para interpretar o arquivo, com um padrão próprio ou /bin/shse não houver "#!" linha).

  4. Às vezes, um compilador já está instalado no seu sistema e, às vezes, os IDEs vêm com seu próprio compilador e / ou executam a compilação para você. Isso pode fazer com que uma linguagem compilada pareça uma linguagem não compilada, mas a diferença técnica ainda está lá.

  5. Uma linguagem "compilada" não é necessariamente compilada no código da máquina, e toda a compilação deste é um tópico em si. Basicamente, o termo é amplamente utilizado: na verdade, pode se referir a algumas coisas. Em um sentido específico, um "compilador" é apenas um tradutor de um idioma (normalmente um idioma de "nível superior", mais fácil de ser usado por humanos) para outro idioma (normalmente um idioma de "nível inferior", mais fácil de usar pelos computadores - às vezes, mas na verdade não com muita frequência, esse é o código da máquina). Além disso, às vezes, quando as pessoas dizem "compilador", estão realmente falando de vários programas trabalhando juntos (para um compilador C típico, na verdade são quatro programas: o "pré-processador", o próprio compilador, o "assembler" e o " vinculador ").

mtraceur
fonte
Não entendo por que essa pergunta foi rejeitada. É uma resposta impressionantemente abrangente a uma pergunta que é ampla e não muito clara.
Anthony Geoghegan
Compilador traduz um idioma para outro. Não precisa ser compilado para linguagem de máquina. Você pode ter o compilador de bytecode. Compilador Java para ASM, etc. Os fabricantes de C não queriam mais potência, queriam a linguagem que atendesse às suas necessidades. C pode ser interpretado até certo ponto. Bash pode ser compilado - há shc . Se a linguagem é compilada / interpretada ou não - na maioria das vezes - depende das ferramentas usadas, não da linguagem em si, embora algumas convenções sejam seguidas.
MatthewRock
@MatthewRock Adicionei uma nota técnica para abordar a compilação em algo que não é necessariamente uma linguagem de máquina. Eu sinto que minha primeira nota técnica já cobre a coisa "C pode ser interpretado ... Bash pode ser compilado". Eu tenho uma idéia de como resolver o problema dos "fabricantes de C não queria mais energia", embora eu ache bem claro que eles projetaram a linguagem para serem eficientes no hardware que estavam usando no momento (o byte nulo- A coisa como terminador de string foi em parte devido ao fato de que isso permite que você use menos um registro ao iterar uma string nesses, afinal).
Mtraceur
@MatthewRock (cont.) Acho justo dizer que os fabricantes de C não queriam exatamente energia, mas queriam abstrair o trabalho de criação de SO, o que, especialmente naqueles dias, era algo em que a eficiência do código era valiosa. E foi um trabalho que eles teriam sido feitos em assembler. Então, eles criaram uma linguagem que se correlacionava estreitamente com o código de máquina usado pelas máquinas PDP para as quais eles estavam escrevendo seu código, que pelo menos como efeito colateral, se não como um objetivo de design notável, se prestava à eficiência nessa plataforma, mesmo com compiladores ingênuos que não otimizam.
Mraceur
Eles não fariam isso em montador. Eles tinham B . Armadilhas em todos os lugares, e a pergunta não está no tópico aqui. De maneira geral, a resposta provavelmente responderia à pergunta, mas ainda há detalhes que não são precisos.
MatthewRock
5

Linguagens de programação / script podem ser compiladas ou interpretadas.

Os executáveis ​​compilados são sempre mais rápidos e muitos erros podem ser detectados antes da execução.

Os idiomas interpretados são geralmente mais simples de escrever e adaptar, sendo menos rigorosos que os idiomas compilados, e não requerem compilação, o que os torna mais fáceis de distribuir.

Julie Pelletier
fonte
1
No script bash também dá um erro quando executado. mas não o compilamos.
quer
30
sempre é uma palavra perigosa ...
Radon Rosborough
3
O CPython compilado otimizado geralmente é mais lento que o asm.js otimizado (um subconjunto de JavaScript). Portanto, há um exemplo de não ser mais rápido e, portanto, nem sempre é "mais rápido". No entanto, geralmente é muito, muito mais rápido.
wizzwizz4
2
Isso não responde à pergunta.
mathreadler
3
sempre mais rápido é uma afirmação ousada. Mas isso vai muito fundo na teoria dos compiladores e intérpretes (e definição).
Giacomo Catenazzi
3

Imagine que o inglês não é sua língua nativa (isso pode ser bastante fácil para você se o inglês não for sua língua nativa).

Existem três maneiras de ler isso:

  1. (Interpretado) Ao ler, traduza cada palavra sempre que a vir
  2. (Interpretada otimizada) Encontre frases comuns (como "seu idioma nativo"), traduza-as e anote-as. Em seguida, traduza cada palavra, exceto as frases que você já traduziu
  3. (Compilado) Peça a alguém para traduzir toda a resposta

Os computadores têm uma espécie de "idioma nativo" - uma combinação de instruções que o processador entende e instruções que o sistema operacional (por exemplo, Windows, Linux, OSX etc.) entende. Esta linguagem não é legível por humanos.

As linguagens de script, como o Bash, geralmente se enquadram nas categorias 1 e 2. Elas pegam uma linha de cada vez, convertem essa linha e executam-na e depois passam para a próxima linha. No Mac e Linux, muitos intérpretes diferentes são instalados por padrão para diferentes idiomas, como Bash, Python e Perl. No Windows, você deve instalá-los você mesmo.

Muitas linguagens de script fazem um pouco de pré-processamento - tente acelerar a execução compilando pedaços de código que serão executados com frequência ou que de outra forma retardariam o aplicativo. Alguns termos que você pode ouvir incluem compilação AOT (Antecipação de Tempo) ou Just-in-time (JIT).

Por fim, linguagens compiladas - como C - traduzem o programa inteiro antes que você possa executá-las. Isso tem a vantagem de que a tradução pode ser feita em uma máquina diferente da execução; portanto, quando você dá o programa ao usuário, enquanto ainda pode haver erros, vários tipos de erros já podem ser limpos. Como se você desse isso ao seu tradutor, e eu mencionei como garboola mizene resplunksisso pode parecer um inglês válido para você, mas o tradutor pode dizer que estou falando bobagem. Quando você executa um programa compilado, ele não precisa de um intérprete - ele já está no idioma nativo do computador

No entanto, há uma desvantagem nos idiomas compilados: mencionei que os computadores têm um idioma nativo, composto por recursos do hardware e do sistema operacional - bem, se você compilar seu programa no Windows, não esperará que o programa compilado seja executado. um Mac. Alguns idiomas resolvem isso compilando para um tipo de idioma intermediário - um pouco como o inglês Pidgin - dessa maneira, você obtém os benefícios de um idioma compilado, além de um pequeno aumento de velocidade, mas isso significa que você precisa agrupar um intérprete com seu código (ou use um que já esteja instalado).

Por fim, seu IDE provavelmente estava compilando seus arquivos e poderia informar sobre erros antes de executar o código. Às vezes, essa verificação de erro pode ser mais aprofundada do que o compilador fará. Um compilador geralmente verifica apenas o necessário para produzir código nativo sensato. Um IDE geralmente executa algumas verificações extras e pode informar, por exemplo, se você definiu uma variável duas vezes ou se importou algo que não usou.

user208769
fonte
Essa resposta é ótima, mas acho que a compilação dinâmica usada pelo "intérprete" do Perl é distinta do que normalmente é chamado de "JIT"; portanto, provavelmente é melhor evitar esse termo. JIT é comumente usado para se referir à compilação just-in-time de um código de bytes já compilado para o código de máquina de destino, por exemplo, JVM, .Net CLR.
IMSoP
@IMSoP Sim, os idiomas que compilam bytes e os idiomas que o JIT compilam a partir do código de bytes são coisas realmente diferentes. Acho que vale a pena mencionar (coloquei uma breve referência a ela nas notas de rodapé da minha resposta), mas a idéia geral de que o JIT se encaixa e que acho que vale a pena ter é que é possível estar em algum lugar no meio entre "compilado" e "interpretado", isto é, parcialmente compilado e parcialmente interpretado. Dito isto, não tenho certeza se isso é mais valioso ou confuso / perturbador para alguém que ainda não entende a distinção entre compilado / interpretado, como o OP.
Mraceur
@mtraceur Para ser sincero, até eu me perco na distinção entre os modelos de, digamos, PHP 7, Perl 5, .Net e Java. Talvez o melhor resumo para um iniciante seja "existem várias maneiras de misturar compilação e interpretação, incluindo o uso de uma representação intermediária e a compilação ou recompilação de partes à medida que o programa é executado".
IMSoP
@IMSoP Eu concordo. Essa é a abordagem que segui na minha resposta, e esse seu comentário me deu uma idéia de como refinar isso ainda mais. Então obrigado.
Mtraceur
O @IMSoP JIT não acontece apenas no bytecode - por exemplo, o node.js possui alguns recursos do JIT. Mas eu concordo - coloquei todos sob o banner "JIT" por simplicidade, pois isso parecia uma pergunta para iniciantes - estou editando-o para usar um termo mais simples.
user208769
1

Muitas pessoas estão falando sobre interpretação versus compilação, mas acho que isso pode ser um pouco enganador, se você olhar de perto, já que algumas linguagens interpretadas são realmente compiladas em um bytecode intermediário antes da execução.

No final, a verdadeira razão pela qual os programas C precisam ser compilados para o formato executável é que o computador precisa fazer muito trabalho para transformar o código em um arquivo de origem C em algo que possa ser executado, por isso faz sentido salvar o produto de tudo isso funciona em um arquivo executável, para que você não precise fazer isso novamente novamente sempre que quiser executar seu programa.

Por outro lado, o interpretador Shell precisa fazer muito pouco trabalho para converter um script de shell em "operações da máquina". Basicamente, você só precisa ler o script linha por linha, dividi-lo em espaço em branco, configurar alguns redirecionamentos de arquivos e pipelines e, em seguida, executar um fork + exec. Como a sobrecarga de analisar e processar a entrada de texto de um script de shell é muito pequena em comparação com o tempo necessário para iniciar os processos no script de shell, seria um exagero compilar os scripts de shell em um formato de máquina intermediário, em vez de apenas interpretar o código fonte diretamente.

hugomg
fonte
+1, apesar de me perguntar se isso não está "colocando a carroça na frente dos bois": talvez os projéteis originais tivessem inicialmente a intenção de serem interativamente utilizáveis ​​e, portanto, baixos o suficiente para não incomodar a compilação, e a relativa simplicidade era apenas uma decisão de design com base nisso?
Mtraceur
Sim, essa é outra maneira de encarar esta questão :) #
1015 hugomg