Global Variables

I'm working on code that requires the use of large number of globa variables. To restrict access I'm thinking of making the globals read onl in all source files bar the file that writes it.

I would prefer to avoid the use of functions to access these globa variables due to code overhead.

The construct I would like to implement uses two declarations for on variable definition, I know this does not comply with MISRA guidelines, bu can anyone see any real problems with the following code where the variable is written in sa.c but requires read access only i sb.c.

I'm sure that this topic has come up before so i'll apologise in advanc if this is the case.

-------------------------------- sa.h

#ifdef SA_C extern type global_var; #else extern const type global_var; #endif

#ifdef SA_C #undef SA_C #endif

--------------------------------

-------------------------------- sa.c #define SA_C #include sa.h type global_var = init;

--------------------------------

-------------------------------- sb.c #include sb.h #include sa.h

--------------------------------

Reply to
pb1
Loading thread data ...

I'm no compiler expert but I wonder if implementing your plan might result in this problem:

If you declare a label const, some tool chains would place the data in the code (RO) section. Whereas, if you declare it with const absent, the same tool chains would place the data in the data (RW) section. I would *expect* a high quality linker to complain of the duality of the label and fail to produce an output if two modules refer to the same label but with different section attributes. So I think it's more than just compiler semantics - it's also about locating data in memory.

Compiler experts: Am I on track with my answer?

JJS

Reply to
John Speth

One more thing: Of course it's simple enough to test your plan.

JJS

Reply to
John Speth

Yes and no.

Declaring data const in one place and not const in another is (AFAIK) perfectly kosher according to the ANSI C standard, because the C virtual machine is perfectly Von Neumann.

Many processors, particularly smaller processors and DSPs use a Harvard architecture or some bastard variant, and have code and data spaces that are fundamentally different. C compilers for these processors handle the disparity between ANSI C and the processor's peculiarities with varying levels of compliance to the ANSI C standard. Some compilers maintain strict compatibility at the cost of higher execution time, and some compilers throw compatibility out the window in the name of efficiency (or programming ease).

So a strict reading of the ANSI standard says "sure, go ahead!", while real practice with PICs, 8051s, and some DSP chips says "sorry, can't link".

--

Tim Wescott
Wescott Design Services
http://www.wescottdesign.com

Do you need to implement control loops in software?
"Applied Control Theory for Embedded Systems" gives you just what it says.
See details at http://www.wescottdesign.com/actfes/actfes.html
Reply to
Tim Wescott

The "extern" declarations don't place data anywhere, they merely tell the compiler to reference it. As long as the actual definition is not "const", the variable will go in read-write memory.

It is legal to cast a non-const variable to a const (but not vice versa, obviously). However, there are some compilers (older versions of ImageCraft's AVR compiler springs to mind - their latest version is different) which are non-conforming in that they use "const" to indicate that data is in flash, and use the AVR's flash-access opcodes to read "const" data. Thus a cast to a const will *not* work there. That's because of non-standard "const" behaviour in those compilers, and their later versions have standard "const" behaviour - but it is worth checking the compilers you use to be sure.

However, legal or not, such hacks are ugly. They are an indication of badly structured or badly modularised code, or a failure to define and follow rules for properly accessing resources across modules. It would be better to think about how the code is to be structured, whether you can define and apply access rules by other means, an perhaps whether you really need all these global variables.

A golden rule about include files is that every time the file is included within a project, it generates the same code (or declarations). If this is not the case, you should have an incredibly good reason for it. A second golden rule is that all #include's should be at the top of the header or C file they are used in, and there should be nothing except comments (and the old "#ifndef __header_h\n #define __header_h" lines at the start of every header) before the #include's. A third rule is that it should not matter which order #include's are given. This hack breaks all these rules.

Global variables are not in themselves a bad thing (some people will tell you otherwise - these are mostly people who live in a world where processors are big, memory is cheap, and pointer access is small and fast). But uncontrolled access to globals can lead to disaster - as can any uncontrolled access to shared resources. You should know what code accesses shared resources at different times, so that you can avoid conflicts - with too many globals, it's hard to keep track.

Access functions can be costly in terms of time and space overhead. Sometimes it is worth it, sometimes not. It is worth noting that it is often possible to use "static inline" access functions - these typically cook down to zero overhead. You can even use code such as :

static inline int getGlobalVar(void) { extern int globalVar; return globalVar; }

in a header to give you a zero overhead, read-only access to "globalVar", while the name "globalVar" itself is hidden from other modules, and thus cannot be accidentally written. It feels somewhat "wrong" to have "extern" within a function, but at least it is in the header file where it belongs. You also retain the error-checking power you get from the defining module #include'ing its own header.

Reply to
David Brown

I do find that the construct

[inside foo.h] #ifdef MAIN #define EXTERN #else #define EXTERN extern #endif

EXTERN unsigned char bar;

[inside foo.c] #define MAIN #include "foo.h" [inside others.c] #include "foo.h"

helps to keep externs under control, though it breaks the rule above.

--
Rich Webb     Norfolk, VA
Reply to
Rich Webb

That's a construct I've seen many places - in my not so humble opinion, it's horrible!

C is, in many ways, a terrible language. It is far too flexible about what it accepts - you need to enforce clear and consistent rules if you are going to write clear and consistent programming.

One of the fundamentals about modularised programming is that you have a clear separation between interface and implementation. If you have a module "module", then "module.h" is the interface, and "module.c" is the implementation.

Ideally, "module.h" contains only declarations (and appropriate comments, of course) - it tells users of the module what is offered by the module, and how to access its functions, data, and structures. Unless you are using "whole program" compilation, it's often unavoidable to have a certain amount of code in the header (as either static inline functions, or equivalent macros), but ideally it contains only things like typedefs and extern declarations.

In "module.c", you have the implementation. Everything that is only used locally in "module.c" is declared "static" in its definition - everything that is exported is defined without "static", and has a matching "extern" in the header. The compiler will check that your externs match up properly as you include "module.h" at the start of "module.c". Even better with gcc, "-Wmissing-declarations" will flag any mismatches, such as a non-static definition without a matching "extern".

The use of the "EXTERN" hack will give the same compiled code in the end

- but at the cost of breaking modularity, breaking consistency with extern functions (in a function declaration in a header, the "extern" is technically redundant - but it is important as a statement of the intent of the programmer), introducing unnecessary "pretend keywords", loosing consistency when using initialised variables, and failing to have the actual implementation of the variables in an appropriate place in the module. Of course, it does save you typing "unsigned char bar;" in "module.c" as well as "module.h" - but if you are having trouble doing that, you're probably in the wrong profession.

That's my 2 øre, anyway.

mvh.,

David

Reply to
David Brown

Sorry to burst your bubble, but that's not possible in a (remotely) well-behaved C program. You would be invoking undefined behaviour by violating C99 6.2.7p2 (or equivalent clauses in whatever language definintion you're based on).

MISRA set aside, it's plain and simply invalid C. It may work if you're rather lucky. Then again, maybe it won't. And if it doesn't, you'll have nobody but yourself to blame.

You'll be toast if you try that on a platform where 'const' data ends up in an actually separate memory space. C allows such things, your code assumes it doesn't.

Reply to
Hans-Bernhard Bröker

At least as of C99, it's not. It violates 6.2.7p2, punishable by undefined behaviour.

No, it's not. The C virtual machine, if anything, is a Harvard architecture. Data and function pointers live in entirely separate worlds.

There is no such disparity to be handled.

The only issue here with actual Harvard architectures, particularly embedded ones, is with the quality-of-implementation for const variables on RAM-starved, not-strictly-Harvard machines. That triggers a desire to leave consts in CODE-addressed-as-data, rather than copying them to RAM along with the non-const variables. So either generic pointers get bogged down by extra machinery for CODE and RAM addressing by the same pointer, or the language has to be extended.

Reply to
Hans-Bernhard Bröker

Instead of exporting it as const, you can just use a header file macro that will turn it into an R-value. Something like:

a.c:

unsigned g_amount_cores_;

a.h:

extern unsigned g_amount_cores_; #define g_amount_cores ((unsigned)g_amount_cores_)

Reply to
Tomás Ó hÉilidhe

Ah well, we'll have to agree to disagree, then. I find that it's a handy construct, if not, as you correctly point out, quite a pure one.

--
Rich Webb     Norfolk, VA
Reply to
Rich Webb

The construct you show is both cumbersome and non-portable. You have to maintain two files in step, and "const" in some compilers (including mspgcc) is a directive to place the object in ROM (I wish they hadn't done that).

A better way to achieve such an end is something like

#ifdef FUNC #define SCOPE #define ASSIGN(x) =(x) #else ##define SCOPE extern #define ASSIGN(x) #endif ...

SCOPE UINT16 bzz ASSIGN(3);

Which means it's all in one file, and no double declarations to maintain.

If your compiler's implementation of "const" does what you want it to do, you could incorporate that in a scheme such as above. But a better way might be to do the lot through macros. A bigger danger than a deliberate write is the inadvertent assignation:

if( a=b) ...

So create a set of macros

#ifdef FUNC #define WRITE(x,v) ((x)=(v)) #else #define WRITE(x,v) #endif

#define READ(x) (x) #define COMPARE(x,y) ((x)==(y)) etc.

JS

Reply to
JSprocket

First of all, I strongly advice you to choose another design that does not require all those global vars.

But If you need them your solution, as already said, can lead to linking problems. I suggest to use an access macro to enforce the read-only behaviour. In the sa.c file you can declare your global:

type global_var = init;

In you sa.h file you declare a macro like this: #define GLOBAL_VAR { extern type global_var; (global_var); }

So if in your read-only code you try to access global_var without the accessor macro you will get a warning from the compiler.

If you have more than one type and variable you can pass them as macro arguments.

--
  _|/ Francesco Sacchi - Develer S.r.l., R&D dept.
   |\ http://www.develer.com/ - http://www.bertos.org
Reply to
Francesco Sacchi

Another unrepentant sinner that refuses to convert to the One True C Programming Style (my style, that is) :-)

Reply to
David Brown

But doesn't a pointer to const still have to be able to point to non-const data? While something declared const may end up in a different section of memory code that addresses an arbitrary const (i.e. pointer) needs to be able to access data stored in a read-write section.

Peter

Reply to
Peter Dickerson

Generally I agree with you, that those kinds of constructs don't provide a good separation between interface and implementation.

My question is how to avoid those constructs for global initialized structures. The following IMHO code has the big disadvantage, that the structure declaration and initialization are defined at different places, this is error-prone and complicates documentation:

[file "globals.h"] typedef struct { int x; int y; } ConfigRecord; extern ConfigRecord Cfg; [file "globals.c"] #include "globals.h" ConfigRecord Cfg = { 1, /* This should be x */ 2, /* This hopefully is y */ }

I do prefer the following construct, although it is not very clean in the sense above:

[file "globals.h"] typedef struct { int x; int y; } ConfigRecord; #ifdef _GLOBALS_C_ ConfigRecord Cfg = { 1, /* This should be x */ 2, /* This hopefully is y */ } #else extern ConfigRec Cfg; #endif [file "globals.c"] #define _GLOBALS_C_ #include "globals.h"

Do you have a better idea ? Regards, Rolf.

Reply to
Rolf Schroedter

(snip)

Eh, thanks for the update. I _thought_ I knew my way around that standard!

--

Tim Wescott
Wescott Design Services
http://www.wescottdesign.com

Do you need to implement control loops in software?
"Applied Control Theory for Embedded Systems" gives you just what it says.
See details at http://www.wescottdesign.com/actfes/actfes.html
Reply to
Tim Wescott

Hi David,

David Brown wrote: ...

Any ideas of persuading OhSoVModelMISRA-C automotive security systems company to this? I was considered picky there, because of these header rules; mentioning, that 'hey, you probably have a huge mess in the code if you really *need* smart headers included in proper order' was treated as a treason. I quit, but... just in case I am in similar position again- what should I do?

Regards,

M.W.

Reply to
Marcin Wolcendorf

One problem you might run into is that the compiler isn't required to allocate storage for a const object if its address isn't used.

--
John W. Temples, III
Reply to
John Temples

Would you accept a pointer indirection overhead? You could define and init the global data first, and then set a "pointer to const" to point at the data. Make all global variable access through the const pointer. This also has the advantage of creating a sort of namespace for the global variables. It would look something like:

gvar.h ------------------------------------------------------------------ struct gvar /* Declare global variables here. */ { int a; int b; };

/* Access all global variables through this pointer. */ extern const struct gvar * G;

/* Initializes global variables and G. */ extern void initGlobals(void);

-------------------------------------------------------------------------

init.c ------------------------------------------------------------------ #include "gvar.h"

/* Space where global variables live. Only visible in this file. */ static struct gvar GvarSpace;

const struct gvar *G; /* Read-only access to global variables. */

void initGlobals(void) { GvarSpace.a = 1; GvarSpace.b = 2; G = &GvarSpace; /* Picky compilers might need a cast. */ }

-------------------------------------------------------------------------

main.c ------------------------------------------------------------------ #include "gvar.h"

int doStuff(void) { return G->a + G->b; }

int main() { initGlobals();

G->a = 4; /* Error, assignment to read-only member. */

return doStuff(); }

-------------------------------------------------------------------------

Reply to
Bob Lied

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.