clang 和 gcc 有什么资格作为未使用的变量

What do clang and gcc qualify as variable being unused

我在 PR 审查中注意到一个未使用的变量,我们想知道为什么编译器没有捕捉到它。因此,我使用 godbolt 测试了以下带有一堆未使用变量的代码,令我惊讶的是,有些变量被报告为未使用,而另一些则没有。尽管所有这些都未使用。

#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]

那么什么是 clang 和 gcc 没有被使用的呢?如果我使用锁怎么办?我声明它但不直接使用它而是将它用于自动释放资源。如果有一天它开始发出有关锁定的警告,我如何告诉编译器我正在使用它?

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
}

标志:-Wunused-variable 铿锵声:14.0.0 海湾合作委员会:11.3

没有警告的原因是 non-trivial class 类型的变量在您初始化它们时在技术上不是未使用的,但是在您的函数中永远不会访问它们。

考虑这个 example:

struct Trivial {};

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

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

GCC 警告 Trivial t; 未被使用,因为此声明不会导致任何 user-defined 代码变为 运行;唯一 运行 的是平凡的构造函数和平凡的析构函数,它们是 no-ops。所以根本没有对 Trivial t 执行任何操作,它确实未被使用(它的内存从未被触及)。

然而,

NonTrivial nt; 不会引起警告,因为它实际上 用于 运行 其构造函数,即 user-defined代码。

这也是为什么编译器不会警告“未使用的锁守卫”或类似的 RAII classes - 他们习惯于 运行 user-defined 构造和销毁代码,这意味着它们被使用(指向对象的指针被传递给user-defined constructor/destructor =地址被使用=使用)。

这可以进一步证明 marking the object's constructor with the gnu::pure attribute:

struct Trivial {};

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

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

在这种情况下,GCC 对它们都发出警告,因为它知道 NonTrivial::NonTrivial() 没有 side-effects,这反过来又使编译器能够证明 [= 的构造和破坏19=] 是一个 no-op,返回我们的“未使用变量”警告。 (它还会警告 gnu::pure 被用在 void 函数上,这很公平。您通常不应该这样做。)

Clang 对以下代码的警告也有道理。

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

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

这是equivalent以下内容:

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

临时Hmm(2)的构造有side-effects(它调用user-defined构造函数),所以这个临时没有被使用。然而,临时变量随后被移入局部变量 Hmm hmm。该局部变量的移动构造函数和析构函数都是微不足道的(因此不会调用用户代码),因此该变量确实未被使用,因为编译器可以证明无论该变量是否存在,程序的行为都是相同的存在(平凡的 ctor + 平凡的 dtor + 没有其他访问变量 = 未使用的变量,如上所述)。如果 Hmm 有一个 non-trivial 移动构造函数或 non-trivial 析构函数,它就不会被使用。

请注意,一个简单的移动构造函数会使 moved-from 对象完好无损,因此它确实没有任何 side-effects(除了初始化正在构造的对象之外)。

这可以通过删除移动构造函数轻松验证,causes both Clang and GCC to complain