Вопрос по c – Как я могу разделить мои монолитные программы на более мелкие, отдельные файлы?

10

Во всем коде, который я вижу в Интернете, программы всегда разбиты на множество небольших файлов. Однако для всех моих школьных проектов я получил только один гигантский исходный файл C, содержащий все используемые мной структуры и функции.

Я хочу научиться разбивать мою программу на более мелкие файлы, что кажется профессиональным стандартом. (Кстати, почему - просто для удобства чтения?)

Я искал вокруг, и все, что я могу найти информацию, - это создание библиотек, а это не то, что я хочу делать, я не думаю. Хотелось бы мне быть более полезным, но я не совсем уверен в том, как это реализовать - я уверен только в конечном продукте, который мне нужен.

Нет "просто" в "для простоты чтения". :) Другими словами, не стоит недооценивать важность неустанной работы над абстракцией, документацией, расцеплением, инкапсуляцией и читабельностью. Они не приходят бесплатно, но, похоже, их обратное. unwind
Другие привели веские причины, почему разделились, позвольте мне дать один, почему бы не Чтобы сделать двоичный файл быстрее, позволяя больше оптимизаций. Оптимизатор работает только в пределах одного модуля компиляции (файл + # включает) одновременно, а компоновщик выполняет лишь несколько элементарных оптимизаций. Объединяя все в один огромный не поддерживаемый большой двоичный объект, вы позволяете компилятору выполнять более глубокие оптимизации. SF.

Ваш Ответ

6   ответов
3

main причины

Maintainability: In large, monolithic programs like what you describe, there's a risk that changing code in one part of the file can have unintended effects somewhere else. Back at my first job, we were tasked with speeding up code that drove a 3D graphical display. It was a single, monolithic, 5000+-line main function (not that big in the grand scheme of things, but big enough to be a headache), and every change we made broke an execution path somewhere else. This was badly written code all the way around (gotos galore, literally hundreds of separate variables with incredibly informative names like nv001x, program structure that read like old-school BASIC, micro-optimizations that didn't do anything but make the code that much harder to read, brittle as hell) but keeping it all in one file made the bad situation worse. We eventually gave up and told the customer we'd either have to rewrite the whole thing from scratch, or they'd have to buy faster hardware. They wound up buying faster hardware.

Reusability: There's no point in writing the same code over and over again. If you come up with a generally useful bit of code (like, say, an XML parsing library, or a generic container), keep it in its own separately compiled source files, and simply link it in when necessary.

Testability: Breaking functions out into their own separate modules allows you to test those functions in isolation from the rest of the code; you can verify each individual function more easily.

Buildability: Okay, so "buildability" isn't a real word, but rebuilding an entire system from scratch every time you change one or two lines can be time consuming. I've worked on very large systems where complete builds could take upwards of several hours. By breaking up your code, you limit the amount of code that has to be rebuilt. Not to mention that any compiler is going to have some limits on the size of the file it can handle. That graphical driver I mentioned above? The first thing we tried to do to speed it up was to compile it with optimizations turned on (starting with O1). The compiler ate up all available memory, then it ate all the available swap until the kernel panicked and brought down the entire system. We literally could not build that code with any optimization turned on (this was back in the days when 128 MB was a lot of very expensive memory). Had that code been broken up into multiple files (hell, just multiple functions within the same file), we wouldn't have had that problem.

Parallel Development: There isn't an "ability" word for this, but by breaking source up into multiple files and modules, you can parallelize development. I work on one file, you work on another, someone else works on a third, etc. We don't risk stepping on each other's code that way.

9

разбить свой код на несколько библиотек!

Давайте возьмем пример, в одном файле у вас есть:

#include <stdio.h>

int something() {
    return 42;
}

int bar() {
    return something();
}

void foo(int i) {
    printf("do something with %d\n", i);
}

int main() {
    foo(bar());
    return 0;
}

Вы можете разделить это до:

mylib.h:

#ifndef __MYLIB_H__
#define __MYLIB_H__

#include <stdio.h>

int bar();
void foo();

#endif

N.B.: the preprocessor code above is called a "guard" which is used to not run twice this header file, so you can call the same include at several places, and have no compilation error

mylib.c:

#include <mylib.h>

int something() {
    return 42;
}

int bar() {
    return something();
}

void foo(int i) {
    printf("do something with %d\n", i);
}

myprog.c:

#include <mylib.h>
int main() {
    foo(bar());
    return 0;
}

для компиляции вы делаете:

gcc -c mylib.c -I./
gcc -o myprog myprog.c -I./ mylib.o

теперь преимущества?

it enables you to split logically your code, and then find the code unit faster it enables you to split your compilation, and recompile only what you need when you modified something (which is what the Makefile does for you) it enables you to expose some functions and hide others (like the "something()" in the example above), and it helps documenting your APIs for people that will read your code (like your teacher) ;)
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded user1209326
1

Ну, я не эксперт, но я всегда стараюсь думать о субъектах, которые больше, чем функция. Если у меня есть группа функций, которая логически принадлежит друг другу, я помещаю ее в отдельный файл. Обычно, если функциональность аналогична, и кто-то хочет одну из таких функций, ему, вероятно, потребуются и другие функции из этой группы.

Необходимость разделения одного файла возникает по той же причине, по которой вы используете разные папки для своих файлов: люди хотят иметь некоторую логическую организацию по многочисленным функциям, чтобы им не нужно было извлекать огромный единственный исходный файл для найти нужный. Таким образом, вы можете забыть о нерелевантных частях программы, когда вы думаете / разрабатываете какую-то фиксированную часть программы.

Еще одна причина для разделения может быть то, что вы можетеhide некоторая внутренняя функция из остальной части кода, не упоминая ее в заголовке. Таким образом, явно отделить внутренние функции (которые необходимы только внутри.c файл) из функций, интересующих внешнюю «вселенную» вашей программы.

Некоторые языки более высокого уровня даже расширили понятие «функции, принадлежащие друг другу». в «функции, работающие над одной и той же вещью, представленные как одна сущность» - и назвал этоclass.

Другая историческая причина разделения - отдельная функция компиляции. Если ваш компилятор работает медленно (например, это часто случается с C ++), разделение кода на несколько файлов означает, что, если вы измените только одно местоположение, высока вероятность того, что для сбора изменений потребуется перекомпилировать только один файл , Поскольку современные компиляторы C не так медленны по сравнению с типичной скоростью процессора, это может быть не проблемой для вас.

3

is it just for ease of reading?

Нет, это также может сэкономить вам много времени на компиляцию; когда вы изменяете один исходный файл, вы только перекомпилируете этот файл, а затем перекомпилируете, вместо того, чтобы перекомпилировать все. Но главное - разделить программу на набор хорошо разделенных модулей, которые легче понять и поддерживать, чем один монолитный «блоб».

Для начала постарайтесь придерживаться правила Роба Пайка о том, что& quot; данные преобладают & quot;: разработайте свою программу вокруг множества структур данных (structобычно с операциями над ними. Поместите все операции, которые принадлежат одной структуре данных, в отдельный модуль. Сделай все функцииstatic это не должно вызываться функциями вне модуля.

Error: User Rate Limit Exceeded
2

Простота чтения - это одна из причин разбивки файлов, но другая заключается в том, что при создании проекта, содержащего несколько файлов (заголовочные и исходные файлы), хорошая система сборки будет перестраивать только файлы, которые были изменены, что сокращает время сборки.

Что касаетсяhow Чтобы разбить монолитный файл на несколько файлов, есть много способов. Говоря за меня, я бы попытался сгруппировать функциональность, поэтому, например, вся обработка ввода помещается в один исходный файл, выводится в другой, а функции, которые используются многими различными функциями, в третьем исходном файле. Я бы сделал то же самое со структурами / константами / макросами, структурами, связанными с группой / и т.д. в отдельных заголовочных файлах. Я бы также отметил функции, используемые только в одном исходном файле, какstaticпоэтому они не могут быть использованы из других исходных файлов по ошибке.

2

Просто чтобы дать вам идею.

создайте файл с именем print.c, поместите его внутрь:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void print_on_stdout(const char *msg) {
   if (msg) fprintf(stdout, "%s\n", msg);
}
void print_on_stderr(const char *msg) {
   if (msg) fprintf(stderr, "%s\n", msg);
}

создайте файл с именем print.h, поместите его внутрь:

void print_on_stdout(const char *msg);
void print_on_stderr(const char *msg);

создайте файл с именем main.c, поместите его внутрь:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "print.h"

int main() {
    print_on_stdout("test on stdout");
    print_on_stderr("test on stderr");

    return 0;
}

Теперь для каждого файла C скомпилируйте с:

gcc -Wall -O2 -o print.o -c print.c
gcc -Wall -O2 -o main.o -c main.c

Затем свяжите скомпилированные файлы для генерации исполняемого файла:

gcc -Wall -O2 -o test print.o main.o

Запустите ./test и наслаждайтесь.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded user1209326
Error: User Rate Limit Exceededman test

Похожие вопросы