预期 "warning: unused variable" 但 C++ 中没有生成任何内容
Expected "warning: unused variable" but nothing is generated in C++
测试代码:
struct X {
X (int x) {}
};
int main() {
X x1(0); // nothing!
X x2 = 0; // warning: unused variable
X x3 = X(0); // warning: unused variable
}
问题
为什么没有为 x1
生成警告?
备注:
我正在使用 -Wall
选项进行编译。
GCC 和 Clang 产生相同的输出。
这 3 行生成相同的汇编程序指令(为清楚起见,放置了一些 asm("nop")
):
nop
lea -0x8(%rbp),%rdi
mov [=12=]x0,%esi
callq 400600 <X::X(int)>
nop
lea -0x10(%rbp),%rdi
mov [=12=]x0,%esi
callq 400600 <X::X(int)>
nop
lea -0x18(%rbp),%rdi
mov [=12=]x0,%esi
callq 400600 <X::X(int)>
nop
原始类型不会发生这种情况(例如,typedef int X
)。
如果原因是构造函数中可能存在副作用,那么问题就变成了:为什么我会收到另外两个警告?
编辑:
- 这不是 this question IMO 的副本,因为在这种情况下构造函数是 非平凡的。
第一个调用您的 user-defined 构造函数来执行直接初始化。这可能有 side-effects。因此使用 x1
。 (或者至少它不能很容易地确定为未使用。)
第二个副本构建。它调用您的 user-defined 构造函数来创建一个临时文件。然后它调用 default 复制构造函数来构造 x2
。 (见下文编辑更详细的讨论和细化。)因此 x2
可以确定为未使用,因为它是由 compiler-generated 构造函数创建的,没有 side-effects,并且 x2
本身并没有出现在其他任何地方。
第三个和第二个一样。通过 user-provided 构造函数创建一个临时文件,然后进行复制。使用了临时的,但x3
本身没有。
查看此问题的已接受答案:Is there a difference in C++ between copy initialization and direct initialization?
编辑
根据评论,这里有一些进一步的讨论。
首先,有评论认为该警告具有误导性。这可能有些主观,但我要指出的是,此类警告通常仅在 "best effort" 的基础上提供。如果编译器只会深入挖掘代码以进行检查,那么在特定情况下,通常无法保证的事情可能是正确的。不过,在某些时候,编译器开发人员必须划清界限。这意味着您通常不能指望像这样的警告来捕获未使用变量的所有情况。 (另一方面,如果你有一个 is 使用的变量并且你收到警告,那将是一个错误。)我个人的感觉是这里展示的行为没有误导性,但是,再一次,我看到在进行这样的调用时有一些个人的解释。
其次,@FrançoisAndrieux 指出,如果您修改 OP 给出的示例以包含 user-defined 复制构造函数,您仍然会收到相同的警告。这让人质疑我上面的解释,它引用了默认的复制构造函数。这涉及到第二个理论点,我将针对这个案例进行具体的回答。理论的重点是,除非你真的想深入了解编译器本身并获得它的具体规则,否则手头的问题是编译器是否可以合理地知道变量未被使用,记住可能有 不止一种方式编译器可以得出结论。
由于示例最初是发布的,我认为我的回答给出了 a 编译器可以得出结论的方式。另一种方式,似乎既适用于原始形式,也适用于评论建议的修改形式,是基于复制省略。此处对此进行了广泛的讨论:What are copy elision and return value optimization? 当前讨论的关键点是,特别是对于 复制构造函数 ,允许编译器忽略副作用。事实上,在某些情况下,编译器需要忽略复制。显然,这里的编译器在发出警告时直接或间接地考虑到了这一点。这也可能是 OP 提出的一点,即所有三种情况下的汇编代码都是相同的——那是因为复制操作已被优化。
现在让我尝试预测下一个问题:"If the copy construct is elided, why aren't the three cases really the same?"我认为这里的答案是未使用特定变量。在第二种和第三种情况下构造的是未命名的临时变量,而不是命名变量 x2
和 x3
。临时变量属于与示例中的第一种情况相同的模式,并且是 "used"(或者至少不能轻易确定以未使用)。一旦复制构造被优化,这仍然会使 x2
和 x3
未被任何标准使用。
@Brick 回答如下。对于不同的语义,您可以得到相同的最终目标代码,以证明尝试 add -fno-elide-constructors
to your flags,省略是导致相同目标代码的罪魁祸首,即使在 -O0
.
中也是如此
测试代码:
struct X {
X (int x) {}
};
int main() {
X x1(0); // nothing!
X x2 = 0; // warning: unused variable
X x3 = X(0); // warning: unused variable
}
问题
为什么没有为 x1
生成警告?
备注:
我正在使用
-Wall
选项进行编译。GCC 和 Clang 产生相同的输出。
这 3 行生成相同的汇编程序指令(为清楚起见,放置了一些
asm("nop")
):nop lea -0x8(%rbp),%rdi mov [=12=]x0,%esi callq 400600 <X::X(int)> nop lea -0x10(%rbp),%rdi mov [=12=]x0,%esi callq 400600 <X::X(int)> nop lea -0x18(%rbp),%rdi mov [=12=]x0,%esi callq 400600 <X::X(int)> nop
原始类型不会发生这种情况(例如,
typedef int X
)。如果原因是构造函数中可能存在副作用,那么问题就变成了:为什么我会收到另外两个警告?
编辑:
- 这不是 this question IMO 的副本,因为在这种情况下构造函数是 非平凡的。
第一个调用您的 user-defined 构造函数来执行直接初始化。这可能有 side-effects。因此使用 x1
。 (或者至少它不能很容易地确定为未使用。)
第二个副本构建。它调用您的 user-defined 构造函数来创建一个临时文件。然后它调用 default 复制构造函数来构造 x2
。 (见下文编辑更详细的讨论和细化。)因此 x2
可以确定为未使用,因为它是由 compiler-generated 构造函数创建的,没有 side-effects,并且 x2
本身并没有出现在其他任何地方。
第三个和第二个一样。通过 user-provided 构造函数创建一个临时文件,然后进行复制。使用了临时的,但x3
本身没有。
查看此问题的已接受答案:Is there a difference in C++ between copy initialization and direct initialization?
编辑
根据评论,这里有一些进一步的讨论。
首先,有评论认为该警告具有误导性。这可能有些主观,但我要指出的是,此类警告通常仅在 "best effort" 的基础上提供。如果编译器只会深入挖掘代码以进行检查,那么在特定情况下,通常无法保证的事情可能是正确的。不过,在某些时候,编译器开发人员必须划清界限。这意味着您通常不能指望像这样的警告来捕获未使用变量的所有情况。 (另一方面,如果你有一个 is 使用的变量并且你收到警告,那将是一个错误。)我个人的感觉是这里展示的行为没有误导性,但是,再一次,我看到在进行这样的调用时有一些个人的解释。
其次,@FrançoisAndrieux 指出,如果您修改 OP 给出的示例以包含 user-defined 复制构造函数,您仍然会收到相同的警告。这让人质疑我上面的解释,它引用了默认的复制构造函数。这涉及到第二个理论点,我将针对这个案例进行具体的回答。理论的重点是,除非你真的想深入了解编译器本身并获得它的具体规则,否则手头的问题是编译器是否可以合理地知道变量未被使用,记住可能有 不止一种方式编译器可以得出结论。
由于示例最初是发布的,我认为我的回答给出了 a 编译器可以得出结论的方式。另一种方式,似乎既适用于原始形式,也适用于评论建议的修改形式,是基于复制省略。此处对此进行了广泛的讨论:What are copy elision and return value optimization? 当前讨论的关键点是,特别是对于 复制构造函数 ,允许编译器忽略副作用。事实上,在某些情况下,编译器需要忽略复制。显然,这里的编译器在发出警告时直接或间接地考虑到了这一点。这也可能是 OP 提出的一点,即所有三种情况下的汇编代码都是相同的——那是因为复制操作已被优化。
现在让我尝试预测下一个问题:"If the copy construct is elided, why aren't the three cases really the same?"我认为这里的答案是未使用特定变量。在第二种和第三种情况下构造的是未命名的临时变量,而不是命名变量 x2
和 x3
。临时变量属于与示例中的第一种情况相同的模式,并且是 "used"(或者至少不能轻易确定以未使用)。一旦复制构造被优化,这仍然会使 x2
和 x3
未被任何标准使用。
@Brick 回答如下。对于不同的语义,您可以得到相同的最终目标代码,以证明尝试 add -fno-elide-constructors
to your flags,省略是导致相同目标代码的罪魁祸首,即使在 -O0
.