9
\$\begingroup\$
static volatile unsigned char   PORTB   @ 0x06;

This is a line of code in a PIC microcontroller header file. The @ operator is used to store the PORTB value inside the address 0x06, which is a register inside the PIC controller that represents PORTB. Up to this point, I have a clear idea.

This line is declared as a global variable inside a header file (.h). So, from what I came to know about the C language, a "static global variable" is not visible to any other file - or, simply, static global variables / functions cannot be used outside of the current file.

Then, how can this keyword PORTB be visible to my main source file and many other header files which I created manually?

On my main source file, I only added the header file #include pic.h Does this have something to do with my question?

\$\endgroup\$
4
  • 2
    \$\begingroup\$ no problem with the question but wrong SE section i'm afraid \$\endgroup\$
    – gommer
    Jul 8, 2019 at 12:22
  • \$\begingroup\$ static is normally used inside a function to specify that the variable is created once and keeps its value from one execution of a function to the next. a global variable is one created outside any function so that it's visible everywhere. static global doesn't really make sense. \$\endgroup\$
    – Finbarr
    Jul 8, 2019 at 12:22
  • 9
    \$\begingroup\$ @Finbarr Wrong. static globals are visible inside the whole single compilation unit, and are not exported beyond that. They're much like private members of a class in OOP. I.e. every variable that needs to be shared between different functions inside a compilation unit but are not supposed to be visible outside that c.u. should really be static. This also reduces "clobbering" of the program's global namespace. \$\endgroup\$
    – JimmyB
    Jul 8, 2019 at 12:25
  • 3
    \$\begingroup\$ Re "The @ operator is used to store the PORTB value inside the address 0x06". Really? Isn't more like "The @ operator is used to store the variable "PORTB" at the absolute memory address 0x06"? \$\endgroup\$ Jul 9, 2019 at 13:12

6 Answers 6

21
\$\begingroup\$

The keyword 'static' in C has two fundamentally different meanings.

Limiting Scope

In this context, 'static' pairs up with 'extern' to control the scope of a variable or function name. Static causes the variable or function name to be available only within a single compilation unit and only available to code that exists after the declaration/definition within the compilation unit text.

This limitation itself really only means something if and only if you have more than one compilation unit in your project. If you only have one compilation unit, then it still does things but those effects are mostly pointless (unless you like digging into object files to read what the compiler generated.)

As noted, this keyword in this context pairs with the keyword 'extern', which does the opposite -- by making the variable or function name linkable with the same name found in other compilation units. So you can look at 'static' as requiring the variable or name to be found within the current unit of compilation, while 'extern' permits cross-compilation unit linkage.

Static Lifetime

Static lifetime means that the variable exists throughout the duration of the program (however long that is.) When you use 'static' to declare/define a variable within a function, it means that the variable is created sometime prior to its first usage (which means, every time I've experienced it, that the variable is created before main() starts) and isn't destroyed afterwards. Not even when the function's execution is completed and it returns to its caller. And just like static lifetime variables declared outside of functions, they are initialized at the same moment -- before main() starts -- to a semantic zero (if no initialization is provided) or to a specified explicit value, if given.

This is different from 'auto' type function variables, which are created new (or, as-if new) each time the function is entered and then are destroyed (or, as-if they were destroyed) when the function exits.

Unlike the impact of applying 'static' on a variable definition outside of a function, which directly impacts its scope, declaring a function variable (within a function body, obviously) as 'static' has no impact on its scope. The scope is determined by the fact that it was defined within a function body. Static lifetime variables defined within functions have the same scope as other 'auto' variables defined within function bodies -- function scope.

Summary

So the 'static' keyword has different contexts with what amounts to "very different meanings." The reason it was used in two ways, like this, was to avoid using another keyword. (There was a long discussion about it.) It was felt that programmers could tolerate the use and the value of avoiding yet another keyword in the language was more important (than arguments otherwise.)

(All variables declared outside of functions have static lifetime and don't need the keyword 'static' to make that true. So this kind of freed up the keyword to be used there to mean something entirely different: 'visible only in a single compilation unit.' It's a hack, of sorts.)

Specific Note

static volatile unsigned char PORTB @ 0x06;

The word 'static' here should be interpreted to mean that the linker won't attempt to match up multiple occurrences of PORTB that may be found in more than one compilation unit (assuming your code has more than one.)

It uses a special (non-portable) syntax to specify the "location" (or the label's numeric value which is usually an address) of PORTB. So the linker is given the address and doesn't need to find one for it. If you had two compilation units using this line, they'd each wind up pointing to the same place, anyway. So there's no need to label it 'extern', here.

Had they used 'extern' it might pose a problem. The linker would then be able to see (and would attempt to match up) multiple references to PORTB found in multiple compilations. If all of them specify an address like this, and the addresses are NOT the same for some reason [mistake?], then what's it supposed to do? Complain? Or? (Technically, with 'extern' the rule of thumb would be that only ONE compilation unit would specify the value and the others should not.)

It's just easier to label it as 'static', avoiding making the linker worry about conflicts, and simply put the blame for any mistakes for mis-matched addresses upon whomever changed the address to something it shouldn't be.

Either way, the variable is treated as having a 'static lifetime.' (And 'volatile'.)

A declaration is not a definition, but all definitions are declarations

In C, a definition creates an object. It also declares it. But a declaration does not usually (see bullet note below) create an object.

The following are definitions and declarations:

static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }

The following are not definitions, but are only declarations:

extern int b;
extern int f();

Note that the declarations do not create an actual object. They only declare the details about it, which the compiler can then use to help generate correct code and to provide warning and error messages, as appropriate.

  • Above, I say "usually," advisedly. In some cases, a declaration can create an object and is therefore promoted to a definition by the linker (never by the compiler.) So even in this rare case, the C compiler still thinks the declaration is only a declaration. It is the linker phase that makes any necessary promotions of some declaration. Keep this carefully in mind.

    In the above examples, should it turn out there are only declarations for an "extern int b;" in all the linked compilation units, then the linker is charged with the responsibility to create a definition. Be aware that this is a link-time event. The compiler is completely unaware, during compilation. It can only be determined at link-time, if a declaration of this type most be promoted.

    The compiler is aware that "static int a;" cannot be promoted by the linker at link-time, so this actually is a definition at compile-time.

\$\endgroup\$
16
  • 3
    \$\begingroup\$ Great answer, +1! Only one minor point: They could have used extern, and it would be the more proper C way of doing it: Declaring the variable extern in a header file to be inlcuded multiple times in the program and defining it in some non-header file to be compiled and linked in exactly once. After all, PORTB is supposed to be exactly one instance of the variable to which different c.u.'s can refer. So the use of static here is kind-of a shortcut they took to avoid needing another .c file in addition to the header file. \$\endgroup\$
    – JimmyB
    Jul 9, 2019 at 9:21
  • \$\begingroup\$ I would also note that a static variable declared within a function is not changed across function calls which can be useful for functions that need to retain some sort of state information (I have used it specifically for this purpose in the past). \$\endgroup\$ Jul 9, 2019 at 12:47
  • \$\begingroup\$ @Peter I think I said that. But perhaps not as well as you'd have liked? \$\endgroup\$
    – jonk
    Jul 9, 2019 at 13:27
  • \$\begingroup\$ @JimmyB No, they could not have used 'extern', instead, for function variable declarations that behave as 'static' does. 'extern' is already an option for variable declarations (not definitions) within function bodies and serves a different purpose -- providing link-time access to variables defined outside of any function. But it's possible I'm misunderstanding your point, too. \$\endgroup\$
    – jonk
    Jul 9, 2019 at 18:56
  • 1
    \$\begingroup\$ @JimmyB Extern linkage would definitely be possible, though I don't know if it's "more proper." One consideration is that the compiler may be able to emit more optimized code if the information is found in the translation unit. For embedded scenarios, saving cycles on every IO statement can be a big deal. \$\endgroup\$
    – Cort Ammon
    Jul 9, 2019 at 21:24
9
\$\begingroup\$

statics are not visible outside of the current compilation unit, or "translation unit". This is not the same as the same file.

Notice that you include the header file into any source file where you may need the variables declared in the header. This inclusion makes the header file a part of the current translation unit and (an instance of) the variable visible inside it.

\$\endgroup\$
7
  • \$\begingroup\$ Thanks for your reply. "compilation Unit" , Sorry I don't Understand, can you explain that term. Let me ask you one more question, even if we want to use variables and functions written inside another file we must first INCLUDE that file in to our main SOURCE FILE. Then why the keyword "static volatile" in that header file. \$\endgroup\$ Jul 8, 2019 at 12:26
  • \$\begingroup\$ @ElectroVoyager cs.auckland.ac.nz/references/unix/digital/AQTLTBTE/DOCU_015.HTM \$\endgroup\$
    – JimmyB
    Jul 8, 2019 at 12:28
  • 1
    \$\begingroup\$ Pretty in depth discussion at stackoverflow.com/questions/572547/what-does-static-mean-in-c \$\endgroup\$ Jul 8, 2019 at 12:29
  • 3
    \$\begingroup\$ @ElectroVoyager; if you include the same header containing a static declaration in multiple c source files, then each of those files will have a static variable of the same name, but they are not the same variable. \$\endgroup\$ Jul 8, 2019 at 12:51
  • 2
    \$\begingroup\$ From @JimmyB link: Files included by using the #include preprocessor directive become part of the compilation unit. When you include your header file (.h) in a .c file, think of it as inserting the content of the header in the source file, and now, this is your compilation unit. If you declare that static variable or function in a .c file, you can use them only in the same file, which at the end, will be another compilation unit. \$\endgroup\$ Jul 8, 2019 at 13:11
5
\$\begingroup\$

I will try to summarize comments and @JimmyB's answer with an explanatory example:

Suppose this set of files:

static_test.c:

#include <stdio.h>
#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one();

void main(){

    say_hello();
    printf("var is %d\n", var);
    var_add_one();
    printf("now var is %d\n", var);
}

static.h:

static int var=64;
static void say_hello(){
    printf("Hello!!!\n");
};

no_static.h:

int var=64;
void say_hello(){
    printf("Hello!!!\n");
};

static_src.c:

#include <stdio.h>

#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one(){
    var = var + 1;
    printf("Added 1 to var: %d\n", var);
    say_hello();
}

You can compile and run the code using gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test for using the static header or gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test for using the non-static header.

Note that two compilation units are present here: static_src and static_test. When you use the static version of the header (-DUSE_STATIC=1), a version of var and say_hello will be available for each compilation unit, this is, both units can use them, but check that even though var_add_one() function increments its var variable, when the main function prints its var variable, it is still 64:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test                                                                                                                       14:33:12⌚
Hello!!!
var is 64
Added 1 to var: 65
Hello!!!
now var is 64

Now, if you try to compile and run the code, using non-static version (-DUSE_STATIC=0), it will throw a linking error because of duplicated variable definition:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test                                                                                                                       14:35:30⌚
/tmp/ccLBy1s7.o:(.data+0x0): multiple definition of `var'
/tmp/ccV6izKJ.o:(.data+0x0): first defined here
/tmp/ccLBy1s7.o: In function `say_hello':
static_test.c:(.text+0x0): multiple definition of `say_hello'
/tmp/ccV6izKJ.o:static_src.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
zsh: no such file or directory: ./static_test

Hope this could help you clarify this matter.

\$\endgroup\$
4
\$\begingroup\$

#include pic.h roughly means "copy the content of pic.h into the current file". As a result, every file which includes pic.h gets its own local definition of PORTB.

Perhaps you wonder why there is no single global definition of PORTB. The reason is quite simple: you can only define a global variable in one C file, so if you want to use PORTB in multiple files in your project, you would need pic.h with a declaration of PORTB and pic.c with its definition. Letting each C file define its own copy of PORTB makes code building easier, as you don't have to include in your project files you didn't write.

An additional benefit of static variables vs. globals is that you get less naming conflicts. A C file which doesn't use any MCU hardware features (and thus doesn't include pic.h) can use the name PORTB for its own purpose. Not that it's a good idea to do so on purpose, but when you develop e.g. an MCU-agnostic math library, you'd be surprised how easy it is to accidentally reuse a name which is used by one of the MCUs out there.

\$\endgroup\$
2
  • \$\begingroup\$ "you'd be surprised how easy it is to accidentally reuse a name which is used by one of the MCUs out there" - I dare to hope that all math libraries use only lowercase names, and that all MCU environments only use uppercase for register names. \$\endgroup\$
    – vsz
    Jul 9, 2019 at 12:02
  • \$\begingroup\$ @vsz LAPACK for one is full of historical all-caps names. \$\endgroup\$ Jul 9, 2019 at 12:12
3
\$\begingroup\$

There are some good answers already, but I think the cause of the confusion needs to be addressed simply and directly:

The PORTB declaration is not standard C. It is an extension of the C programming language that only works with the PIC compiler. The extension is needed because PICs were not designed to support C.

The use of the static keyword here is confusing because you would never use static that way in normal code. For a global variable, you would use extern in the header, not static. But PORTB is not a normal variable. It's a hack that tells the compiler to use special assembly instructions for register IO. Declaring PORTB static helps trick the compiler into doing the right thing.

When used at file scope, static limits the scope of the variable or function to that file. "File" means the C file and anything copied into it by the preprocessor. When you use #include, you are copying code into your C file. That's why using static in a header makes no sense -- instead of one global variable, each file that #includes the header would get a separate copy of the variable.

Contrary to popular belief, static always means the same thing: static allocation with limited scope. Here's what happens to variables before and after being declared static:

+------------------------+-------------------+--------------------+
| Variable type/location |    Allocation     |       Scope        |
+------------------------+-------------------+--------------------+
| Normal in file         | static            | global             |
| Normal in function     | automatic (stack) | limited (function) |
| Static in file         | static            | limited (file)     |
| Static in function     | static            | limited (function) |
+------------------------+-------------------+--------------------+

What makes it confusing is that the default behavior of variables depends on where they're defined.

\$\endgroup\$
2
\$\begingroup\$

The reason the main file can see the "static" port definition is because of the #include directive. That directive is equivalent to inserting the whole header file into your source code on the same line as the directive itself.

The microchip XC8 compiler treats .c and .h files exactly the same so you can put your variable definitions in either one.

Normaly a header file contains "extern" reference to variables that are defined elsewhere (usually a .c file).

The port variables needed to be specified at specific memory addresses that match the actual hardware. So an actual (non extern) definition needed to exist somewhere.

I can only guess as to why Microchip corporation chose to put the actual definitions in the .h file. A likely guess is that they just wanted one file (.h) instead of 2 (.h and .c) (to make things easier for the user).

But if you put the actual variable definitions in a header file and then include that header into multiple source files then the linker will complain that the variables are defined multiple times.

The solution is to declare the variables as static then each definition is treated as local to that object file and the linker won't complain.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.