0

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.

asked Feb 4, 2024 at 14:39
4
  • 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. Commented Feb 4, 2024 at 14:43
  • 3
    I 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. Commented Feb 4, 2024 at 16:18
  • @PhilipKendall Could you spell out what you mean by "DSL?" Commented Feb 5, 2024 at 9:26
  • @AnaNimbus A domain-specific language. Commented Feb 5, 2024 at 9:31

2 Answers 2

3

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, plus

  • a 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.

answered Feb 5, 2024 at 16:56
10
  • 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 on mystruct that require knowledge of the struct members (or private access if using CPP terminology). Commented Feb 6, 2024 at 12:24
  • Re Advantages: Yes: permit navigation with ubiquitous tools and avoid merge collisions. Commented Feb 6, 2024 at 12:26
  • @AnaNimbus: you are right, let me edit my post Commented 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. Commented 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. Commented Feb 6, 2024 at 13:15
1

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)

answered Feb 5, 2024 at 17:01
4
  • Won't separate compilation create multiple definition errors when linking? Each of the subject functions needs the definition of mystruct so that the struct members can be manipulated freely (but with a discipline imposed by the programmer). Commented Feb 6, 2024 at 12:32
  • OK, so you need to put mystruct in a header. Commented 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. Commented 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? Commented Feb 6, 2024 at 14:49

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.