Pourquoi certains programmes C sont-ils écrits dans un fichier source énorme?

88

Par exemple, l' outil SysInternals "FileMon" du passé possède un pilote en mode noyau dont le code source est entièrement dans un fichier de 4 000 lignes. Même chose pour le tout premier programme ping jamais écrit (~ 2 000 LOC).

Fibre
la source

Réponses:

143

L'utilisation de plusieurs fichiers nécessite toujours des frais administratifs supplémentaires. Il faut configurer un script de construction et / ou un fichier de compilation avec des étapes de compilation et de liaison distinctes, s'assurer que les dépendances entre les différents fichiers sont correctement gérées, écrire un script "zip" pour faciliter la distribution du code source par courrier électronique ou par téléchargement, etc. sur. Les IDE modernes actuels supportent généralement une grande partie de ce fardeau, mais je suis assez certain qu'au moment où le premier programme ping a été écrit, aucun tel IDE n'était disponible. Et pour des fichiers aussi petits que ~ 4000 LOC, sans un tel IDE qui gère plusieurs fichiers pour vous, le compromis entre la surcharge mentionnée et les avantages liés à l'utilisation de plusieurs fichiers peut permettre aux utilisateurs de prendre une décision en ce qui concerne l'approche de fichier unique.

Doc Brown
la source
9
"Et pour des fichiers aussi petits que ~ 4000 LOC ..." Je travaille actuellement en tant que développeur JS. Quand j'ai un fichier de 400 lignes de code, je suis inquiet de la taille de ce fichier! (Mais nous avons des dizaines et des dizaines de fichiers dans notre projet.)
Kevin
36
@ Kevin: un cheveu sur ma tête, c'est trop peu, un cheveu sur ma soupe, c'est trop ;-) A en croire plusieurs fichiers JS ne cause pas autant de frais administratifs, comme dans "C sans IDE moderne".
Doc Brown
4
@ Kevin JS est une bête assez différente cependant. JS est transmis à un utilisateur final chaque fois qu'un utilisateur charge un site Web et que celui-ci ne l'a pas déjà mis en cache par son navigateur. C doit seulement avoir le code transmis une fois, puis la personne à l'autre bout le compile et il reste compilé (évidemment, il y a des exceptions, mais c'est le cas d'utilisation attendu généralement). De plus, les contenus en C ont tendance à être des codes hérités, tout comme la plupart des projets «4000 lignes, c'est normal», décrits dans les commentaires.
Pharap
5
@ Kevin Maintenant, allez voir comment underscore.js (1700 loc, un fichier) et une myriade d'autres bibliothèques distribuées sont écrits. Javascript est en réalité presque aussi mauvais que C en ce qui concerne la modularisation et le déploiement.
Voo
2
@Pharap Je pense qu'il voulait dire utiliser quelque chose comme Webpack avant de déployer le code. Avec Webpack, vous pouvez travailler sur plusieurs fichiers, puis les compiler en un seul paquet.
Brian McCutchon
81

Parce que C n’est pas bon en modularisation. Cela devient désordonné (fichiers d'en-tête et #includes, fonctions externes, erreurs de temps de lien, etc.) et plus vous apportez de modules, plus cela devient délicat.

Les langues plus modernes offrent de meilleures capacités de modularisation, en partie parce qu'elles ont été apprises des erreurs de C et facilitent la décomposition de votre base de code en unités plus petites et plus simples. Mais avec C, il peut être avantageux d’éviter ou de minimiser tous ces problèmes, même si cela signifie que l’on regrouperait ce qui serait autrement considéré comme trop de code dans un seul fichier.

Maçon Wheeler
la source
38
Je pense qu'il est injuste de décrire l'approche C comme des "erreurs"; elles étaient parfaitement raisonnables et judicieuses au moment où elles ont été prises.
Jack Aidley
14
Aucune de ces choses de la modularisation n'est particulièrement compliquée. Il peut être fait compliqué par le mauvais style de codage, mais il est difficile de ne pas comprendre ou mettre en œuvre, et rien ne pourrait être qualifiée de « erreurs ». La vraie raison, selon la réponse de Snowman, est que l'optimisation sur plusieurs fichiers source n'était pas aussi bonne dans le passé et que le pilote FileMon requiert des performances élevées. En outre, contrairement à l'opinion du PO, ces fichiers ne sont pas particulièrement volumineux.
Graham
8
@Graham Tout fichier de plus de 1000 lignes de code doit être traité comme une odeur de code.
Mason Wheeler
11
@JackAidley n’est pas injuste du tout , avoir quelque chose qui ne soit pas une erreur n’est pas exclusif entre nous et dire que c’était une décision raisonnable à l’époque. Les erreurs sont inévitables, étant donné les informations imparfaites et le temps limité, et doivent être apprises sans être honteusement cachées ou reclassées pour sauver la face.
Jared Smith
8
Quiconque prétend que l'approche de C n'est pas une erreur ne comprend pas comment un fichier C apparemment à dix lignes peut être en réalité un fichier à dix mille lignes avec tous les en-têtes #include: d. Cela signifie que chaque fichier de votre projet contient au moins dix mille lignes, quel que soit le nombre de lignes donné par "wc -l". Une meilleure prise en charge de la modularité réduirait facilement les temps d'analyse et de compilation en une fraction infime.
Juhist
37

Outre les raisons historiques, il y a une raison pour l'utiliser dans les logiciels modernes sensibles aux performances. Lorsque tout le code est dans une unité de compilation, le compilateur est capable d’optimiser l’ensemble du programme. Avec des unités de compilation séparées, le compilateur ne peut pas optimiser le programme entier de certaines manières (par exemple, l’inclusion de certains codes).

L'éditeur de liens peut certainement effectuer certaines optimisations en plus de ce que le compilateur peut faire, mais pas tous. Par exemple: les lieurs modernes sont très efficaces pour élire des fonctions non référencées, même à travers plusieurs fichiers objets. Ils pourront peut-être effectuer d'autres optimisations, mais rien ne ressemble à ce qu'un compilateur peut faire dans une fonction.

SQLite est un exemple bien connu de module de code à source unique. Vous pouvez en savoir plus à ce sujet sur la page de fusion de SQLite .

1. Résumé

Plus de 100 fichiers sources distincts sont concaténés dans un seul fichier volumineux de code C nommé "sqlite3.c" et appelé "la fusion". La fusion contient tout ce dont une application a besoin pour intégrer SQLite. Le fichier de fusion comprend plus de 180 000 lignes et plus de 6 mégaoctets.

La combinaison de tout le code pour SQLite dans un seul fichier de grande taille simplifie le déploiement de SQLite - il n’ya qu’un seul fichier à suivre. Et comme tout le code est dans une seule unité de traduction, les compilateurs peuvent optimiser l'optimisation entre procédures, ce qui permet d'obtenir un code machine plus rapide de 5% à 10%.


la source
15
Cependant, notez que les compilateurs C modernes peuvent optimiser l'ensemble du programme de plusieurs fichiers sources (sauf si vous les compilez d'abord dans des fichiers objet individuels).
Davislor
10
@ Davislor Regardez le script de construction typique: les compilateurs ne le feront pas de manière réaliste.
4
Il est beaucoup plus facile de modifier un script de compilation $(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(CFILES)que de tout transférer dans un seul fichier soudce. Vous pouvez même faire la compilation complète du programme en tant que cible alternative au script de construction traditionnel qui ignore la recompilation des fichiers source qui n'ont pas été modifiés, de la même manière que les utilisateurs pourraient désactiver le profilage et le débogage pour la cible de production. Vous n'avez pas cette option si tout est dans un gros tas o'source. Ce n’est pas ce à quoi les gens sont habitués, mais cela n’a rien de lourd.
Davislor
9
L'optimisation du programme entier / l'optimisation du lien (LTO) de @Davislor fonctionne également lorsque vous "compilez" le code dans des fichiers objet individuels (selon ce que "compiler" signifie pour vous). Par exemple, le LTO de GCC ajoutera sa représentation de code analysé aux fichiers d'objet individuels lors de la compilation, et au moment de la liaison, il utilisera celle-ci à la place du code d'objet (également présent) pour recompiler et construire le programme complet. Cela fonctionne donc avec les configurations de compilation qui compilent d’abord des fichiers objets individuels, bien que le code machine généré par la compilation initiale soit ignoré.
Rêveur
8
JsonCpp le fait aussi de nos jours. La clé est que les fichiers ne sont pas comme ça pendant le développement.
Courses de légèreté en orbite
15

En plus du facteur de simplicité mentionné par l'autre répondant, de nombreux programmes C sont écrits par une seule personne.

Lorsque vous avez une équipe de personnes, il devient souhaitable de diviser l'application en plusieurs fichiers source pour éviter les conflits gratuits lors des modifications de code. Surtout quand il y a des programmeurs avancés et très juniors travaillant sur le projet.

Quand une personne travaille seule, ce n'est pas un problème.

Personnellement, j'utilise plusieurs fichiers basés sur la fonction comme une chose habituelle. Mais c'est juste moi.

Ron Rouble
la source
4
@OskarSkog Mais vous ne modifierez jamais un fichier en même temps que votre futur moi.
Loren Pechtel
2

Parce que C89 n'avait pas de inlinefonctions. Ce qui signifiait que la fragmentation de votre fichier en fonctions entraînait la surcharge de transmettre des valeurs sur la pile et de sauter. Cela a ajouté un peu de temps supplémentaire à la mise en œuvre du code dans une seule instruction de commutateur de grande taille (boucle d'événement). Mais une boucle d'événement est toujours beaucoup plus difficile à mettre en œuvre efficacement (ou même correctement) qu'une solution plus modulaire. Donc, pour les projets de grande taille, les gens choisiraient toujours de ne pas moduler. Mais quand ils ont eu la conception de la conception à l'avance et peuvent contrôler l'état dans une déclaration de commutateur, ils ont opté pour cela.

De nos jours, même en C, il n'est pas nécessaire de sacrifier les performances pour la modularisation, car même en C, les fonctions peuvent être intégrées.

Dmitry Rubanovich
la source
2
Les fonctions C pourraient être tout aussi inline dans 89 que ces jours-ci, inline est quelque chose qui devrait être utilisé presque jamais - le compilateur sait mieux que vous dans presque toutes les situations. Et la plupart de ces fichiers 4k LOC ne constituent pas une fonction gigantesque, c’est un style de programmation horrible qui n’a aucun avantage notable en termes de performances.
Voo
@Voo, je ne sais pas pourquoi vous mentionnez le style de codage. Je ne le préconisais pas. En fait, j'ai mentionné que dans la plupart des cas, cela garantissait une solution moins efficace en raison d'une implémentation bâclée. J'ai également mentionné que c'est une mauvaise idée car elle ne s'adapte pas (aux projets plus importants). Cela dit, dans des boucles très serrées (ce qui est le cas dans le code réseau proche du matériel), pousser et extraire inutilement des valeurs sur la pile (lors de l'appel de fonctions) augmentera le coût du programme en cours d'exécution. Ce n'était pas une bonne solution. Mais c'était le meilleur disponible à l'époque.
Dmitry Rubanovich
2
Note obligatoire: le mot-clé inline n'a que peu à voir avec l'optimisation en ligne. Pour le compilateur, ce n'est pas un conseil particulier pour cette optimisation, mais plutôt une liaison avec des symboles en double.
Hyde
@Dmitry Le fait est que prétendre que, faute de inlinemot-clé dans les compilateurs C89, les compilateurs ne pouvaient pas s'inscrire en ligne, c'est pourquoi il fallait tout écrire dans une fonction géante, ce qui est incorrect. Vous ne devriez pratiquement jamais utiliser inlinecomme optimisation des performances - le compilateur en sait généralement mieux que vous de toute façon (et peut tout aussi bien ignorer le mot clé).
Voo
@Voo: Un programmeur et un compilateur savent généralement que certaines choses ne sont pas connues de l'autre. Le inlinemot-clé contient une sémantique liée à l'éditeur de liens qui est plus importante que la question de savoir s'il faut ou non effectuer des optimisations en ligne, mais certaines implémentations ont d'autres directives pour contrôler l'entrée en ligne et de telles choses peuvent parfois être très importantes. Dans certains cas, une fonction peut sembler trop volumineuse pour valoir la peine d'être doublée, mais un pliage constant peut réduire la taille et le temps d'exécution à presque rien. Un compilateur qui ne reçoit pas un fort coup de pouce pour encourager l'
entrée en ligne
1

Cela compte comme un exemple d'évolution, ce qui, je suis surpris, n'a pas encore été mentionné.

Dans les jours sombres de la programmation, la compilation d'un seul fichier peut prendre quelques minutes. Si un programme était modularisé, l’inclusion des fichiers d’en-tête nécessaires (aucune option d’en-tête précompilée) serait une cause supplémentaire importante de ralentissement. De plus, le compilateur peut choisir / avoir besoin de conserver certaines informations sur le disque lui-même, probablement sans l'avantage d'un fichier d'échange automatique.

Les habitudes auxquelles ces facteurs environnementaux ont conduit se sont répercutées sur les pratiques de développement en cours et ne se sont que lentement adaptées au fil du temps.

À l’époque, le gain découlant de l’utilisation d’un seul fichier serait semblable à celui obtenu par l’utilisation de disques SSD au lieu de disques durs.

ça
la source