*why* does accessing a static var in another TU not fail compilation?

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Afternoon all,

 

I've got a horribly simply question, that has been bugging me for a long time - I'm familiar with the usage of static functions, to limit visibility of static functions to within a given translation unit and to avoid exposing functions outside of a single source file that do not need to be, and the same principle applied to variables also to limit the usage of global vars, including MISRA (and general best-practice) rules on limiting usage of global variables etc.

 

However, there is a compiler behavior which has always given me a headache, understanding why it behaves this way...

 

Simple example (compiled on Windows using gcc just to prove the point...):

 

An example 'module' in our application, with a static variable and static function.

 

#include "module.h"

static int var = 30;

static void module_add(void){

	printf("Adding 20 to var...\n");
	var += 20;
	printf("Added Var = %d\n", var);
}
#ifndef MODULE_H_
#define MODULE_H_

#include <stdio.h>

static int var;
static void module_add(void);

#endif /* MODULE_H_ */

If I try and call said function from main (or any other translation unit), I expect the compiler to fail

#include "module.h"
#include <stdio.h>

void subtract(void);

int main(void){
    subtract();
    module_add();
}

void subtract(void){
    printf("Var = %d\n", var);
    var -= 5;
    printf("Subtracted Var = %d\n", var);
}

Which it does... exactly as expected due to an undefined reference to module_add()...

undefined reference to `module_add'

If I change this slightly though, so that module_add() is not static, I would expect this to still not compile, because var which is also referenced in main, is also static.

#include "module.h"

static int var = 30;

void module_add(void){

	printf("Adding 20 to var...\n");
	var += 20;
	printf("Added Var = %d\n", var);
}
#ifndef MODULE_H_
#define MODULE_H_

#include <stdio.h>

static int var;
void module_add(void);

#endif /* MODULE_H_ */

But instead, the behavior I get is a successful compilation, and when run I get the following printed out into the command window...

Var = 0
Subtracted Var = -5
Adding 20 to var...
Added Var = 50

 

Now the actual value of var is clearly hidden from main here (but not module.c, as expected), given it's printed as =0 when called from main, then as =50 when called from module_add(), so that is what I would expect... but, the question I've never been able to get out of my mind is this:

 

a) why does this compile, I've always thought this should throw a compiler error, such as below - because var is now completely 'hidden' from main!?

error: 'var' undeclared (first use in this function)

b) does this mean the subtract() function is effectively creating it's own local, automatic variable called 'var', of some type (I assume int, whose width may vary depending on target architecture), and as per rules of C is initializing that variable to 0. If so, again - why no error? I can't just go about using undeclared variables any other time, so why should this work?

 

I appreciate I'm probably missing the point, but it seems like inconsistent behavior between declaring a var or function static. Before I'm shot - I appreciate static has different meanings depending on how it's called, an that a local static variable will persist between function calls etc...

 

OR - is this specific to gcc?

This topic has a solution.
Last Edited: Sun. Sep 13, 2020 - 12:00 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You have a definition in a .h file??

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
You have a definition in a .h file??

 

...no? Where?

 

Oh... int var; in the header is both a declaration, and definition isn't it...

 

If I take it out the header, I get exactly what I expect, compilation failure.

Last Edited: Sat. Sep 12, 2020 - 01:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ah by hang on, that still leaves me somewhat confused...

 

If main.c looks like this

 

#include "module.h"
#include <stdio.h>

void subtract(void);

int main(void){
    subtract();
	module_add();
}

void subtract(void){
	printf("Var = %d\n", var);
	var -= 5;
	printf("Subtracted Var = %d\n", var);
}

and module .c and .h look like this

#include "module.h"

int var = 30;

void module_add(void){
	
	printf("Adding 20 to var...\n");
	var += 20;
	printf("Added Var = %d\n", var);
}
#ifndef MODULE_H_
#define MODULE_H_

#include <stdio.h>

void module_add(void);

#endif /* MODULE_H_ */

Then this will fail compilation due to 

main.c: In function 'subtract':
main.c:12:23: error: 'var' undeclared (first use in this function)
   12 |  printf("Var = %d\n", var);

But this is the same as if I had defined var static in module.c?

 

So, what's the difference between using static or not in this case?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sit down with a nice cup of tea.

 

Write your H files to follow convention.

i.e. declare any extern variables and functions.    define macros, structs, typedefs, ...

but no executable code.

 

When you include the H file,  the compiler knows about the external references.

 

When you link the object files,   the linker reports an error if any references have not been defined.

 

It is unwise (tm) to declare a static in an H file.    (unless you are putting an inline function into the H file).

 

Obviously you must obey the syntax and rules for a programming language.

There is no requirement to follow convention.    It just makes your life easier.

 

David.

Last Edited: Sat. Sep 12, 2020 - 02:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>So, what's the difference

 

Here is your main.c, it has no idea what 'var' is because you have not given any info about it.

 

//main.c

#ifndef MODULE_H_
#define MODULE_H_

#include <stdio.h>

void module_add(void);

#endif /* MODULE_H_ */

 

#include <stdio.h>

void subtract(void);

int main(void){
    subtract();
    module_add();
}

void subtract(void){
    printf("Var = %d\n", var);
    var -= 5;
    printf("Subtracted Var = %d\n", var);
}

 

Check out this answer if you want, it may help, or not-

https://www.avrfreaks.net/commen...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks both, though (unless I'm missing something) I believe there's a subtlety not covered by any of the answers. Are we not talking about a linkage issue here?

 

The bit I think I'm missing, is in the code snippets in my post #4; whether I define var as static or not, I still get a compiler error. Half of this makes complete sense to me; if var is static - of course I will see a compiler error. If I want to make it visible to main, I must declare var extern.

 

But, what about if I don't define it static, OR declare it extern? It fails compilation still - so it's not global? Trying to get my head around external, internal or no linkage - I'm assuming no linkage is the case where neither extern or static keywords are used... 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>Are we not talking about a linkage issue here?

 

Not yet. Your main.c in #4 has not been informed of anything named 'var' so the error is from the compiler, not the linker.

 

 

 

echo "static char var;" > test.c
./avr-gcc -c test.c 
./avr-nm test.o | grep var
00000000 b var  //lowercase b, so is a 'local' symbol

 

echo "char var;" > test.c
./avr-gcc -c test.c 
./avr-nm test.o | grep var
00000001 C var //uppercase C, so is 'global'/extern

 

 

These files have no idea about other c files, and simply saying something exists is enough to satisfy the compiler-

 

echo "extern char var; int main(){ var = 1; }" > main.c
./avr-gcc -c main.c 
./avr-nm main.o | grep var
         U var

 

In code #4, the compiler does not have any info about 'var', so the compiler complains.

 

When you get to link time, then these references to symbols have to be figured out, and if the symbol is not found or is not 'global' then the linker complains.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

>Are we not talking about a linkage issue here?

 

Not yet. Your main.c in #4 has not been informed of anything named 'var' so the error is from the compiler, not the linker.

 

 

Thanks again - that's crystal clear in my mind - the key question for me I think is:

 

  • if the variable is defined as static (with external linkage), or not static (with internal linkage, but not extern) - then what's the difference there?
Last Edited: Sat. Sep 12, 2020 - 05:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>because var which is also referenced in main, is also static.

 

Your original problem in #1, was you ended up with 2 'var' variables, 1 was a local var which module_add was using, and you also created a static var 'var' in main.c when you included module.h ( your declaration turned into a definition). If you generated a map file, and the var's were not optimized away for some reason, you would see the separate var's, one would be in the data section, the other bss.

 

 

Last Edited: Sat. Sep 12, 2020 - 05:12 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

>because var which is also referenced in main, is also static.

 

Your original problem in #1, was you ended up with 2 'var' variables, 1 was a local var which module_add was using, and you also created a static var 'var' in main.c when you included module.h ( your declaration turned into a definition).

 

 

 

Hmm... maybe I'm doing a poor job of explaining my question, or misunderstanding the answer...

 

In post #4, I have no static keyword left in the code - so how is var static? Is there a default rule I'm missing? I've trawled K&R over and over...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jtw_11 wrote:
if the variable is defined as static (with external linkage), or not static (with internal linkage, but not extern) - then what's the difference there?

This is a bit backwards.

When talking about filescope variables as is the case here (as opposed to local variables defined inside a function), without static it has external linkage, it is adding static that changes it to internal linkage.

 

In your original code, in module.c you would have ended up with

static int var;  // from the included header

static int var = 30;  // in the c file itself

This is, somewhat strangely I always thought, allowed, as long as the types match. So you have one var here with internal linkage initialised to 30.

 

In main.c you would have ended up with

static int var; // from the included heaedr

This is another var with internal linkage, initalised to 0, completely unrelated to the other var.

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

 

Yes - I'm onboard with how I ended up with the pair of vars. But, coming back to post 4 - specifically module.c in post 4, whether I add or remove 'static', the compilation fail is the same.

 

So, I understand:

 

  • declaring var as extern in the corresponding header means I can access it in main
  • with static, I shouldn't be able to access it in main - which I can't. Good.
  • without static, OR extern - what does this case mean?

 

#include "module.h"

static int var = 30;

void module_add(void){
	
	printf("Adding 20 to var...\n");
	var += 20;
	printf("Added Var = %d\n", var);
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you meant what does this mean (assuming it isn't inside a function)

 

int var = 30;

 

it means var is a filescope variable with external linkage and so could be accessed from another translation unit (asuming an external declaration extern int var is visible to that other translation unit).

 

I wonder if you are getting confused by the different meanings of static keyword.

 

static applied to filescope variable changes the linkage. All filescope variables are already 'statically allocated'.

static applied to local variable changes the storage (from automatic to statically allocated, scope remains as block scope, it doesn't have any linkage being a local variable)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I *think* it has just clicked; to access a file scope variable from another file (module.c for example), said variable must be declared extern in the accessing file (main.c for example).

Now, there's a statement in K&R that days "typical practice is to simple declare extern in the header, and include the header"... Which is now so obvious, as it avoids having to declare said variable extern in each place the variable needs accessing from.

Effectively, variable is file scope - until such a time it's accessed by an extern declaration elsewhere, effectively converting to to global (or declaring it extern in the header to start with).

So I think you're right Kendo, it's the meaning of static in the context of a global variable I'm confused by!

Edit... No wait - I think the latter part has clicked too. Declaring the global variable static, effectively prevents you accessing it from elsewhere via an extern declaration - as trying to declare an already (static) declared variable throws a compiler error!

Last Edited: Sat. Sep 12, 2020 - 07:32 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jtw_11 wrote:
Edit... No wait - I think the latter part has clicked too. Declaring the global variable static, effectively prevents you accessing it from elsewhere via an extern declaration - as trying to declare an already (static) declared variable throws a compiler error!
Yes.

 

You use it to create variables that are accessible my multiple functions but only in that single file.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jtw_11 wrote:
*why* does accessing a static var in another TU not fail compilation?

 

You based your question on some very strange expectations.

 

There's never been any restrictions on accessing static entities from other translation units. "Accessing" is a very broad term that can cover many different approaches to implementing "access".

 

Declaring a variable (or a function) as `static` simply means that no one will be able to link to that variable directly from other translation units. It gives that variable internal linkage, which means that another variable (or function) with the same name declared in another translation unit will not be associated with a definition made here. That's all there's to it.

 

But that does not in any way preclude other types of implementing access. For example, in one translation unit I can declare

 

static int a = 42;
extern int *const b = &a;

Note that variable `b` has external linkage. So, in another translation unit I can declare 

 

extern int *const b;

void foo()
{
  printf("%d\n", *b); // prints the value of `a`
  *b = 5;             // changes the value of `a`
  printf("%d\n", *b); // prints the new value of `a`
}

and that will immediately provide me with full access to `a` through `*b`. Done.

 

Nothing strange with it. I did not link to `a` directly (`static` prevents me from doing that), but instead I accessed `a` indirectly, through `b`.

 

Once you declared something `static`, it becomes non-linkable from outside world. But you yourself can still provide alternative means for accessing that static entity, e.g. by providing outside world with a function that manipulates the entity (as in your example), or by exposing a pointer to that entity to the outside world (as in my example). Basically, you fully control how this entity will be accessible to the outside world. And if you provide no means for accessing it, it will become truly and fully inaccessible. This decision up to you.

 

That is the purpose of `static`. It is not about blocking all access. It is about handing over the access control to you.

Dessine-moi un mouton

Last Edited: Sat. Sep 12, 2020 - 09:56 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jtw_11 wrote:

without static, OR extern - what does this case mean?

 

It (slightly) depends on the language. In C++ defining a namespace-scope variable "without static or extern" creates a variable with external linkage. In C the end result is pretty much the same - a variable with external linkage, except that it might involve such chiefly C-specific feature as tentative definition.

Dessine-moi un mouton

Last Edited: Sat. Sep 12, 2020 - 10:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Andrey,

Both your answers at the end here were very informative, particularly around my poor usage of the word access - even more so given in my example I am indeed 'accessing' said static through an exposed funxtion.

Clearly pointers are another way to achieve this also... I've also just been reading about tentative definitions as I read your post, so some more reading up to do there...

Many thanks to all whom contributed today