[Icc-avr] Error message
David Brown
david_brown at hotpop.com
Wed Sep 5 14:22:51 PDT 2007
Robertson, Derek wrote:
> Ah... I get it...
>
> It's like a "Only do this once" flag to the compiler.
>
> So, first time round __AABB_H__ isn't defined, so define it and do
> <something>. The next time the header is 'called', the compiler gets
> to this bit, __AABB_H__ has been defined so you skip <something>.
>
That's what the construct means, but you have to be careful how you use
it if you want to write maintainable and understandable code. Remember
that the C preprocessor is a very flexible hack-job to fix deficiencies
in the C language, and it's easy to get carried away.
> I've seen this construct a lot, but hadn't really understood
> it or questioned it.
>
> So you could do something like....
>
> #ifndef INIT
> #define INIT //
> unsigned char myVar = 0;
> #endif
>
> void Set_myVar(unsigned char);
> unsigned char Get_myVar(void);
>
> Then by including the header in several C files you make the function
> proto-types 'public', but myVar only get's declared and initialised
> once. Would I be right in saying that you would need to compile the the
> C file to Object before compiling your project for myVar to be private
> to the correct module?
>
Nope, it is going to turn out wrong - remember that all #define'd
symbols are cleared at the beginning of each object file's compilation,
so each C file with this header will get its own "unsigned char myVar = 0;".
>
> Would doing this in your C file
>
> #define C_FILE_NAME
>
> and this in your header file
>
> #ifdef C_FILE_NAME
> unsigned char myVar = 0;
> #endif
>
> void Set_myVar(unsigned char);
> unsigned char Get_myVar(void);
>
> Not achieve the same result but force the correct scope, or am I still
> not getting this?
>
> Derek
I've seen many suggested ways to handle public and private symbols in C,
and many ways to try to get program structure to work sensibly. There
is one way that works far better than anything else I've seen, by
following the models used by real structured languages such as Pascal or
Modula 2:
If you have a module "module.c" that contains some resources used by
other modules then you want something like this:
// module.h
#ifndef module_h__
typedef unsigned char dataType; // Global typedefs included here
extern dataType globalData; // Externally accessible data
extern void doSomething(int x); // Externally accessible func.
#endif
// module.c
#include "module.h" // Essential for consistency
dataType globalData; // Definition (with or without
// initialisation, as needed)
void doSomthing(int x) { ... } // Function definition
static int localData;
static int localFunction(int y);
So every module has an interface header file which describes the types,
data and functions exported by the module. It also contains comments
describing what the module and its functions do - anyone wanting to use
the module need only read through the header file. The C file contains
the implementation, and describes *how* the header's data and functions
work.
The point of using the #ifndef module_h__ ... #enddef clause is that the
header files can be included multiple times. For example, if you have a
module "time" that handles time information, your "logging" header might
#include "time.h" because it needs typedefs from "time.h". Your
"capture" module might #include both "time.h" and "logging.h" since it
needs functions declared in both header files - the #ifndef trick makes
this safe.
It is important for "module.c" to #include "module.h" as a guarantee of
consistency - data and functions are declared "extern" in the header and
defined in the module itself - any mistakes will be caught at compile time.
A key point is that all data and functions are either declared "extern"
in the module's header, and are therefore globally accessible to any
module #include'ing the header, or they are declared "static" and are
therefore private to the module's implementation. It's good for
structuring, it's good for modular programming, it's good for making the
code clearer, and it lets the compiler generate better code (if the
compiler can do inter-procedural optimisations).
As an aside, hiding a simple global variable with two global "Set" and
"Get" functions leads to double the name-space pollution, a messier
syntax, unnecessary extra source code, and very inefficient object code,
while providing approximately zero benefit in terms of data
encapsulation or modularisation. If you want to actually implement
something useful in the Set and Get code, such as range checking, or
resource locking, then it makes sense. Don't add such access functions
"just in case" - figure out what data is going to need purposeful access
functions, and use them as needed. If a program re-structure means that
previously simple variables need access, then you can easily add them later.
mvh.,
David
More information about the Icc-avr
mailing list