查看编译器生成的默认函数?

时间:2010-01-24 22:37:45

标签: c++ visual-c++

有没有办法查看由编译器(如VC ++ 2008)为未定义它们的类生成的默认函数(例如,默认复制构造函数,默认赋值运算符)?

5 个答案:

答案 0 :(得分:11)

使用clang编译器,您可以通过传递-ast-dump参数来查看它们。 Clang仍处于开发阶段,但您已经可以将它用于这些事情:

[js@HOST2 cpp]$ cat main1.cpp
struct A { };
[js@HOST2 cpp]$ clang++ -cc1 -ast-dump main1.cpp
typedef char *__builtin_va_list;
struct A {
public:
    struct A;
    inline A();
    inline A(struct A const &);
    inline struct A &operator=(struct A const &);
    inline void ~A();
};
[js@HOST2 cpp]$

我希望这就是你要求的。让我们改变代码再看一遍。

[js@HOST2 cpp]$ cat main1.cpp
struct M { M(M&); };
struct A { M m; };
[js@HOST2 cpp]$ clang++ -cc1 -ast-dump main1.cpp
typedef char *__builtin_va_list;
struct M {
public:
    struct M;
    M(struct M &);
    inline struct M &operator=(struct M const &);
    inline void ~M();
};
struct A {
public:
    struct A;
    struct M m;
    inline A();
    inline A(struct A &);
    inline struct A &operator=(struct A const &);
    inline void ~A();
};
[js@HOST2 cpp]$

注意A的隐式声明的复制构造函数现在如何具有非const引用参数,因为其中一个成员也具有(成员m),并且M没有声明了默认构造函数。

要获取生成的代码,可以让它发出虚拟机中间语言。让我们看一下生成的代码:

struct A { virtual void f(); int a; };
A f() { A a; a = A(); return a; } // using def-ctor, assignment and copy-ctor

[js@HOST2 cpp]$ clang++ -cc1 -O1 -emit-llvm -o - main1.cpp | c++filt
[ snippet ]
define linkonce_odr void @A::A()(%struct.A* nocapture %this) nounwind align 2 {
entry:
  %0 = getelementptr inbounds %struct.A* %this, i32 0, i32 0 ; <i8***> [#uses=1]
  store i8** getelementptr inbounds ([3 x i8*]* @vtable for A, i32 0, i32 2), i8*** %0
  ret void
}

define linkonce_odr %struct.A* @A::operator=(A const&)(%struct.A* %this, 
  %struct.A* nocapture) nounwind align 2 {
entry:
  %tmp = getelementptr inbounds %struct.A* %this, i32 0, i32 1 ; <i32*> [#uses=1]
  %tmp2 = getelementptr inbounds %struct.A* %0, i32 0, i32 1 ; <i32*> [#uses=1]
  %tmp3 = load i32* %tmp2                         ; <i32> [#uses=1]
  store i32 %tmp3, i32* %tmp
  ret %struct.A* %this
}

define linkonce_odr void @A::A(A const&)(%struct.A* nocapture %this, %struct.A* nocapture) 
  nounwind align 2 {
entry:
  %tmp = getelementptr inbounds %struct.A* %this, i32 0, i32 1 ; <i32*> [#uses=1]
  %tmp2 = getelementptr inbounds %struct.A* %0, i32 0, i32 1 ; <i32*> [#uses=1]
  %tmp3 = load i32* %tmp2                         ; <i32> [#uses=1]
  store i32 %tmp3, i32* %tmp
  %1 = getelementptr inbounds %struct.A* %this, i32 0, i32 0 ; <i8***> [#uses=1]
  store i8** getelementptr inbounds ([3 x i8*]* @vtable for A, i32 0, i32 2), i8*** %1
  ret void
}

现在,我不理解中间语言(在llvm.org定义)。但是您可以使用llvm编译器将所有代码转换为C:

[js@HOST2 cpp]$ clang++ -cc1 -O1 -emit-llvm -o - main1.cpp | llc -march=c -o - | c++filt
[snippet]
void A::A()(struct l_struct.A *llvm_cbe_this) {
  *((&llvm_cbe_this->field0)) = ((&_ZTV1A.array[((signed int )2u)]));
  return;
}


struct l_struct.A *A::operator=(A const&)(struct l_struct.A *llvm_cbe_this, struct l_struct.A
  *llvm_cbe_tmp__1) {
  unsigned int llvm_cbe_tmp3;

  llvm_cbe_tmp3 = *((&llvm_cbe_tmp__1->field1));
  *((&llvm_cbe_this->field1)) = llvm_cbe_tmp3;
  return llvm_cbe_this;
}


void A::A(A const&)(struct l_struct.A *llvm_cbe_this, struct l_struct.A *llvm_cbe_tmp__2) {
  unsigned int llvm_cbe_tmp3;

  llvm_cbe_tmp3 = *((&llvm_cbe_tmp__2->field1));
  *((&llvm_cbe_this->field1)) = llvm_cbe_tmp3;
  *((&llvm_cbe_this->field0)) = ((&_ZTV1A.array[((signed int )2u)]));
  return;
}

多田!请注意它如何在复制构造函数和默认构造函数中设置虚拟表指针。希望这可以帮助。

答案 1 :(得分:7)

您可以使用调试器跟踪代码以查看正在进行的操作。例如:

#include <string>

struct A {
    int a[100];
    char c;
    std::string s;
};

int main() {
    A a;
    A b(a);
}

通过复制构造函数在'b'的构造处设置断点。 VC ++ 6调试器中此时的汇编器输出为:

12:       A b(a);
00401195   lea         eax,[ebp-1B0h]
0040119B   push        eax
0040119C   lea         ecx,[ebp-354h]
004011A2   call        @ILT+140(A::A) (00401091) 

最后一个是复制构造函数调用。如果您想了解更多细节,也可以追溯到这一点。

但是,如果您的问题是“如何查看复制构造函数的C ++代码等”,答案是您不能,因为没有 - 编译器生成汇编程序或机器代码(取决于你的编译器),而不是C ++代码。

答案 2 :(得分:2)

编译器生成的方法是抽象的,它们在源代码中不存在 请看下面的示例,我尝试解释四个编译器生成的方法应该在源代码级别执行的操作。从这里你应该能够推断出任何正常的类。

如果您有这样的课程:

class X: public Base
{
    int*   a;
    double b;
    Y      c;
};

然后编译器生成以下内容的等价物:

X::X() // Default constructor
    :Base() Calls the base class default constructor
    //,a    pointers are POD no default initialization
    //,b    double   are POD no default initialization
    ,c()    //Call default constructor on each non POD member
{}

X::~X() // Default destructor
{}
// Destructor for each non POD member in reverse order
~c()       calls the destructor of the class type
//~b       double are POD no destructor
//~a       pointers are POD no destructor
~Base()    // Calls the base class destructor

X::X(X const& copy)
    :Base(copy)    // calls the base class copy constructor
    // Copies each member using its copy constructor
    ,a(copy.a)     // Pointers copied  (Note just the pointer is copied, not what it points at)
    ,b(copy.b)     // Double copied.
    ,c(copy.c)     // Uses copy constructor of the class type (must be accessible)
{}

X& X::operator=(X const& copy)
{
    Base::operator=(copy);  // Calls the base class assignment operator
    // Copies each member using the members assignment operator
    a = copy.a;    // Pointers copied  (Note just the pointer is copied, not what it points at)
    b = copy.b;    // Double copied
    c = copy.c;    // Uses assignment operator of the class type (must be accessible)

    return *this;
}

答案 3 :(得分:0)

对象查看工具(如objdumpdumpbin)可以为您反汇编输出对象文件。然后你可以四处寻找,看看你关心的函数/方法会发出什么指令。

答案 4 :(得分:-1)

您确定需要查看此功能吗?

默认情况下,编译器通过在每个成员变量上调用Copy Constructor或Assignment运算符来创建它们。

问题在于,当您使用使用引用计数来管理数据的对象时,默认的复制构造函数将创建对象的副本,但不会创建对象指向的数据的副本。这也是指针的情况。所以如果你有一个类:

class aClass
{
  int one;
  int *ptwo;
};

默认的复制构造函数只复制数据a和指针b。但是他没有复制b指向的数据。如果您使用此类

aClass a, b;
a.ptwo = new int;
a.one = 1;
*(a.ptwo) = 2;

b = a;

*(b.ptwo) = 1;  
//a.ptwo now points to an integer with the value of 1

如果您希望此类复制ptwo的值而不是指针,则需要您自己的复制赋值运算符函数。如果您对更多详细信息感兴趣,那么默认函数会执行什么操作以及不执行哪些功能,那么您可以查看“Effective C ++”一书。
它有一整章关于这个东西,它解释了类的默认功能,不执行,应该做什么,何时编写自己的功能。如果您只想了解此功能,我相信您可以在网络上获得数字版本。