Jump to content
IGNORED

Splitting C code over multiple files


LX.NET

Recommended Posts

Hello everyone,

 

I need some advice here. My C source code is getting so big now, that I feel the need to separate the code into several logical units. I tried creating multiple c files that each hold the logical units, but am stuck with all sorts of compile and linker errors (unresolved externals). So, hopefully some of you are able to enlighten me. I am struggling with what to include where.

 

An example:

 

- main.c

#include "items.h"

#include "enemies.h"

void main() -> uses enemy and item struct, calls spawn_enemy, spawn_item

 

- items.h

typedef struct item { ... }

 

- items.c

#include "items.h"

void spawn_item(...)

 

- enemies.h

#define MAX_ENEMIES 10

typedef struct enemy { ... }

enemy enemies[MAX_ENEMIES]

 

- enemies.c

#include "enemies.h"

void spawn_enemy(...)

 

Makefile will create these objects:

objects = lynx-160-102-16.o lynx-stdjoy.o items.o enemies.o main.o

 

My questions:

- Am I on the right track here?

- Why the unresolved externals on spawn_enemy?

- Where to leave variable declarations?

- Any examples of how to partition your code?

 

Thanks.

 

Link to comment
Share on other sites

As an afterthought: what would really help is some guidelines on what to place where. Here's what I thought would work (but doesn't):

 

(referenced) C (.c) files:

Function implementations

Variables?

 

header (.h) files

Defines

Types (structs)

Variables?

 

Referencing C (.c) files

--> What should be referenced here? Is an #include "foo.h" enough, or do I need to reference the variables and functions with extern declarations? Or can these be put into the header file that I'm #include-ing?

 

I've added include guards in my .h files. Still the variables that I have (inside the guards) get declared multiple times and there are warnings for this from the linker.

Link to comment
Share on other sites

You're missing prototypes in header files. By example, if you have a 'movement.c' file containing a function void move_enemy(int which), then you should have a prototype in 'movement.h' looking like this 'void move_enemy(int);' so other modules can call it.

 

Also if you're referencing variables from other modules you should use things like 'extern int var;' in the module or in a header. The rule is 'int var;' only appears in one module, and every other module or header refers to it via 'extern'.

Link to comment
Share on other sites

When the projects start to grow bigger then the number of files do not fit on the command line anymore.

 

One way to handle this is to start building libraries. The other is to start using compiler include files.

 

If you look at my segmented pong-example you can see that I split stuff into separate directories.

 

pong/main

pong/intro

pong/play

pong/music

pong/cart

 

In every subdirectory I also create an objlist file that can later be used by the linker to include all the right files into the cart.

 

A typical objlist contains a relative path to the object files like:

 

../play/pong.o

../play/gametime.o

 

In the cart-subdirectory I do the final cart build. Here I can list the needed object files by preceeding the list of file with a @ character like:

 

cl65 @../play/objfiles @../music/objfiles etc.

 

Then the limit of 256 characters per line that is a Windows problem does not cause troubles.

Link to comment
Share on other sites

- enemies.h

#define MAX_ENEMIES 10

typedef struct enemy { ... }

enemy enemies[MAX_ENEMIES].

 

Do not define the real space in the h-file.

 

enemies.h:

typedef struct enemy {...}
external enemy enemies[MAX_ENEMIES],

 

enemies.c:

#include "enemies.h"

enemy enemies[MAX_ENEMIES];
static int mylocaldata;
static in mylocalfunction() { ...}

 

Also use the keyword "static" in all variables and functions that you do not need outside you C-file.

Link to comment
Share on other sites

Thanks everyone.

So, to summarize: definitions go into the .h files, declarations into the .c files.

 

Also use the keyword "static" in all variables and functions that you do not need outside you C-file.

Is the effect of that that the symbols are not exported, or is there another reason (e.g. optimizations) that you should do this?

Should you mark variables/data that doesn't change (reference data e.g.) with const?

 

You're missing prototypes in header files. By example, if you have a 'movement.c' file containing a function void move_enemy(int which), then you should have a prototype in 'movement.h' looking like this 'void move_enemy(int);' so other modules can call it.

Should the prototypes be marked as extern as well? I imagine that main.c needs to have an extern void spawn_enemy prototype inside enemies.h. Will this conflict with enemies.c also including enemies.h (since it will have spawn_enemy, and then doesn't have to be extern)?

Link to comment
Share on other sites

The static keyword allows you to keep local things local.

 

Using "const" is very good for segments. A const definition puts the data into read-only so it will not be in the DATA segment anymore. This means that you can discard the LEVEL_RODATA segment and you can just read it in again from the cart when you need it.

 

The DATA segment keeps the variable values so you may need to keep this is RAM for the whole game.

 

It does not matter if you mark a variable extern first and then define it later in the file.

Link to comment
Share on other sites

Thanks everyone.

So, to summarize: definitions go into the .h files, declarations into the .c files.

 

Is the effect of that that the symbols are not exported, or is there another reason (e.g. optimizations) that you should do this?

Should you mark variables/data that doesn't change (reference data e.g.) with const?

 

It is mainly to simplify things. Anything declared in .h would make many eyebrows raised. Just is the way that C programmers have evolved. There is nothing written about it.

 

Should the prototypes be marked as extern as well? I imagine that main.c needs to have an extern void spawn_enemy prototype inside enemies.h. Will this conflict with enemies.c also including enemies.h (since it will have spawn_enemy, and then doesn't have to be extern)?

Yes, you can use the 'extern' keyboard all over your enemies.h file. And include it in your enemies.c, the C language automatically will "fuse" the declarations and there are no conflicts.

 

So you can do this:

enemies.h (to be included everywhere it is needed)

struct enemy {
};

extern struct enemy enemies[40];
extern void new_enemy(int);

enemies.c

#include "enemies.h"
struct enemy enemies[40];  /* The real declaration */
void new_enemy(int type)
{
  ...
}

Edited by nanochess
Link to comment
Share on other sites

Is the effect of that that the symbols are not exported, or is there another reason (e.g. optimizations) that you should do this?

Should you mark variables/data that doesn't change (reference data e.g.) with const?

 

Also, declaring a local variable as static puts it into the BSS segment or DATA segment depending on if you initialized it with the declaration (ie. not on the C stack), which can improve performance in some cases (and can cause problems in others-recursive code for instance). There is even a compiler flag to use static locals everywhere. This removes all the C stack manipulation for local variables at the cost of more memory.

 

This type of optimization should (imo), be done at the end of your project (along with the register keyword, using zeropage variables, loop optimization, etc...)

 

Will the prototypes be marked as extern as well? I imagine that main.c needs to have an extern void spawn_enemy prototype inside enemies.h. Will this conflict with enemies.c also including enemies.h (since it will have spawn_enemy, and then doesn't have to be extern)?

 

Not if you wrap your header files in an #ifdef like this:

 

#ifndef ENEMIES_H

#define ENEMIES_H

 

your enemies.h protoypes, externs and typedefs

 

#endif

 

If it's already defined, the header file is basically a nop (nothing is included).

Edited by Shawn Jefferson
Link to comment
Share on other sites

Thanks everyone for chipping in. I've managed to get this all to work properly know and (more importantly) have gotten my head around it. Now all it takes is some good planning and partitioning.

 

I will take Shawn's advice and not go overboard on the optimizations just yet. For now the project's game runs fast enough, but I may need to squeeze out some extra perf later on.

Link to comment
Share on other sites

Not if you wrap your header files in an #ifdef like this:

 

#ifndef ENEMIES_H

#define ENEMIES_H

 

your enemies.h protoypes, externs and typedefs

 

#endif

 

If it's already defined, the header file is basically a nop (nothing is included).

 

I've got my include guard in place. What I meant was that for other modules the extern keyword on functions in enemies.h (from enemies.c) is relevant, but it is not for enemies.c itself. For the latter I think that "intern" would be more relevant. You're saying that the compiler will understand when it encounters an extern marked function that is intern to the module, and ignore/merge the prototype?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...