8

Does anybody have any tip how to handle the symbolic constants and types definition in large program in a system manner to avoid circular dependencies between the header files? I have an idea to define one header file which will contain all structs, unions and enum types definitions and another header file which will contain all symbolic constants definitions. But I have doubts as far as this solution regarding to implementation hiding. Thank you for any ideas.

SiHa
  • 7,830
  • 13
  • 34
  • 43
Steve
  • 805
  • 7
  • 27
  • 4
    one straight way: use include guard. – Sourav Ghosh Sep 11 '17 at 07:53
  • 1
    Just simply use include guard or `#pragma once`. `#ifndef HELLO_H #define HELLO_H // your declarations here #endif` – danglingpointer Sep 11 '17 at 07:56
  • 1
    Putting all structure definitions in a single file is usually a bad idea since headers should be separated based on the modules they define. – Ajay Brahmakshatriya Sep 11 '17 at 08:04
  • 2
    @Steve If you're running into a dependecy problem, you're probably planning something the wrong way. The solution is usually to rethink your program structure to resolve the issue, instead of trying some dirty tricks. In some cases, a forward declaration can help, but I'd recommend this only if there's no other solution. – SBS Sep 11 '17 at 09:25
  • @SouravGhosh Include guard can fix such issue. But is such circular dependency *inevitable*? – smwikipedia Nov 14 '19 at 04:59

3 Answers3

8

You can separate out the typedef statements from the actual struct definitions and place them before including the other headers. This way if your datatype foo contains a member of type bar *, it doesn't need the definition of bar straight away - it'll be happy enough knowing that it's a valid datatype that will get resolved later.

#ifndef FOO_H
#define FOO_H

typedef struct foo_s foo;

#include "bar.h"

struct foo_s
  {
  bar *my_bar;
  };
#endif

.

#ifndef BAR_H
#define BAR_H

typedef struct bar_s bar;

#include "foo.h"

struct bar_s
  {
  foo *my_foo;
  };
#endif
Chris Turner
  • 8,082
  • 1
  • 14
  • 18
  • 1
    This would be an example of bad design though. If you have two header files that include each other, something is very very wrong with your program design. – Lundin Sep 11 '17 at 09:22
  • 2
    There are plenty of reasons why you might have two structures interconnected - how would you deal with that? Would you rather they were put in the same header file? – Chris Turner Sep 11 '17 at 09:38
  • I really can't think of a reason. Name one, if there are plenty. – Lundin Sep 11 '17 at 09:39
  • 3
    OK. Say you have a "person" structure that includes amongst other things the "house" that they live in. That "house" structure also has a list of "person" that live in the house. If either of those links were left out, you'd need to search through every "person" or "house" to find the missing reverse connection. – Chris Turner Sep 11 '17 at 09:57
  • 1
    @ChrisTurner Actually, if two structs are interconnected in a circular way, I'd really prefer putting them into the same header file. Moreover, I wouldn't use a forward declaration, but use the struct prefix in the member definition. E.g. `struct foo_s { struct bar_s *my_bar; } foo;` and `struct bar_s { struct foo_s *my_foo; } bar;` This is much more straightforward and compact. Forward declarations tend to make code difficult to read, so I avoid them whenever possible. – SBS Sep 11 '17 at 10:03
  • @ChrisTurner That's a situation that only arises if your program design allows "person" to be found out of any context. For example if you have a function `go_home (person p)` which is completely disconnected from the house class. A better design might be to have `house get_home (person p)` followed by `go_house (house h, person p)`. But overall I'm getting bad design vibes from this. I would guess such a design might be based on "all things that exist (nouns) should be objects" rather than "determine needed objects based on use-cases". The former tend to lead to weird dependencies. – Lundin Sep 11 '17 at 11:52
  • 2
    @Lundin it's just a possible example I came up. I'm sure there are plenty of reasons why you might have a "person" without the context of a "house" - maybe you can search for a "person" to find out where they live? iterating over each "house" and then every "person" in each "house" to find out where someone lives seems like a worse design than just having the "house" as an attribute of the "person" – Chris Turner Sep 11 '17 at 12:34
4

The solution is to simply use some manner of program design. Each "object"/"module" in your program should consist of one h file and one c file. The h file is the public interface. Each such object should only be concerned with its own designated task. It should only include the resources needed to perform that task.

With such a design, there should never be any circular dependencies, or the design is flawed. You should not fix a bad design with various code tricks, you should re-do the design.

But of course the same resource could be included multiple time from different parts of the code. This is why we always use header guards in every single h file.

mihai
  • 4,592
  • 3
  • 29
  • 42
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 3
    an example would help here :P – jenkizenki Aug 06 '20 at 22:14
  • 2
    Circular dependencies are not a valid design to you? There are no use case where you could need a circular dependency? I think this reasoning is pretty biased. True some circular decencies might have a bad design, but the concept itself is not flawed on its own. – Alexandre Daubricourt Aug 14 '22 at 20:15
  • @AlexandreDaubricourt There are real-word scenarios where there are circular dependencies which you need to modul with your program design, but that doesn't mean that you program design should have circular dependencies. Programs are abstractions. I can't think of any valid use-case where a program design should have a circular dependency. – Lundin Aug 15 '22 at 06:59
  • @Lundin Well let's say you're building a game. You have a `game` structure with all sorts of methods. In your game everything rendered to the screen comes from a `game_object`, each of these have an update function that is called this way `game_object.update(game)`. (where you might use `game.tick/game.deltaTime` or anything like that.) So in my `game_object` header file I'll need to have a reference to `game`. And in my `game` header file I'll need to have a reference to `game_object` (would usually be done through something like (`game->scene->game_object[]`) – Alexandre Daubricourt Aug 16 '22 at 06:38
  • @AlexandreDaubricourt What you describe seems to be global variables, so it's already a much bigger design problem than circular dependencies. Also I don't see why header files defining your function interface need access to the parameters to be passed, that's also a big design problem - again comes the global variable spaghetti. – Lundin Aug 16 '22 at 06:41
  • @Lundin global variables? Why? No I don't use any global, not even statics. Header files defining function interface need access to the parameters to be passed because its type has to be specified in the function interface declaration, no? in game.h: defining t_game->actors of type game_object[] inside t_game struct. in game_object.h: defining game_object_update(t_game_object *actor, t_game *game) – Alexandre Daubricourt Aug 16 '22 at 17:21
-1

In any given header file, put

#ifndef FILENAME_H
#define FILENAME_H

struct a {}
union b {}

function c();
#endif

This will cause the file to be included only once.

  • 2
    I think the problem is not about a file being included multiple times but the headers have type definitions which depend on each other. – Ajay Brahmakshatriya Sep 11 '17 at 08:07
  • Then I misunderstood. Maybe an example in the question would help... There is an answer [here](https://stackoverflow.com/questions/10122621/c-circular-dependency) that is probably what the OP is after. – Hans Petter Taugbøl Kragset Sep 11 '17 at 08:11
  • @Hans Petter Taugbøl Kragset the problem which I would like to prevent is as Ajay Brahmakshatriya said. I have no code yet. I would like to know the good practise how to prevent the above mentioned situation in large project. – Steve Sep 11 '17 at 08:33
  • @Steve Read up on object-oriented design. It applies to all programs no matter language. Your code needs to be organized as autonomous "objects" (call them what you like) with private encapsulation. Create files based on the code's functionality, not based on what kind of language features and mechanism you think will be in those files. And that's about it. – Lundin Sep 11 '17 at 09:29