Struggling with multi-file C project

Go To Last Post
28 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Let me begin by saying that I've searched all over this site and I've looked throught lots of the great "sticky" links about C resources on the net. I'm STILL stumped however. So I thought I'd just post my question here and see how many flames I get.

My problem:

I had this nice piece of code that was all jumbled together in one file. It worked great so I thought I would fix it. :lol:

I wanted to break it into 5 or 6 .c files to make it more readable. Now nothing works. ... I know surprise surprise. Anyway, it's clear that my problems stem from not having a clear understanding of how definitions and declarations work; how to use the "extern" keyword correctly; and how the order of things within a .c file affect the compiler.

My code is close to 2000 lines so I think it's a bit much to post all of it here. I'm asking if anyone knows a readable treatese on just this one subject of mulit-file C projects. I've read quite a few from googling around the net but none so far have answered my questions.

Anyone? I KNOW I'm not the first to struggle with this.

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Have a look here
https://www.avrfreaks.net/index.p...

Basically declare all your "Global vars" as extern in some ".h" file , and it's even legal to include the ".h" file declaring them external in the file where they are actually declared wo. the external.

And remember to declare the functions also in the ".h" file

A declaretion like this is a "function"
uint8_t func(uint8_t char)
{
}

A declaration like this is a "definition" , like the external. (no brackets before the semicolon)
uint8_t func(uint8_t char);

A C Compiler wants a definition before encountering the first reference to a function , or it'll assume all parms are int's.

Meaning include the ".h" file defining the functions from other modules . At top of the c file.

Watch out for the use of static , as it specifically tells the compiler not to make that var/function known outside of that specific ".c" file. (Great for hiding "subfunctions/vars" only used within a module.

/Bingo

Last Edited: Tue. Jan 17, 2006 - 03:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's not really too complicated. If you just take one large C file and split it into 5 then the only issue becomes one of "visibility" from the code in one file to another. You don't really need to worry about functions as the linker will "glue" them all back together at the end of the compile anyway so if dump() in file dump.c calls timer() in time.c then you don't need to do anyhing for the one to be able to "see" the other. EXCEPT that when timer() is called in the dump.c at that point the compiler needs to know the types of paramaters passed into timer() and the type of it's return value. This is where .h files come in. In those you can define the prototype for timer() which is just like the definition at the head of the function but instead of it being followed by {...code...} it ends simply with a semi-colon.

Now for each of the split .c files you could choose to have a separate header file (dump.h, time.h, etc.) or it may be easier if the project isn't TOO big to just have one .h file that is shared amongst all the modules in the project and it contains the function prototypes for all the functions in all six .c files. If you go for one big .h then it will probably be #include'd in all the .c files. If you go for separate .h files then a .c file only has to #include the .h files for the modules who's services it calls o - so above dump.c would have a #include time.h so it would "see" the prototype of the timer() function.

The other thing you have to move in the .h file(s) are any typedefs and struct/enum definitions that have to be visible to the clients of the functions in a particular module so it knows how to pass values to/from the function. What you NEVER put in .h files is any lines of program that actually generate any code or data!

The only other thing to worry about now is const data and variables. Anything that's only used by a local module (dump / time) should remain in the relevant .c file but any const or variables that must be "visible" to more than one module should be separated into their own file and then a .h file (possibly lines in the single, main .h file) declare the same things but with the word "extern" pre-pended. So say there's a:

const unsigned char greeting[] = { "Hello World"};

then this would go in the separate .c file (I usually call it global.c) and then a reference to this would go into main.h as:

extern const unsigned char greeting[];

and then both time.c and dump.c that want to use greeting would #include and could then printf("%s", greeting); or whatever.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bruce Eckel's "Thinking in C++" e-book @ http://www.pythoncriticalmass.com/ is a good resource. He provides a lot of C information before he explains how C++ differs, I've always found his writing very easy to understand (and learned a lot about both languages). Check out Vol 1, chap 3 "The C in C++" (scoping, and specifying storage allocation), chap 4 "data abstraction" (header file etiquette).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh, also "tools for seperate compilation" in chap 2, and "static elements from C" in chap 10.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wow. Ok. Great answers all. It will take me some time to parse through the "not too complicated" answer. I will read up and come back with some more pointed questions. In the mean time, I'm at work now. So I decided to poke through our software guy's code to see how he is doing it. The unfortunate discovery is he is doing some things I think my compiler will complain about. For example he has a global in a global.h file with "extern" in front of it. That's fine but then he #includes global.h in main.c AND at the top of main.c he has the variable again shown with "extern" in front of it. For the life of me I don't understand why that has to be there. And why the compiler wouldn't complain about it showing up twice.

Anywho...I'll study your answers above and come back later with yet more questions. :lol:

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, I've studied the posts above and did some reading in a couple of books. I think the fog is just slightly starting to lift.

One specific question:

If you have a global struct that you want several modules to be able to use to declare variables, where do you put the "prototype" or whatever you call it. And how do you handle the "extern" thing in this case.

In other words lets say you have a prototype struct like this:

struct xyz{
char A;
int B;
float C;
}

moduleA.c wants to declare a variable testA of type struct xyz.
moduleB.c wants to declare a variable testB of type struct xyz.
moduleC.c wants to decalre a global variable testC of type struct xyz
moduleD.c wants to use the global variable struct xyz testC.

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh and I forgot another question:

I see a lot of header files with the compiler directive:

XXXX.h

#ifndef XXXX_INCLUDED_
#define XXXX_INCLUDED_
...

Why is this needed?

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need to put the structure in a header file, and include this header file in each source file.
If you look through code examples you might of seen, there is often the following at the start of a header file:-
#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEEN

it is a very common construct, and stops you getting multiple definition errors. Have a look at the following link, or search for ' ifndef + headers '

http://developer.apple.com/documentation/DeveloperTools/gcc-4.0.1/cpp/Once_002dOnly-Headers.html

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

With the moduleD.c example, if testC is declared in moduleC.c (and not in a header file), then to access it in moduleD.c, you need to declare it as:

extern struct xyz testC

in moduleD.c before you can use it. This tells the compiler that it is already defined somewhere else. (The definition of the struct is, however, still done in the header file).

You also have the option of defining the struct as:

typedef struct
{
  char A;
  int B;
  float C;
}  xyz;

Then you can use:

xyz typeA;
xyz typeB;
...

instead of:

struct xyz typeA;
struct xyz typeB;
...

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sgomes wrote:
And why the compiler wouldn't complain about it showing up twice.

The compiler doesn't mind as long as all such declarations are identical but it is kind of "messy" to have it defined in two places in case you ever need to change it. I'll bet the original author had it local to the one .c file then realised he needed access to it in other modules so copied the declaration to the global.h but forgot to remove it from the original C file (I've done that!)

The ability to declare the same things multiples times (as long as identical) is so that if, for example, you #include a .h with some definition but you also #include another .h and that in turn #include's the first .h then all the definitions in the first will be seen a second time but the compiler won't complain.

But this is why you often see the entire contents of a fred.h wrapped in:

#ifndef _fred_h
#define _fred_h
... contents of .h file
#endif

That way the contents of fred.h will only be parsed on the first occasion the compiler "sees" it. During that _fred_h will be #define'd so the next time the compiler comes across fred.h the #ifndef will no longer be true and the entire contents are then ignored.

Apart from anything else this just speed compilation but is mainly to protect against the case where fred.h has a #include of eric.h and eric.h has a #include of fred.h (think about it!)

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sgomes wrote:
Oh and I forgot another question:

I see a lot of header files with the compiler directive:

XXXX.h

#ifndef XXXX_INCLUDED_
#define XXXX_INCLUDED_
...

Why is this needed?

This feauture allows the file to be idempotent. Here's a link:
http://www.catb.org/~esr/jargon/...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Now THERE is a 25cent word!!! I've got to work that into some conversation today. :shock:

I've noticed the two examples given both refer to nested #includes. Wouldn't it also apply to the far more common serial #includes you get from the common or or etc? I think I have atleast one of those in each .c file in my whole project! Yes, I'm aware that those files have been made idempotent but the same is true of my own header files that get included often. In other words, would there be a good reason NOT to idempotentize ALL of my header files?

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sgomes wrote:
In other words, would there be a good reason NOT to idempotentize ALL of my header files?

No. It doesn't cost a thing to do it, and saves many a headache in the long run. It should be mandatory as a part of everyone's coding standard. It's one of those "do it and forget about it" items.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Of course we're all assuming that you wanted to do it properly in the first place :)

Its probably worth knowing that the compiler religously places the contents of any file for which there is a #include in its compile stream.

It helps in understanding why all those #ifndef _HEADERX_H definitions. As, when the compiler comes to that bit and the value is defined it just ignores everything else until it finds the matching #endif. Thus, even though the compiler has multiple includes of a single file, it only processes the contents once.

So, if all you wanted to achieve was the source spread over multiple files you could've just hacked the .c file into smaller files. One would be .c and the others with a different extension (say .inc) and then used #include to include each in turn.

But, as I said, that's hardly doing it properly :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, I'd like to post that all is well but I can't. Frustration is mounting. I really thought I understood what was going on too.... :(

My mental picture of this concept is that I imagine all the files back together as one large file. I ask myself what the compiler/linker needs to know at each point in the code. If it doesn't know about a variable/structure/typdef/prototype etc then I need to tell it. Either by "extern" or actual definition/declaration. Correct me if I'm wrong with this picture.

Anyway, I thought I had accomplished all that. I also added the "#ifndef XYZ_INCLUDED / #define XYZ_INCLUDED / #endif" wrapper around each of my header files.

My situation now is that I get a pile of "identifier xxx redefined" errors with just two of my header files. I'm completely stumped. It really seems as though the compiler is ignoring the wrappers on just those two. Yes I checked the spelling and all that. I've searched all through the code to see if I'm declaring or defining those variables anywhere else (i.e. as if I forgot the "extern" keyword somewhere) but nope it's all clean.

At this point I think it is wise to put everything back in one file and move on. Maybe I can come back to this high level stuff after a few years have passed. I'm spending way to much time trying to do this while I could just be writing code instead.

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't suppose you'd be able to post your current set of files in a ZIPped attachment would you?

I'm sure that if we give you a pointer as to the reason for even one of the "redefined" errors something will hopefully "click" and you'll see how to fix them all.

If it's proprietary code you don't want to publish then is it possible to cut out a small example that still shows the problem? (though I often find that doing that you stumble on the "obvious" thing you are doing wrong anyway once you've sorted the wood from the trees)

Cliff

PS I keep noticing your sig about the electric car and I'm interested to know more about it - is this a commercially available car or a project you've made yourself? Also is that the car in your avtar? It looks "sporty" for an electric car!!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sgomes wrote:
Well, I'd like to post that all is well but I can't. Frustration is mounting. I really thought I understood what was going on too.... :(

My mental picture of this concept is that I imagine all the files back together as one large file. I ask myself what the compiler/linker needs to know at each point in the code. If it doesn't know about a variable/structure/typdef/prototype etc then I need to tell it. Either by "extern" or actual definition/declaration. Correct me if I'm wrong with this picture.

Anyway, I thought I had accomplished all that. I also added the "#ifndef XYZ_INCLUDED / #define XYZ_INCLUDED / #endif" wrapper around each of my header files.

My situation now is that I get a pile of "identifier xxx redefined" errors with just two of my header files. I'm completely stumped. It really seems as though the compiler is ignoring the wrappers on just those two. Yes I checked the spelling and all that. I've searched all through the code to see if I'm declaring or defining those variables anywhere else (i.e. as if I forgot the "extern" keyword somewhere) but nope it's all clean.

At this point I think it is wise to put everything back in one file and move on. Maybe I can come back to this high level stuff after a few years have passed. I'm spending way to much time trying to do this while I could just be writing code instead.

As clawson said, we can't really help unless we know what you are actually doing. Just cut down the files to enough to show the behaviour and post them, then we'll take a look.

You've obviously made some mistake because, believe me (and several million other C programmers), this stuff works!
[/code]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nothing proprietary here. Just don't look at the code to closely. This is just a raw app to test out some hardware. It doesn't do anything useful....yet. :lol:

Here is the code if a form that compiles under CodeVision. The errors I get are all related to the two files: interupts.h and twi_master.h. The errors are all refering to "identifier XXX redefined." It's the four globals in twi_master.h and the seven globals in interupts.h. Also I get two sets of "identifier XXX redefined" errors for the global in twi_master.h

I don't know how to thank you strangers for even wanting to help a fellow stranger out. Altruism at it's finest. Thank you so much. I've already learned quite a bit!

[code removed]

Go electric!
Happy electric car owner / builder

Last Edited: Fri. Jan 20, 2006 - 01:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh and Cliff,

It's not the exact car in my Avatar. Mine is yellow. :lol: It's a converted Porsche 914. Yes I did the conversion myself. All this code I'm struggling with is going to end up on two on-board computers for display and control of things. Not the controll of the motor itself but other peripherals on the car. It will monitor the controller for the motor but it won't actually do anything beyond that.

The conversion was actually quite trivial even for a guy like me that shouldn't be allowed to hold a hammer. it took me 3 months to strip the gas car and install the electic bits. A blast to drive! And because you will probably ask... I get anywhere from 60 to 80 miles per charge riight now on my single string of 20 6V lead-acid batteries. I only stop at gas stations to put air in my tires. :lol:

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sgomes wrote:
It's a converted Porsche 914.

OMG what a thing to do to a Porsche. I prefer the 2.7L 240bhp engine in my Boxster - though I guess my 27mpg fuel economy isn't quite the same as yours in terms of running costs (I live in a country where petrol is $8/gallon!)

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
(I live in a country where petrol is $8/gallon!)

Ouch! :shock: Are you trying to say us Americans should stop complaining about our outrageous $2.50/gallon??? :lol:

Actually the local 914 community has been quite supportive! Did you know the Ferdinand Porsche's first car was all electric?

http://leo.worldonline.es/jaumepor/angles/porsches/loh_porsche.htm

I'm just trying to get back to his original thoughts! :lol:

..and if you want to see scary fast for a 914.... click on the first and second videos (they have a n orange 914 shown)

http://www.nedra.com/reports/pso04/pso04_photos&movies.html

He's the guy who built the controller for my car! :shock:

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I forgot to add... he has 1000 foot-pounds of torque at the wheels. :shock:

Go electric!
Happy electric car owner / builder

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, I've taken your files and put them into a GCC project in AVR Studio. I had to make a few modifications to clear the compiler errors (mainly interrupt differences between Codevision and GCC).

Oh and twi_master.c contains TWBR = TWI_TWBR; but TWI_TWBR is not defined in any of the files you gave - so I just set it to zero.

Finally I got everything to compile which then got me to the linker errors. The first error it complained about is TWI_State being multiply defined.

The reason for this is obvious. The definition of TWI_State is in TWI_master.h at line 25. In fact in that .h file you have:

unsigned char lastTransOK;      
unsigned char TWI_buf[ TWI_BUFFER_SIZE ];    // Transceiver buffer
unsigned char TWI_msgSize;                   // Number of bytes to be transmitted.
unsigned char TWI_state = TWI_NO_STATE;      // State byte. Default set to TWI_NO_STATE.

I'm afraid that's a no-no. You can't put either functions or actual data definitions into .h files. Anything that effectively generates even a single byte of hex in the output MUST appear in a .c file.

What actually happens is the following. The GCC compiler is told to compile ds3231.c and put it's output into ds3231.o - while it's being compiled it #include's twi_master.h so there'll be a data object called TWI_state generated in ds3231.o.In fact if you are at a command prompt and you use:

\winavr\bin\avr-nm ds3213.o

you'll see all the code and data objects it contains. This include a TWI_state.

Now the compiler, in a separate compilation invoked out of the makefile is told to compile testapp.c and it too #include's twi_master.h - so it too will output to a testapp.o and that will also contain a data object called TWI_state (again avr-nm confirms this).

So it maybe isn't too much of a surprise when the link command invoked out of the makefile to link together all the generated .o files finds a thing called TWI_state in ds3213.o and another one in testapp.o - it can't cope with two copies of the same thing and hence the "multiply defined" error you see.

This all stems from the definition of TWI_state being located in the .h file. You cannot reserve either code or data space in a .h

If instead you moved those lines that define TWI_state and other variables above from twi_master.h and put them into twi_master.c then you'd be OK - you would leave very similar lines in twi_master.h but they'd be declaring the items as "extern" so that anything that #include's twi_master.h will know that TWI_state is an unsigned char but it won't know WHERE it is at compile time. When that code refers to TWI_state when it's compiled the object file will include a reference saying something along the lines of "Oi! Mr Linker you need to put the actual address where TWI_state is located in this position when you link all the .o files together". So the .h would have

extern unsigned char lastTransOK;      
extern unsigned char TWI_buf[ TWI_BUFFER_SIZE ];    // Transceiver buffer
extern unsigned char TWI_msgSize;                   // Number of bytes to be transmitted.
extern unsigned char TWI_state;      // State byte. Default set to TWI_NO_STATE.

and notice how I dropped the initialiser from the TWI_state declaration line in this.

I'm going to mod these lines in your project now and see if that's the only mutiple definition problem or not... I'l be back (as Arnie would say!)

Cliff

Last Edited: Thu. Jan 19, 2006 - 04:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yup, those were the only problem.

It's probably safe to say that if you ever get a linker error about a multiply defined object then have a look at where it is being defined - either you really have got it in two or more C files or, more likely, you have put it in a .h and need to put it into a .c instead and just add an extern reference (or a function prototype) to a .h

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

PS I probably still got it wrong but I was just checking my use of the words declare and define for the above post and came across this:

http://www.cs.cf.ac.uk/Dave/C/no...

which explains the "large program" thing very well I think.

Cliff

PS declarations in .h files, definitions in .c files !

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

WOW! Cliff you've gone over and above the call of duty on this one. Thank you so much. In my defense that header file was copied from Atmel! I was hesitant to edit it. I saw the definition in there but I never imagined it would cause all this trouble. I figured it was a no no from a style standpoint but not from a catastrophic compiler error.

{edited}

Thank you so much. I was at my wit's end.

Go electric!
Happy electric car owner / builder

Last Edited: Thu. Jan 19, 2006 - 09:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

nevermind my last question.... I understand now.... ALL of the variables in headerfiles MUST be preceeded by "extern". Can't have actual memory space being allocated in the header files...got it... wow. Feeling pretty dense over here.

I don't know how to express my gratitude! Thanks again all and especially Cliff

!

Quote:
6164 line(s) compiled
No errors
No warnings

Go electric!
Happy electric car owner / builder