I have a project where there is a primary, high-level, opaque struct
with many functions that operate on it. Maybe I am simulating a CPU.
How might the corresponding source code be organized?
One way to organize the source is to put the entire interface in one ".h" file and the definitions for all the functions in a single ".c" file. But I am leaning towards code organization that is more modular---not in the sense of program logic, but in the sense of getting each source file to be small as well as specific to a single function.
An important feature is that, in the compiled code, each function on the opaque struct
has knowledge of the struct
-ural details as well as the interface of the other functions.
Alternative 1
Code might be organized similar to:
//FILE: function001.h
// declarations
// ...
//FILE: function999.h
// declarations
//FILE: function000.c
// definition
// ...
//FILE: function999.c
// definition
//FILE: mystruct.h
typedef
struct s_mystruct
mystruct ;
#include "function000.h"
// ...
#include "function999.h"
//FILE: mystruct.c
#include "mystruct.h"
struct s_mystruct
{
// ...
} ;
#include "function000.h"
// ...
#include "function999.h"
#include "function000.c"
//...
#include "function999.c"
//FILE: main.c
#include "mystruct.h"
// do stuff with an instance of mystruct.
Alternative 2
Another way to organize is like (acknowlegments: @PhilipKendall and @pjc50):
//FILE: function001.h
// declarations
// ...
//FILE: function999.h
// declarations
//FILE: function000.c
// definition
// ...
//FILE: function999.c
// definition
//FILE: mystruct_h.h
typedef
struct s_mystruct
mystruct ;
//FILE: mystruct_h.c
#include "mystruct.h"
struct s_mystruct
{
// ...
} ;
//FILE: main.c
#include "mystruct.h"
// do stuff with an instance of mystruct.
And a make
rule would accomplish:
cat mystruct_h.h function*.h > mystruct.h
cat mystruct_h.c function*.c > mystruct.c
- compilation of the generated
mystruct.c
so that mystruct.h
and mystruct.c
form a conventional pair of interface and implementatuon files.
-
I don't think we can really help with this level of detail. Although my very strong recommendation is that if you are actually simulating a CPU, rather than this just being a thought experiment, is to autogenerate the source from a DSL of some kind, at which point you care much, much less about the structure of the actual C code.Philip Kendall– Philip Kendall02/04/2024 14:43:17Commented Feb 4, 2024 at 14:43
-
3I don’t think getting as many source files as possible is a worthwhile goal. Quite the opposite. For example, Swift encourages you to combine related functionality of various classes into one file. And #including multiple .c files is a big no.gnasher729– gnasher72902/04/2024 16:18:05Commented Feb 4, 2024 at 16:18
-
@PhilipKendall Could you spell out what you mean by "DSL?"Ana Nimbus– Ana Nimbus02/05/2024 09:26:51Commented Feb 5, 2024 at 9:26
-
@AnaNimbus A domain-specific language.Philip Kendall– Philip Kendall02/05/2024 09:31:29Commented Feb 5, 2024 at 9:31
2 Answers 2
The result of your suggested code organization does logically not differ from
a single header file
mystruct.h
with the struct itself and all function declarations, plusa single code file
mystruct.cpp
containing all the functions' implementations.
However, it differs physically, since now each function declaration and implementation gets its own source files. This has a few advantages, and one disadvantage:
One advantage is that it may be easier to navigate to a specific function when you don't have decent tooling. A modern IDE should be able to show you a condensed list of all functions inside one .c file and allow directly moving to it's code even with all functions are placed inside one file. When you use a very simple text editor, it may be easier to open a specific file for finding a certain function.
Another advantage is, when you work with different team members on the code in parallel, it will be less likely to get merge collisions on the same file.
The main disadvantage I see are potentially increased build times. If you really have 1000 .c files plus 1000 .h files, I guess you will notice the difference. But you are right, the speed affects here only the preprocessor - the compiler and linker will still see just one compilation unit and should not be affected much. There is only one way to find out if that's an issue: try it out in your environment.
So in case you work alone on this module, and in case you have a decent IDE at hand, you may prefer using the IDE's navigational features for jumping to a specific function's implementation. If you don't have such an IDE, your suggested code organization might help you, but you should keep an eye on the turn-around times.
Also, observe whether administrative tasks with many small files turn out to be more effective or less effective than administrative tasks with 2 or 3 files. Since I don't know the specific size and number of your files nor your way of working with them, I really can't tell you how it will work out for you. However, when the total number of LOC in one file will exceed (roughly) 5000 lines, my own judgement would probably go towards many small files. When the total number is less than 2000 lines, I would probably stick with a few larger files.
Hence, if I were in your shoes, and I had indicators that using two files per function might make things easier, I would give it a try. In case it turns out it is not as effective as expected, it should not be too hard to reverse this decision later with a few command line / script commands and merge everything back into one file again.
-
Re: "main disadvantage:" Although it may not be clear from my post, my intention is to have a single translation unit for the implementation of
mystruct
. While the preprocessor will process a large number of files via#include
, the compiler proper should see only one translatuon unit that completely defines the operations onmystruct
that require knowledge of thestruct
members (or private access if using CPP terminology).Ana Nimbus– Ana Nimbus02/06/2024 12:24:26Commented Feb 6, 2024 at 12:24 -
Re Advantages: Yes: permit navigation with ubiquitous tools and avoid merge collisions.Ana Nimbus– Ana Nimbus02/06/2024 12:26:40Commented Feb 6, 2024 at 12:26
-
@AnaNimbus: you are right, let me edit my postDoc Brown– Doc Brown02/06/2024 13:02:21Commented Feb 6, 2024 at 13:02
-
Re "turn-around times:" That is one of my concerns as well. I want to avoid accidentally editing a large ".c" file in the wrong place, such as between functions or in the wrong function.Ana Nimbus– Ana Nimbus02/06/2024 13:13:53Commented Feb 6, 2024 at 13:13
-
Re "IDE:" I think that a project that leans too heavily on a particular IDE risks loosing portability for copy-and-modify.Ana Nimbus– Ana Nimbus02/06/2024 13:15:34Commented Feb 6, 2024 at 13:15
Don't bother using #include to combine many C files into one file. They will behave fine as separate compilation units if they include the right headers.
(There are a few build systems which concatenate C files, or more usually CPP files, because this can build faster, but that's quite a hassle to work with)
-
Won't separate compilation create multiple definition errors when linking? Each of the subject functions needs the definition of
mystruct
so that thestruct
members can be manipulated freely (but with a discipline imposed by the programmer).Ana Nimbus– Ana Nimbus02/06/2024 12:32:32Commented Feb 6, 2024 at 12:32 -
OK, so you need to put mystruct in a header.pjc50– pjc5002/06/2024 12:39:45Commented Feb 6, 2024 at 12:39
-
Re "put mystruct in a header:" 1) I might be confusing definition with declaration. 2) Putting
mustruct
in a header breaks the encapsulation. I am starting to think that my problem is overly constrained.Ana Nimbus– Ana Nimbus02/06/2024 12:49:02Commented Feb 6, 2024 at 12:49 -
It's not unreasonable to have "internal only" .h files that are only referenced in a particular implementation, and "public" .h files that expose the public interface. Maybe putting all this lot in a library is the behavior you want for encapsulation?pjc50– pjc5002/06/2024 14:49:48Commented Feb 6, 2024 at 14:49
Explore related questions
See similar questions with these tags.