Is this definition of "static" variables incorrect, misleading, or neither?

Viewed 170

According to https://www.learn-c.org/en/Static:

By default, variables are local to the scope in which they are defined. Variables can be declared as static to increase their scope up to file containing them. As a result, these variables can be accessed anywhere inside a file.

Two code examples are provided.
First, an example where local variable int count is removed from memory after the function runner() returns:

#include<stdio.h>
int runner() {
    int count = 0;
    count++;
    return count;
}

int main()
{
    printf("%d ", runner());
    printf("%d ", runner());
    return 0;
}

The output is: 1 1.

The next example given makes count static:

#include<stdio.h>
int runner()
{
    static int count = 0;
    count++;
    return count;
}

int main()
{
    printf("%d ", runner());
    printf("%d ", runner());
    return 0;
}

The output this time is: 1 2.

count increased to 2 in this example because it wasn't removed from memory at the return of runner().
...BUT, that doesn't seem to relate to the statement at the beginning of the page about static variables being accessed anywhere inside a file. The two examples only show that static allowed count to remain in memory across multiple calls to runner() (and that it is not set to 0 each call). They do not show whether or not count can be accessed "anywhere inside a file", because main() simply prints whatever runner() returns.

To illustrate my point, I made an example showing that static does not make count() accessible anywhere inside a file:

#include<stdio.h>
void runner()
{
    static int count = 0;
    count++;
    return;
}

int main()
{
    runner();
    printf("%d ", count);
    runner();
    printf("%d ", count);
    return 0;
}

The output is:

prog.c: In function 'main':
prog.c:12:23: error: 'count' undeclared (first use in this function)
         printf("%d ", count);
                       ^
prog.c:12:23: note: each undeclared identifier is reported only once for each function it appears in

That is what I expected.

I made another example, this one uses x which can be accessed by both runner() and main():

#include<stdio.h>
int x = 0;
void runner()
{
    static int count = 0;
    count++;
    x = count;
    return;
}

int main()
{
    runner();
    printf("%d ", x);
    runner();
    printf("%d ", x);
    return 0;
}

The output is: 1 2.

Is the quote at the top of this question incorrect, misleading, or neither? Am I misunderstanding a semantics issue?

4 Answers

You are correct, and the quote is wrong. Declaring a variable in a function block static increases its lifetime, not its scope. (Scope is the portion of code where you can use a name.)

I can't really imagine a correct way of interpreting "accessed anywhere inside a file". If you have a pointer to a variable of this sort, it's valid to dereference that pointer in other functions, but that's true of all functions, not just the ones in the same file.

Probably time to stop using that site.

That tutorial is wildly confused and needs to be deleted from the Internet.


C has two different, but related terms: scope and storage duration. Scope specifies where a variable can be accessed through its name. Storage duration specifies for how long a variable will retain its value.

In your 2 first examples, you do not change the scope of the variable at all, you change its storage duration.

The scope remains the very same: the variable has local scope to the function int runner() and cannot be accessed by its name outside that function. The keyword static does not affect scope.

However, you change the storage duration. All static variables have (unsurprisingly) static storage duration, which means they will keep their contents valid throughout the whole execution of the program. This is why the function remembers the value when called a second time.

And finally, to address further misconceptions of the tutorial, there is another term called linkage, which is also related to scope. Variables with internal linkage are definitely just accessible in the .c file where they are declared (and in all included .h files from that .c file).

Whereas variables with external linkage are explicitly accessible all over the program, using the keyword extern. You cannot combine internal and external linkage - and all variables declared as static will get internal linkage. Meaning you won't be able to access them from other .c files.

By declaring a variable static, you give it static storage duration and internal linkage.

Functions follow the same rules for scope and linkage as variables, except storage duration isn't meaningful to functions.

TL;DR - A variable with static storage lives throughout the entire execution of the program and any access made to it would be valid. Whether it can be accessed from any part or not, depends on the scope of the definition.

So, to answer the title: I'd agree, it's misleading but at the same time, I'll add a little more context to elaborate on the actual message that was attempted to be communicated.


You already had your answer, just to add my two cents (using authoritative words from the standard):

static is a storage class specifier, it helps determining the storage duration.

Quoting C11, chapter §6.2.4

An object has a storage duration that determines its lifetime. [....]

Then,

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address,33) and retains its last-stored value throughout its lifetime. [...]

So, for variables with static storage duration,

An object whose identifier is declared without the storage-class specifier _Thread_local, and either with external or internal linkage or with the storage-class specifier static, has static storage duration. Its lifetime is the entire execution of the program and its stored value is initialized only once, prior to program startup.


'nuff talk, show me teh codez:

So, to compare, let's see the snippets:

  • Snippet one: Invalid Code
#include<stdio.h>
char *func()
{
    char arr [ ] = "Hello!";
    return arr;
}

int main()
{        
    printf("%s ", func()); // you're accessing `arr`, some or other way!!
    return 0;
}

Here, the returned pointer points to invalid memory, as with the end of the function call, the automatic local variable arr is destroyed (lifetime expired), and the returned address points to invalid memory now.

So, even if you have a way to access arr, that access is not valid anymore.

  • Snippet 2: valid Code
#include<stdio.h>
char *func()
{
    static char arr [ ] = "Hello!";
    return arr;
}

int main()
{        
    printf("%s ", func()); // you're accessing `arr`, some or other way!!
    return 0;
}

this is a perfectly valid code, as it makes arr "accessible" outside the scope of it's definition, because of the static storage duration.

The quote is confusing scope and lifetime.

The scope of a variable describes where a variable can be accessed by name. The lifetime of a variable, or more accurately an object, describes when the object is valid. For a non-static local variable, both its scope and its lifetime is the block it is defined in.

In the case of a static as in your example, its scope is still the enclosing block but its lifetime is that of the whole program. This means you can return the address of a static variable and dereferencing that address is valid. For example:

#include<stdio.h>
int *runner()
{
    static int count = 0;
    count++;
    return &count;    // ok, count has full program lifetime
}

int main()
{
    int *p = runner();
    printf("%d ", *p);
    p = runner();
    printf("%d ", *p);
    return 0;
}

Output:

1 2

If you however attempt to do this with a non-static variable:

#include<stdio.h>
int *runner()
{
    int count = 0;
    count++;
    return &count;    // BAD: count no longer exists when function returns
}

int main()
{
    int *p = runner();
    printf("%d ", *p);   // UNDEFINED BEHAVIOR
    p = runner();
    printf("%d ", *p);   // UNDEFINED BEHAVIOR
    return 0;
}

You invoke undefined behavior by using a pointer to an object that no longer exists.

Related