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.)