12

In C, you'd often/sometimes (as a matter of style) use a file-scope static variable where you'd use a private class member variable in C++. When scaling to multithreaded programs, simply adding thread_local in C11 or the long-supported extension __thread fits well. I know you can do exactly the same in C as C++ by putting everything inside a struct and making a set of functions that takes a pointer to that struct as its first argument. Some libraries do this extensively. But my personal style is to keep a struct as small as possible, if needed.

I often read or hear some people arguing 'global' variables are so much bad. I follow their reasons, and most of their argument seems to be related to extern global variables in C terms. What they say is certainly true. I sometimes use 1 or 2 of extern declared variables throughout the whole program when it'll simplify things a lot and when it's easy to keep track of them, but going further will easily make a program unpredictable.

What about static variables? Do they still have the same problem as 'real' global variables? Maybe I don't even have to ask this question and go on if I think what I'm doing is right, but today I saw another 'global variables are BAD' kind of post, and finally came here thinking perhaps this is a right place for such kind of question. What is your thought?

This question is not a duplicate of this because this question asks about extern and static non-local variables while the other question is about file-scope and block-scope static variables.

8
  • 2
    possible duplicate of Global variable vs. local-static variable for storing state
    – gnat
    Commented Aug 27, 2015 at 17:00
  • 3
    I don't think this is a duplicate. This question is asking about a file-static variable compared to an extern static (global) variables. The other question is comparing function-static variables to global variables.
    – user22815
    Commented Aug 27, 2015 at 17:07
  • @gnat This is not a duplicate question. I've made an edit to explain that basically says what Snowman said in his comment. Even the title is different.
    – user186330
    Commented Aug 27, 2015 at 17:21
  • @xiver77 while there currently are no votes to close as duplicate, some of the answers on the other question might be insightful here.
    – user22815
    Commented Aug 27, 2015 at 17:23
  • @Snowman per my reading, accepted answer in that other question covers all three cases (it seems to explain when and why file scope statics are to be preferred over both function scoped and global ones)
    – gnat
    Commented Aug 27, 2015 at 17:34

4 Answers 4

19

In a well-design C program, a file-static variable is similar to a private static member of a class:

  • It can only be accessed by functions in that file, similar to how a private static member variable can only be accessed by functions in the class in which it is defined.

  • There is only one copy of the variable.

  • Its lifetime is the program lifetime.

An extern variable would be a true global variable as in any language that supports them.

A static non-global variable is not as bad as a global; in fact, they are necessary in some cases.

  • Access is controlled through functions you write. This helps with data integrity including both bounds checking as well as thread-safety. (note: this does not guarantee thread-safety, it is simply one tool to help along the way)

  • Data is encapsulated: only that file can access it. This is as close as C can get to encapsulation where multiple functions can access a static variable.

Global variables are bad no matter what. Static file variables have the benefits of a private static variable but none of the drawbacks of a global variable.

The only issue is unlike with a true private static variable as in C++, other files can declare an extern variable matching the declaration and you cannot prevent access. In other words, you are relying on the honor system to avoid turning it into a global variable.

1
  • But in all seriousness, if someone else wants to waltz in and extern your private variable, let 'em have at it. When stuff breaks and becomes a mess on their end, kindly show them a well-worded disclaimer about "at your own risk" and all that. And to be pedantic, I could probably find a way to grab a pointer to a C++ object and offset my way to the private section. If I'm doing that I probably deserve whatever comes out the other end. Probably. Commented Sep 8, 2021 at 9:35
8

Global state, including extern variables and non-const static variables in file scope or in functions can frequently be an easy solution to a given problem, but there are three issues:

  1. static makes code untestable, because static variables tend to be non-replaceable dependencies. Or in more OOP-y words: you aren't following the Dependency Inversion Principle. I came to C and C++ from dynamic languages such as Perl, so my cost model is slanted towards virtual dispatch and function pointers and so on. With current languages, there's some conflict between testability and good architecture, but I think the minor nuisance of making your dependencies explicit and letting them be overridden in tests is noticeably offset by the ease of writing tests, and thus making sure your software is working as expected. Without making your code more dynamic, the only available mechanism to inject dependencies for a test is conditional compilation.

  2. Global state makes it difficult to reason about correctness, and that leads to bugs. The more bits and pieces have access to a variable and can modify it, the easier it is to lose track of what's happening. Instead: prefer single assignment of variables! Prefer const wherever reasonable! Prefer guarding variables through getters and setters where you can introduce correctness checks. As long as the state is static and not extern, it is still possible to maintain correctness, but it's always better to assume me-in-a-week won't be as smart as me-right-now. Especially in C++, we can use classes to model various abstractions that make it impossible to misuse something, so try to utilize the type system rather than your intelligence – you have more important stuff to think about.

  3. Global state might imply that your functions are not re-entrant, or that they can only be used in one context at a time. Imagine a database driver that could only manage one connection! That's a totally unnecessary restrictions. In reality, the limitations are often subtler, such as a global variable that's used to aggregate results. Instead, make your data flow explicit and pass everything through function parameters. Again, C++ classes can make this more manageable.

Obviously, static const NAMED_CONSTANTS are OK. Using static inside of functions is a lot trickier: while it is useful for lazily initialized constants, it may be fairly untestable. A compromise is to separate calculating the initial value from the static variable, so that both parts can be tested separately.

In small, self-contained programs, all of this won't matter, and you can keep using static state to your hearts delight. But as you pass around 500 LOC or if you are writing a reusable library, you should really start thinking about good architecture and a good interface without unnecessary restrictions.

5
  • Some of your suggestions about design and safety sometimes is impossible to take care of in programs that needs to be written in C.
    – user186330
    Commented Aug 27, 2015 at 17:59
  • And global state isn't necessarily thread unfriendly. The latest C and C++ standard both have thread_local and compilers long supported it as an extension before standardization.
    – user186330
    Commented Aug 27, 2015 at 18:01
  • To avoid many of the problems of global state you mentioned, most well-written C programs have a shallow and transparent structure with minimized outer dependencies.
    – user186330
    Commented Aug 27, 2015 at 18:10
  • 1
    Keeping code static and data mutable, while bug-prone, is the only way to write the most efficient program possible in the current computer architecture.
    – user186330
    Commented Aug 27, 2015 at 18:15
  • 1
    @xiver77 I have the luxury of writing more high-level code, so neither sacrificing architecture for performance nor using C is necessary in my line of work :) When writing C, I find it much more challenging to write correct code, and the choice of suitable abstractions is different from what I'd prefer, but it's still possible to provide a lot of safety. My answer tries to clearly mark C++-specific ideas as such. Your point about small files is very good, that makes it easier to reason about the program and reduces but not eliminates the problem posed by static variables.
    – amon
    Commented Aug 27, 2015 at 18:50
2

It's all tied to the scope of the variable (not constant, something mutable) in my view. It's a view admittedly lacking some nuance, but it's a pragmatic counter and appeal to work back to the most basic of fundamentals to those who say, "This is absolutely evil!" only to then trip up on issues similar to those associated with what they criticize, like race conditions.

Imagine you have a 50,000 line function with all sorts of variables declared at the top and goto statements to hop around all over the place. That's not very pleasant with such a monstrous variable scope, and trying to reason about the function, and what's going on with such variables, is going to be extremely difficult. In such a monstrous case, the normal distinction between "external" and "internal" side effect loses a lot of its practical purpose.

Imagine you have a 80-line simple program you just write once and crank out with a global variable (either with internal linkage and file scope or external linkage, but either way the program is teeny). That's not so bad.

Imagine you have a monstrous class in an object-oriented language that contains your entire program's logic with thousands and thousands of lines of code for its implementation. In that case its member variables are more problematic than the globals in the above, 80-line program.

If you want to be able to better and more confidently reason about your code, the thread safety of it (or lack thereof), make it more predictable, make sure your tests have good coverage without all kinds of potential edge cases being missed, etc, then it helps to narrow access to variables.

File scope statics will tend to have a narrower scope than ones with external linkage, but then again if your source file is 100,000 lines of code, that's still pretty damn wide. So for file scope statics, if you can't avoid them, I'd try to keep their scope narrow by not making your source file that can access them ginormous, since in that case reducing its scope is about reducing the size and design scope of the source file, as opposed to function (for local variables, including parameters), class (for member variables), or possibly module (for globals with external linkage but accessible only within the module), or even entire software (for globals with external linkage accessible to the entire software).

1

I do not consider variables with file-scope as bad as global variables. After all, all accesses to these variables are confined to one single source file. With that restriction, file-scope variables are pretty much as good or bad as a C++ private static data member, and you don't forbid their use, do you?

3
  • 1
    In c++, the syntax kind of encourages you to put a lot of member variables in classes, so having a private static member is not often needed. But in C, if you're not going to fully emulate what you'd do with a C++ class, static or thread_local variables can be used instead of a struct member if you want to avoid passing the struct pointer everywhere. My question is about this.
    – user186330
    Commented Aug 27, 2015 at 17:32
  • I'm talking about members with static storage, which exist exactly once, not for each object. These variable do not need any instance of the class in order to be referenced by the code of the class, so there is no need to pass around a reference/pointer to a singleton. This is precisely what file-scope variables achieve as well. The only difference is, that the static member has class scope while the static "global" variable has file scope. But these two scopes are very similar in extent, which is my point. I agree that the different meanings of static are confusing, though. Commented Aug 27, 2015 at 18:19
  • 1
    In C at least, static really has one meaning for variables (not for functions).
    – user186330
    Commented Aug 27, 2015 at 18:22

Your Answer

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