Por que alguns programas em C são escritos em um arquivo de origem enorme?

88

Por exemplo, a ferramenta SysInternals "FileMon" do passado tem um driver de modo kernel cujo código-fonte é totalmente em um arquivo de 4.000 linhas. O mesmo para o primeiro programa de ping já escrito (~ 2.000 LOC).

    
por Bran 02.03.2017 / 22:44
fonte

6 respostas

141

O uso de vários arquivos sempre requer uma sobrecarga administrativa adicional. É preciso configurar um script de compilação e / ou makefile com estágios separados de compilação e vinculação, verifique se as dependências entre os arquivos diferentes são gerenciadas corretamente, escreva um script "zip" para facilitar a distribuição do código-fonte por email ou download em. Os IDEs modernos hoje em dia costumam suportar muito esse fardo, mas tenho certeza de que, no momento em que o primeiro programa de ping foi criado, esse IDE não estava disponível. E para arquivos pequenos como ~ 4000 LOC, sem essa IDE que gerencia vários arquivos para você, o trade-off entre a sobrecarga mencionada e os benefícios de usar vários arquivos pode permitir que as pessoas tomem uma decisão a abordagem de arquivo único.

    
por 02.03.2017 / 23:00
fonte
80

Porque C não é bom em modularização. Ele fica bagunçado (arquivos de cabeçalho e #includes, funções externas, erros de tempo de link, etc) e quanto mais módulos você traz, mais complicado ele fica.

Linguagens mais modernas têm melhores recursos de modularização, em parte porque aprenderam com os erros de C e facilitam a decomposição de sua base de código em unidades menores e mais simples. Mas, com C, pode ser benéfico evitar ou minimizar todo esse problema, mesmo que isso signifique colocar o que seria considerado um código excessivo em um único arquivo.

    
por 02.03.2017 / 23:09
fonte
37

Além das razões históricas, há uma razão para usar isso em softwares modernos sensíveis ao desempenho. Quando todo o código está em uma unidade de compilação, o compilador é capaz de realizar otimizações de todo o programa. Com unidades de compilação separadas, o compilador não pode otimizar o programa inteiro de determinadas maneiras (por exemplo, inlining determinado código).

O vinculador pode executar algumas otimizações, além do que o compilador pode fazer, mas não todas. Por exemplo: os linkers modernos são realmente bons em eliminar funções não referenciadas, mesmo em vários arquivos de objeto. Eles podem ser capazes de realizar algumas outras otimizações, mas nada como o que um compilador pode fazer dentro de uma função.

Um exemplo bem conhecido de um módulo de código de fonte única é o SQLite. Você pode ler mais sobre isso na Página de amalgamação do SQLite .

1. Executive Summary

Over 100 separate source files are concatenated into a single large files of C-code named "sqlite3.c" and called "the amalgamation". The amalgamation contains everything an application needs to embed SQLite. The amalgamation file is more than 180,000 lines long and over 6 megabytes in size.

Combining all the code for SQLite into one big file makes SQLite easier to deploy — there is just one file to keep track of. And because all code is in a single translation unit, compilers can do better inter-procedure optimization resulting in machine code that is between 5% and 10% faster.

    
por 03.03.2017 / 03:59
fonte
15

Além do fator de simplicidade que o outro entrevistado mencionou, muitos programas em C são escritos por um indivíduo.

Quando você tem uma equipe de indivíduos, torna-se desejável dividir o aplicativo em vários arquivos de origem para evitar conflitos desnecessários nas alterações de código. Especialmente quando há programadores avançados e muito júniores trabalhando no projeto.

Quando uma pessoa trabalha sozinha, isso não é um problema.

Pessoalmente, eu uso vários arquivos baseados na função como algo habitual. Mas isso sou só eu.

    
por 02.03.2017 / 23:28
fonte
2

Porque o C89 não tinha inline funções. O que significava que dividir seu arquivo em funções causava a sobrecarga de empurrar valores na pilha e pular. Isso adicionou um pouco de sobrecarga sobre a implementação do código em uma grande instrução switch (event loop). Mas um loop de eventos é sempre muito mais difícil de implementar de forma eficiente (ou até mesmo correta) do que uma solução mais modularizada. Portanto, para projetos de grande porte, as pessoas ainda optariam por modularizar. Mas quando eles tiveram o design planejado antecipadamente e conseguiram controlar o estado em uma declaração de switch, eles optaram por isso.

Hoje em dia, mesmo em C, não é preciso sacrificar o desempenho para modularizar, porque mesmo em funções C pode ser embutido.

    
por 04.03.2017 / 11:41
fonte
1

Isso conta como um exemplo de evolução, que eu estou surpreso por não ter sido mencionado ainda.

Nos dias sombrios da programação, a compilação de um único FILE pode levar alguns minutos. Se um programa fosse modularizado, a inclusão dos arquivos de cabeçalho necessários (sem opções de cabeçalho pré-compiladas) seria uma causa adicional significativa de lentidão. Além disso, o compilador pode escolher / precisar manter alguma informação no próprio disco, provavelmente sem o benefício de um arquivo de troca automático.

Os hábitos que esses fatores ambientais levaram a práticas de desenvolvimento em andamento e só se adaptaram lentamente ao longo do tempo.

No momento, o ganho de usar um único arquivo seria semelhante ao que obtemos pelo uso de SSDs em vez de HDDs.

    
por 15.03.2017 / 23:42
fonte