Hi,
I am working on an application, which will run embedded without an OS.
The app is build up out of a couple of well defined parts. At first I
wanted to keep those parts seperated and use opaque data types to
transfer information in between them.
At some stage I was stuck and needed to make a variable global, and I
also needed to make the struct declaration public to some other parts.
Looking through the code I found out that lots of functions, which were
intended to alter the data structure would contain only one or two
lines. So I wrote macros to speed things up.
Looking through the assembly I discovered that the compiler would leave
some function calls completely out because of the optimisation of the
code including the macro.
So now for my real question, what is your opinion on using global
variables/structures and macro's to access them in this sort of environment?
I normally try to avoid global variables as much as possible, but in
this case I see somereal advantages esspecially over the opaque data
approach.
Mark
I use the phrase "non local variables" (my invention) to refer to
variables defined at file scope, regardless of whether they have
internal or external linkage.
The biggest difficulties caused by the use of this type of variable
are, in my opinion, these two:
1. Accidental modification via name duplication.
2. Difficult to understand code due to such variables being modified
"behind the scenes" of code one is reading.
We have come up with rules for use of such objects in coding projects,
especially but not limited to those involving more than one
programmer.
Truly global variables are absolutely forbidden. Values which must be
used throughout a program need to belong to some specific code. If
the values must be read by a lot of different code, so much that "get"
calls become a burden, put them all in a structure and pass around a
const qualified pointer to it to functions that need the values.
If you have program-wide data that must be changed very often and from
all over the code, redesign right now.
The next step is to minimize the accessibility of all non-locals.
Modularize your code. When a variable needs to be shared between a
few related functions that fit comfortably in a single source file,
define it there with the static keyword.
Otherwise break the code into functional modules (example, file i/o,
printing, spell checking, whatever). Each module goes into its own
directory. Non-locals needed by various functions in different source
files within the modules is prefixed with a module specific prefix to
and declared in a header located in the module's directory, not
allowed to be included by source files outside the directory.
The module-specific prefix prevents global name provision, and
simulates multiple external namespaces such as the C++ namespace
feature provides. So you can have PR_lines_per_page in your print
module that can't possible clash with anything defined in your file
i/o module, where everything starts with FIO_. This is useful for
functions with external linkage as well.
Finally the important restriction on non-local variables. With a few
trivial exceptions, we require the following:
If a function accesses the value of a non-local variable, that value
will remain invariant for each execution of that function other than
for visible modifications in that function.
This last one has saved a lot of grief. It initially came about
because of a very poorly coded state machine implementation, like
this:
enum state { STATE0, STATE1, STATE2, STATE3 } the_state;
void some_func(void)
{
if (STATE0 == state)
func_1();
if (STATE1 == state)
func_2();
if (STATE2 == state)
func_3();
if (STATE3 == state)
func_4();
}
When questioned in a code inspection as to why either a switch
statement or an if..else if...else if...else structure was not used,
the author revealed that some (but not all) of the called functions
might modify the state variable. In some cases, to more than one
possible value. So a single call of the function could result in
calling at least one, but sometimes two or three of the different
functions.
Hence the insistence on visible changes to non-locals if they changed
during the execution of a function.
Even something like this:
switch (state)
{
case STATE0:
func_1();
func_other();
func_yes_another();
break;
/* etc. */
}
....can be made much more readable and maintainable by changing the
called functions to return a new state to the caller...
switch (state)
{
case STATE0:
func_1();
func_other();
state = func_yet_another();
break;
/* etc. */
}
If the day ever comes that I need to understand and modify this
function, written by somebody else, or even by me years later, I can
see at a glance which functions might actually decide on a state
change and which cannot.
As for the trivial exception, something to allow for debugging like
this:
void change_state(state_type new_state)
{
#if DEBUGGING
printf("state change from %d to %d\n", state, new_state);
#endif
state = new_state;
}
Now:
switch (state)
{
case STATE0:
func_1();
func_other();
change_state(func_yet_another());
break;
/* etc. */
}
....is considered to meet the requirements.
--
Jack Klein
Home:
http://JK-Technology.Com
FAQs for
comp.lang.c
http://www.eskimo.com/~scs/C-faq/top.html
comp.lang.c++
http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++ ftp://snurse-l.org/pub/acllc-c++/faq