Global variable modifiable by some translation units but not others

S

Spiros Bousbouras

Let's say I have a global variable int var which I want
to be known to translation units T1 and T2, I want T1
to be able to read its value and modify it and T2 to be
able to read its value but not modify it. Would it work
if I declare it inside T1 as "extern int var" and inside T2
as "extern const int var" ?
 
D

David T. Ashley

Spiros Bousbouras said:
Let's say I have a global variable int var which I want
to be known to translation units T1 and T2, I want T1
to be able to read its value and modify it and T2 to be
able to read its value but not modify it. Would it work
if I declare it inside T1 as "extern int var" and inside T2
as "extern const int var" ?

This may work with most development tools.

However, I wouldn't do it in quite that way.

There are two downsides of declaring something in multiple locations:

a)Extra work when something changes.

b)The risk of not changing all related items.

Linkers in general aren't smart enough to figure out that:

int c=0;

and

extern long c;

are inconsistent when they appear in different translation units (i.e.
changed one place but not the other), and bizarre results can be obtained at
runtime.

In your scenario, I would look at two alternate ways of doing it.

Method 1: Shared Header File

T1.C
----
#define MODULE_T1

#include "t1.h"

T2.C
----
#define MODULE_T2

#include "t1.h"

T1.H
----
#ifndef (T1_H_INCLUDED)
#define T1_H_INCLUDED

#ifdef (MODULE_T1)
#define DECMOD_T1
#else
#define DECMOD_T1 extern
#endif

DECMOD_T1
#ifndef (MODULE_T1)
const
#endif
int shared_var;

#endif

Method 2: Accessor Functions to T1-Static Variable

No further explanation needed for this method.

Dave.
 
S

Spiros Bousbouras

David said:
This may work with most development tools.

However, I wouldn't do it in quite that way.

There are two downsides of declaring something in multiple locations:

a)Extra work when something changes.

b)The risk of not changing all related items.

Linkers in general aren't smart enough to figure out that:

int c=0;

and

extern long c;

are inconsistent when they appear in different translation units (i.e.
changed one place but not the other), and bizarre results can be obtained at
runtime.

In your scenario, I would look at two alternate ways of doing it.

Method 1: Shared Header File

T1.C
----
#define MODULE_T1

#include "t1.h"

T2.C
----
#define MODULE_T2

#include "t1.h"

T1.H
----
#ifndef (T1_H_INCLUDED)
#define T1_H_INCLUDED

#ifdef (MODULE_T1)
#define DECMOD_T1
#else
#define DECMOD_T1 extern
#endif

DECMOD_T1
#ifndef (MODULE_T1)
const
#endif
int shared_var;

#endif

Why not simply write

#ifndef (T1_H_INCLUDED)
#define T1_H_INCLUDED

#ifdef (MODULE_T1)
int shared_var;
#else
extern const int shared_var;
#endif

#endif

It's certainly more readable.
Method 2: Accessor Functions to T1-Static Variable

No further explanation needed for this method.

Actually I would appreciate an example for this one too.
 
R

Richard

Spiros Bousbouras said:
Let's say I have a global variable int var which I want
to be known to translation units T1 and T2, I want T1
to be able to read its value and modify it and T2 to be
able to read its value but not modify it. Would it work
if I declare it inside T1 as "extern int var" and inside T2
as "extern const int var" ?

Any easy way is to not declare it at all in T2 and only give access via a "get"
function declared in T1. Or?
 
D

David T. Ashley

Spiros Bousbouras said:
Why not simply write

#ifndef (T1_H_INCLUDED)
#define T1_H_INCLUDED

#ifdef (MODULE_T1)
int shared_var;
#else
extern const int shared_var;
#endif

#endif

It's certainly more readable.

The hypothetical risk is that someone would accidentally do this:

#ifdef (MODULE_T1)
long shared_var;
#else
extern const int shared_var;
#endif

leading to some spectacular undefined behavior at runtime, as T1 will have
different ideas about shared_var than T2.

Repeating the type and/or the variable name makes this class of mistake
possible.

I agree with you about readability ... and one can certainly carry any idea
too far.

Repeating ANYTHING in a way where there can be a modification accident is
unwise.
 
D

David T. Ashley

Spiros Bousbouras said:
Actually I would appreciate an example for this one too.

T1.H
----
extern int vomit_the_variable(void);
/* Note that I'm breaking my own rules here for brevity.
** Ideally this should be done so it serves both as
** an interface definition for T2 and a prototype to
** check linkage for T1. See my earlier post using
** DECMOD_...
*/


T1.C
-----
static int shared_variable;

int vomit_the_variable(void)
{
return(shared_variable);
}

P.S.--The other poster suggested this style, too.
 
C

Chris Torek

This may work with most development tools.
[omitted: preprocessor tricks for avoiding various errors]

One possible problem here is that a compiler is free to assume
that all "const" objects are stored in a read-only segment, which
might for instance be based off the $24 register, while all
non-"const" objects are stored in a read/write area based off
the $25 register. Then:

/* foo.c */
extern const int var;
int retrieve(void) { return var; }

would compile to something like:

retrieve:
ld 400($24), $v0
ret

while:

/* bar.c */
int var = 42;
void set(int newval) { var = newval; }

would compile instead to:

set:
st $a0, 400($25)
ret

The linker makes sure to put the same offset (here, 400) into both
sections of code. Unfortunately, $24 points to 0x0001004080002000
(the pages where read-only data live; these are shared between all
running instances of the program), while $25 points to 0x000700408ca04000
(where read/write data for this particular instance of the program
live). So calling set() never changes the value returned from
retrieve(), which is not even 42 initially.

Practically speaking, this problem is rare. More commonly (but
equally surprising), programs that use a "small data segment" (on
systems that support such) misbehave if an array size is specified
differently in one module from another:

/* foo.c */
extern int arr[1];
... code using arr ...

/* bar.c */
int arr[400];

When compiling foo.c, the compiler sees that the array "arr" is
small enough to place it in .sdata; but when compiling bar.c, it
sees that the array is large enough that it must go in .data. (The
solution, at least in C, is to omit the size in foo.c -- this must
work no matter whether bar.c places the array in .data or .sdata,
so the implementation may have to "work hard" to get this right.)
 
K

Kenneth Brody

:
[...]
Linkers in general aren't smart enough to figure out that:

int c=0;

and

extern long c;

are inconsistent when they appear in different translation units (i.e.
changed one place but not the other), and bizarre results can be obtained at
runtime.
[...]

FYI -

I have used systems where the size of the variables is checked at
link time. So, if sizeof(int)!=sizeof(long), you would get an error
at link time.

However, this doesn't help with something like:

long c = 0;
and
extern void *c;

when sizeof(long)==sizeof(void *).

But you are probably correct that linkers "generally" aren't smart
enough to do this, as many platforms I use don't report such things.

--
+-------------------------+--------------------+-----------------------+
| Kenneth J. Brody | www.hvcomputer.com | #include |
| kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------+
Don't e-mail me at: <mailto:[email protected]>
 
K

Kenneth Brody

David T. Ashley said:
The hypothetical risk is that someone would accidentally do this:

#ifdef (MODULE_T1)
long shared_var;
#else
extern const int shared_var;
#endif

leading to some spectacular undefined behavior at runtime, as T1 will have
different ideas about shared_var than T2.
[...]

#ifdef MODULE_T1
#define T1_EXTCONST /* "T1_" prefix to allow "T2_" and so on. */
#else
#define T1_EXTCONST extern const
#endif

T1_EXTCONST int shared_var;
T1_EXTCONST void *shared_ptr;

--
+-------------------------+--------------------+-----------------------+
| Kenneth J. Brody | www.hvcomputer.com | #include |
| kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------+
Don't e-mail me at: <mailto:[email protected]>
 
D

David T. Ashley

Chris Torek said:
This may work with most development tools.
[omitted: preprocessor tricks for avoiding various errors]

One possible problem here is that a compiler is free to assume
that all "const" objects are stored in a read-only segment, which
might for instance be based off the $24 register, while all
non-"const" objects are stored in a read/write area based off
the $25 register. Then:

Agreed.

In the systems I use, the const objects in FLASH and non-near RAM variables
both require 16-bit addresses, and the compiler won't distinguish them (i.e.
the machine instructions will be compatible with both).

But, in the general case, this ain't true.

So I guess we're back to the accessor function idea.
More commonly (but
equally surprising), programs that use a "small data segment" (on
systems that support such) misbehave if an array size is specified
differently in one module from another:

/* foo.c */
extern int arr[1];
... code using arr ...

/* bar.c */
int arr[400];


In the systems I use, I could also see a compiler only using 8 bits of the
pointer offset math because it reasons that the array is < 256 bytes so
nobody would dare index beyond the end.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top