A linguagem C responde a uma dificuldade concreta dos anos 1970: como escrever um sistema operativo suficientemente próximo do hardware para continuar eficiente, sem ter de o refazer quase por completo sempre que se muda de computador?
Dennis Ritchie desenvolveu C nos Bell Labs no contexto do Unix. A linguagem não procura esconder a máquina. Em vez disso, fornece um pequeno conjunto de abstrações — tipos, funções, estruturas e ponteiros — que o compilador pode traduzir eficientemente para várias arquiteturas. Este compromisso explica a sua difusão, mas também os erros de memória que linguagens mais recentes procuram impedir.
Antes de C: BCPL e B
Nos anos 1960, o software de sistemas ainda era escrito em grande parte em assembly. Esta linguagem permite controlar precisamente o processador, mas cada arquitetura possui as suas próprias instruções. Portar um programa significa muitas vezes reescrever uma grande parte dele.
Martin Richards concebeu BCPL em 1967 como uma linguagem compacta destinada, entre outros usos, a compiladores e sistemas. Ken Thompson derivou dela B para as primeiras versões do Unix. B simplificava o trabalho em relação ao assembly, mas o seu modelo essencialmente não tipado tinha sido pensado para máquinas organizadas em torno de palavras de memória.
O PDP-11, no qual o Unix evoluiu, podia endereçar bytes e manipular vários tamanhos de dados. Ritchie acrescentou tipos, melhorou arrays e estruturas e transformou progressivamente o «New B» em C entre 1971 e 1973.
Uma linguagem moldada pelo Unix
C não foi concebida no papel e depois aplicada ao Unix. A linguagem e o sistema evoluíram em conjunto. As necessidades do kernel levaram C a representar endereços, caracteres, blocos de memória e estruturas de dados com pouco custo adicional.
Um ponteiro contém o endereço de um dado. Permite percorrer um array, construir uma lista ligada ou aceder a um periférico. Esta flexibilidade não garante que o endereço seja válido. Um programa pode ler para além de um array, utilizar uma zona já libertada ou interpretar bytes com um tipo incorreto.
A linguagem também deixa certos detalhes à implementação: tamanho dos inteiros, ordem dos bytes ou alinhamento das estruturas. Um programa portátil deve respeitar as garantias definidas por C em vez de depender das particularidades observadas numa máquina.
A reescrita do Unix muda o alcance do projeto
Em 1973, a maior parte do kernel Unix foi reescrita em C. Algumas partes dependentes do hardware continuaram em assembly, mas a maior parte do sistema passou a poder ser recompilada para uma nova arquitetura.
Isto não significa que C torne automaticamente qualquer programa portátil. Drivers e pressupostos de hardware continuam a exigir adaptação. Demonstra, porém, que um sistema operativo de alto desempenho pode ser expresso numa linguagem de nível superior sem ficar preso a um processador.
O Unix difundiu-se depois pelas universidades com o seu código-fonte e ferramentas. Os estudantes aprenderam o sistema e a sua principal linguagem, escreveram novos programas e levaram essas práticas para a indústria. C beneficiou da adoção do Unix; o Unix tornou-se mais adaptável graças a C.
Uma sintaxe reduzida, uma biblioteca essencial
C oferece poucas construções integradas. Entrada e saída, manipulação de strings e alocação dinâmica vêm principalmente de bibliotecas. Esta separação mantém o núcleo da linguagem compacto e permite implementá-lo em ambientes muito diferentes.
A biblioteca padrão estabelece, contudo, um contrato indispensável. Uma função como fopen evita que cada programa tenha de conhecer a interface exata do sistema para abrir um ficheiro. A portabilidade assenta, portanto, em três elementos: a linguagem, o compilador e uma biblioteca compatível.
A compilação produz geralmente código nativo. C não impõe máquina virtual nem garbage collector. O programador controla o tempo de vida das alocações, favorecendo uma utilização previsível dos recursos, mas assumindo maior responsabilidade sobre eles.
Do «K&R C» à norma internacional
Em 1978, Brian Kernighan e Dennis Ritchie publicaram The C Programming Language. O livro descreveu a linguagem com precisão suficiente para se tornar a sua referência de facto. Esta variante é frequentemente chamada K&R C.
A multiplicação dos compiladores revelou gradualmente divergências. Um comité da ANSI começou em 1983 a definir uma norma comum. C89 formalizou a linguagem e a sua biblioteca; a ISO adotou-a em 1990.
As revisões seguintes acrescentaram prudentemente novos tipos, ferramentas para programação concorrente e clarificações. Esta evolução continua condicionada pela compatibilidade: enormes bases de código e muitas interfaces de sistema dependem do comportamento histórico de C.
Porque C influenciou tantas linguagens
C++ parte diretamente de C para acrescentar abstrações mais ricas. Objective-C associa-lhe um modelo de objetos inspirado em Smalltalk. Java, JavaScript, C# e Go retomam parte da sua sintaxe sem conservar o mesmo modelo de memória.
Esta influência sintática não deve esconder contratos diferentes. Java protege mais os acessos à memória através de uma máquina virtual. Rust procura garantir os tempos de vida através do seu sistema de tipos. Go utiliza garbage collection. Todas respondem, à sua maneira, aos custos do controlo direto oferecido por C.
C continua a ser uma interface comum em sistemas operativos, bibliotecas e ambientes embebidos. Mesmo uma linguagem que protege a memória precisa frequentemente de comunicar com uma API concebida em C.
Os limites da confiança concedida ao programador
A norma C define comportamentos indefinidos: quando um programa viola determinadas regras, nenhuma resposta é imposta. O compilador pode então otimizar partindo do princípio de que essas situações não ocorrem. Esta liberdade favorece o desempenho, mas torna alguns erros difíceis de prever.
Ultrapassagens de buffer e erros de tempo de vida estiveram na origem de numerosas vulnerabilidades. Compiladores, analisadores estáticos, proteções em execução e regras de codificação reduzem os riscos sem os eliminar. C continua pertinente quando são indispensáveis controlo preciso e ampla compatibilidade; não é, por isso, a escolha por defeito para todo o software.
Porque C continua importante
C estabeleceu um nível de abstração particularmente duradouro entre assembly e software de alto nível. Permite descrever operações próximas do hardware com uma notação partilhável por várias arquiteturas.
A sua história mostra que uma abstração bem-sucedida não esconde necessariamente todos os custos. Pode também torná-los manipuláveis. O preço dessa transparência é uma disciplina que a própria linguagem pouco impõe e que deve ser fornecida pelas equipas, ferramentas e normas.
Cronologia
- 1967: Martin Richards apresenta BCPL.
- 1969–1970: Ken Thompson desenvolve B para as primeiras ferramentas do Unix.
- 1971–1972: Dennis Ritchie faz B evoluir para C no PDP-11.
- 1973: a maior parte do kernel Unix é reescrita em C.
- 1978: publicação de The C Programming Language.
- 1983: início dos trabalhos do comité ANSI X3J11.
- 1989: publicação da norma ANSI C.
- 1990: adoção de C como norma ISO.
- 1999: C99 acrescenta, entre outros elementos, novos tipos e ferramentas numéricas.
- 2011: C11 introduz, nomeadamente, um modelo de memória para concorrência.
- Hoje: C continua central nos sistemas, no software embebido e nas interfaces de software.
Perguntas frequentes
Dennis Ritchie criou C sozinho?
É o seu principal criador. A linguagem nasceu, porém, no ambiente coletivo dos Bell Labs, em contacto com B, Unix, os seus utilizadores e compiladores.
C é portátil por natureza?
Facilita a portabilidade, mas um programa pode depender de detalhes de hardware ou extensões. A verdadeira portabilidade exige respeitar a norma e isolar o código específico de uma plataforma.
Qual é a diferença entre C e C++?
C++ deriva de C e acrescenta, entre outros mecanismos, classes, templates, sobrecarga e gestão determinística de recursos. Atualmente, as duas linguagens têm normas distintas e não são totalmente compatíveis.
Porque C não tem garbage collector?
A sua conceção privilegia um ambiente de execução reduzido e o controlo explícito dos recursos. Uma implementação pode acrescentar um coletor, mas a linguagem padrão não o exige.
C ainda é uma boa escolha para começar?
Ensina a representação dos dados e o funcionamento da memória, mas expõe rapidamente riscos complexos. A sua pertinência depende do objetivo pedagógico e da área em causa.
C será substituída por linguagens mais seguras?
Alguns novos projetos privilegiam Rust ou outras linguagens quando a segurança da memória é prioritária. A dimensão do código, das ferramentas e das interfaces existentes garante, no entanto, um longo período de coexistência para C.