'What do clang and gcc qualify as variable being unused [duplicate]

I noticed in a PR review an unused variable and we were wondering why compiler didn't catch that. So I tested with godbolt the following code with bunch of unused variables and was surprised that some were reported as unused but others not. Even though all of them are unused.

#include <string>

struct Index
{
  Index(int index) : m_index(index) {}
  int m_index;
};

int main()
{
    std::string str = "hello"; // case 1. no warning here - unexpected
    int someValue = 2; // case 2. warning - as expected
    const int someConstant = 2; // case 3. warning - as expected
    Index index1(2);  // case 4. just as equally not used but no warning - unexpected
    // here using the assignment but do get a warning here
    // but the str assignment doesn't give a warning - weird
    Index index2 = 2; // case 5.
    Index index3{2}; // case 6. just as equally not used but no warning - unexpected
    Index index4 = {2}; // case 7. just as equally not used but no warning - unexpected
    return 0;
}

warning: unused variable 'someValue' [-Wunused-variable]

warning: unused variable 'index2' [-Wunused-variable] (warning only on clang, not on gcc)

warning: unused variable 'someConstant' [-Wunused-variable]

So what do clang and gcc qualify as unused? What if I'm using a lock? I declare it but don't use it directly but use it for automatic releasing of a resource. How do I tell the compiler that I am using it if one day it starts to give a warning about the lock?

int g_i = 0;
std::mutex g_i_mutex;  // protects g_i
 
void safe_increment()
{
    const std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
    // g_i_mutex is automatically released when lock goes out of scope
}

flags: -Wunused-variable clang: 14.0.0 gcc: 11.3



Solution 1:[1]

The reason why there's no warning is that variables of non-trivial class type aren't technically unused when you initialize them but then never access them in your function.

Consider this example:

struct Trivial {};

struct NonTrivial {
    NonTrivial() {
        //Whatever
    }
};

void test() {
    Trivial t;
    NonTrivial nt;
}

GCC warns about Trivial t; being unused since this declaration never causes any user-defined code to run; the only thing that's run are the trivial constructor and trivial destructor, which are no-ops. So no operation at all is performed on Trivial t and it is truly unused (its memory is never even touched).

NonTrivial nt; doesn't cause a warning, however, since it is in fact used to run its constructor, which is user-defined code.

That's also why compilers are not going to warn about "unused lock guards" or similar RAII classes - they're used to run user-defined code at construction and destruction, which means that they are used (a pointer to the object is passed to the user-defined constructor/destructor = address taken = used).

This can further be proved by marking the object's constructor with the gnu::pure attribute:

struct Trivial {};

struct NonTrivial {
    [[gnu::pure]] NonTrivial() {
        //Whatever
    }
};

void test() {
    Trivial t;
    NonTrivial nt;
}

In this case, GCC warns about both of them because it knows that NonTrivial::NonTrivial() doesn't have side-effects, which in turn enables the compiler to prove that construction and destruction of a NonTrivial is a no-op, giving us back our "unused variable" warning. (It also warns about gnu::pure being used on a void function, which is fair enough. You shouldn't usually do that.)

Clang's warning about the following code also does make sense.

struct Hmm {
  int m_i;
  Hmm(int i): m_i(i) {}
};

void test() {
  Hmm hmm = 2; //Case 5 from the question
}

This is equivalent to the following:

void test() {
  Hmm hmm = Hmm(2);
}

Construction of the temporary Hmm(2) has side-effects (it calls the user-defined constructor), so this temporary is not unused. However, the temporary then gets moved into the local variable Hmm hmm. Both the move constructor and the destructor of that local variable are trivial (and therefore don't invoke user code), so the variable is indeed unused since the compiler can prove that the behavior of the program would be the same whether or not that variable is present (trivial ctor + trivial dtor + no other access to the variable = unused variable, as explained above). It wouldn't be unused if Hmm had a non-trivial move constructor or a non-trivial destructor.

Note that a trivial move constructor leaves the moved-from object intact, so it truly does not have any side-effects (other than initializing the object that's being constructed).

This can easily be verified by deleting the move constructor, which causes both Clang and GCC to complain.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1