Clang生成非法指令,而GCC不会。

19

我在这个问题进行实验时,发现了一种情况:Clang会生成一个非法指令,而gcc不会。

我的问题是:我是否做错了什么,还是这是Clang的一个实际问题?

我将其简化为最小的代码片段以重现问题。请取文件eigen.cpp

#include <iostream>

#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>

int main() {
    Eigen::Matrix2d A;

    A << 0, 1, 2, 3;

    std::cout << A << "\n";
}

还有文件eigen_matrix_addons.hpp

friend std::ostream &operator<<(std::ostream &o, const Derived &m) {
    o << static_cast<const MatrixBase<Derived> &>(m);
}

(详见此处,其中有关于此文件的详细解释。简而言之,它的内容直接放置在template<class Derived> class MatrixBase;类定义中。因此,它为Derived引入了另一个ostream运算符,该运算符调用MatrixBase<Derived>的Eigen实现的ostream运算符。阅读此问题后,这一动机变得显然。)

使用GCC编译并运行:

$ g++ -std=c++11 -Wall -Wextra -pedantic -isystem/usr/include/eigen3 -I. -o eigen_gcc eigen.cpp
$ ./eigen_gcc
0 1
2 3
$ g++ --version
g++ (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

然后使用Clang进行编译并运行:

$ clang++ -std=c++11 -Wall -Wextra -pedantic -isystem/usr/include/eigen3 -I. -o eigen_clang eigen.cpp
$ ./eigen_clang
0 1
Illegal instruction
$ clang++ --version
clang version 3.4 (branches/release_34 198681)
Target: x86_64-suse-linux
Thread model: posix

你可以看到,在执行非法指令后,程序被中断了。在gdb中进行回溯可以发现问题出现在eigen_matrix_addons.hpp的第二行:

(gdb) bt
#0  0x00000000004013e1 in Eigen::operator<< (o=..., m=...)
    at ./eigen_matrix_addons.hpp:2
#1  0x00000000004010f0 in main () at eigen.cpp:15

也就是说可能是static_cast

根据zypper,我的Eigen版本为3.2.0-2.1.4。

编辑

@Mysticial要求的反汇编:

(gdb) disass
Dump of assembler code for function Eigen::operator<<(std::ostream&, Eigen::Matrix<double, 2, 2, 0, 2, 2> const&):
   0x00000000004013c0 <+0>:     push   %rbp
   0x00000000004013c1 <+1>:     mov    %rsp,%rbp
   0x00000000004013c4 <+4>:     sub    $0x20,%rsp
   0x00000000004013c8 <+8>:     mov    %rdi,-0x10(%rbp)
   0x00000000004013cc <+12>:    mov    %rsi,-0x18(%rbp)
   0x00000000004013d0 <+16>:    mov    -0x10(%rbp),%rdi
   0x00000000004013d4 <+20>:    mov    -0x18(%rbp),%rsi
   0x00000000004013d8 <+24>:    callq  0x4013f0 <Eigen::operator<< <Eigen::Matrix<double, 2, 2, 0, 2, 2> >(std::ostream&, Eigen::DenseBase<Eigen::Matrix<double, 2, 2, 0, 2, 2> > const&)>
   0x00000000004013dd <+29>:    mov    %rax,-0x20(%rbp)
=> 0x00000000004013e1 <+33>:    ud2
End of assembler dump.

2
这是什么处理器?您能提供在它断开的点处的反汇编吗? - Mysticial
4
函数的返回值流失到函数外部是未定义行为。 - Shafik Yaghmour
@Mysticial 处理器是 Intel(R) Core(TM) i5-3340M CPU @ 2.70GHz。我已经将反汇编代码添加到问题中。 - Lemming
ud2 告诉 CPU 引发无效的操作码指令。因此,Clang 故意使程序崩溃。有趣的是,在这种情况下,大多数编译器甚至不会编译,如果省略返回值。 - Mysticial
@Mysticial 很有趣,我不知道这一点。对我来说很奇怪,既然gcc和clang都没有发出警告。尽管所有的警告标志...这就是我通常注意到我在ostream运算符中忘记了返回值的方式。 - Lemming
@ShafikYaghmour 是的,确实是缺少了 return 语句。 - Lemming
2个回答

32

"非法指令"错误很可能是因为您的 "operator <<" 缺少返回语句,这会导致未定义的行为。

标准的第6.6.3节规定:

函数结束时没有返回值相当于使用了不带值的return;这会导致值返回函数的未定义行为。

您应该添加:

return o;

在函数结束时。


4
您还可以使用return o << ...;使其保持在一行中。 - Tavian Barnes
谢谢。是缺少了return语句。由于某种原因,我总是在operator<<中忘记它。然而值得注意的是,无论是gcc还是clang在这种情况下都没有发出警告。 - Lemming
@Lemming,正如我在这里的回答中所指出的,您可以使用-Werror=return-type标志来生成一个错误。 - Shafik Yaghmour
@ShafikYaghmour 谢谢。是的,-Wreturn-type 包含在我设置的 -Wall 中。在上面的例子中,-Werror=return-type 没有显示任何不同的行为,并且也忽略了缺少的返回语句。原因很简单,但我之前完全忽略了它... Eigen 库通过 -isystem 添加到包含搜索路径中,这指示编译器不从该代码生成任何警告(包括 -Werror)。由于 Eigen 插件技巧,我的代码仅作为这些系统头文件的一部分进行解析。如果我改用 -I,则会打印出警告。 - Lemming
@RichardSmith 是的,对不起,我不明白那与我在上一条评论中所说的有何不同。 - Lemming

2

我在使用谷歌的单元测试时遇到了相同的问题。 我有一个带有默认返回值的模拟函数,设置方式为

ON_CALL(...).WillByDefault(Return(...));

然后在预期中,我使用了类似 SetArgReferee 的方法。
EXPECT_CALL(...).WillOnce(SetArgReferee<...>(...))

在某些相同的期望值中,clang在SetArgReferee函数的末尾插入了ud2,但并非全部。将其更正为:

EXPECT_CALL(...).WillOnce(
    DoAll(
        SetArgReferee<...>(...),
        Return(...)));

已修复。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接