Clang warnings on uninitialized variables

extern bool a_variable;

void sideEffect() {}

void important() {
  bool a_variable;               // shadows global variable
  a_variable = false;

  if (a_variable)
    sideEffect();

  a_variable = true;
}

void evenMoreImportant() {
  if (a_variable).            // global and maybe uninitialized variable
    sideEffect();
}

A mildly fresh Clang gave no warnings at all. At least for reading the maybe uninitialized variable in evenMoreImportant, I would have hoped for a warning.

Fortran version

You have an external variable, so the initialization happens in a different translation unit. So if an uninitialized warning would be raised, I would expect it on that other compilation unit. Though, in C++, global variables are always zero-initialized, so it cannot be uninitialized. I’m not familiar enough with C to know if they have the same rules.

Static variables and thread variables are initialized with zero. The global variable is extern. In the comment, I wrote that it maybe reads an uninitialized variable. You will never know whether a different TU initialized the variable.

An extern variable has to be defined in a different translation unit. Otherwise you will get a linker error: Compiler Explorer. You can do that by dropping the extern and it will be zero-initialized: Compiler Explorer.

That’s true. But you still may have read an uninitialised variable. You don’t known when the other TU initialised or if at all the variable.

The Fortran guys have exactly the same problem in a famous benchmark. SPEC!

You can’t not initialize it. If you just declare it it will be zero-initialized.

2 Likes

Thanks. Then the semantics may differ between C-ish languages and Fortran.

The global variable a_variable has no initializer, therefore it is considered as a declaration and not a definition. The definition must be in a different TU and must have an initializer.

Thanks again.

I am slow. If I have only declarations and no definitions, then I will get a linker error. But Clang does not know about the linker error. It could still assume that the global variable is not initialized.

How? If it has a definition it’s initialised. If it does not have a definition there is nothing to initialise, it doesn’t have any storage.

Agreed. But this is a linker issue. Clang only sees the code above. The global declaration of a_variable is visible. How can Clang prove or disprove that a_variable was initialized when it is read in evenMoreImportant ?

Clang is assuming the definition comes from another C++ compilation unit; therefore, the definition has an initialization, and a warning is inappropriate.

You could argue that Clang can’t actually know, which is correct. However, if it assumes the definition is not C++, when actually it is, then the warning will be a false positive. The tradeoff is in favor of the common case, that the definition is in fact C++ and no warning is needed.

2 Likes

If it would be a global variable from another language, should it not be marked with extern "language" {

Yes, for the interaction between and C++ code, you need extern “c” {}. But the code above is probably valid C and C++ code.

In the end, it is a tradeoff whether to warn or not.

I don’t see how it’s a tradeoff, nor what your point is. It is not possible in C/C++ to have variable at global scope that is not initialised, there is no provision in the standard for that being something you can express. Therefore by definition all variables are initialised and reading their values is well-defined.

1 Like

I’m not sure that is true. Consider a global variable with a dynamic initializer. The C++ standard doesn’t specify when that dynamic initializer is run relative to initializations in other TUs. What happens when (initialization code in) a TU attempts to access the value of a global variable whose initializer has not yet run. Does the standard guarantee a zero-initialized value representation? I’m not sure (I haven’t tried to look; I think there might have been core issues about this). In the case of a non-trivial class type, such an access would certainly be UB because it is accessing an object that has not yet been constructed.

I believe those are indeed well-defined as being zero-initialised strictly before dynamic initialisation is performed (whether for non-block or block variables).

Only static and thread-local variables are zero initialized. To be picky, there is no guarantee that the global variable will ever get initialized. It will only be initialized by a definition in a different TU. How does Clang know that there is a second TU with a definition?

Because if there isn’t it won’t link, and so you cannot possibly run the program to observe the uninitialised value.

Also thread-local variables can be extern, too.

1 Like

(There is a fundamental difference between “does not exist” and “exists but has ill-defined contents”; you are conflating the two, but they are totally different)