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

Posted

技术标签:

【中文标题】查看编译器生成的默认函数?【英文标题】:view the default functions generated by a compiler? 【发布时间】:2010-01-24 22:37:45 【问题描述】:

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

【问题讨论】:

您的问题似乎有些混乱。你的意思是如何查看函数原型,如何查看哪些是默认的,哪些是我实现的,或者我如何查看默认实现的代码? 【参考方案1】:

使用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 的隐式声明的复制构造函数现在有一个非常量引用参数,因为它的一个成员也有(成员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;

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

【讨论】:

Clang 可以在 Windows 7 上运行吗?我找不到任何 MS windows 的下载。 为什么是“struct A;”是以下 struct A public: struct A; 中生成的代码的一部分内联 A(); ... 代码&gt; 使用 clan 3.4.2(target: x86_64-redhat-linux-gnu) 对于空结构的第一个示例,我得到不同的输出。我得到以下信息: TranslationUnitDecl 0x2fde550 > |-TypedefDecl 0x2fdea90 > __int128_t '__int128' |-TypedefDecl 0x2fdeaf0 > __uint128_t 'unsigned __int128' |-TypedefDecl 0x2fdeeb0 > __builtin_va_list '__va_list_tag [1]' -CXXRecordDecl 0x2fdef00 &lt;main1.cpp:1:1, col:12&gt; struct A definition -CXXRecordDecl 0x2fdf010 <1 col:8>【参考方案2】:

您可以使用调试器跟踪代码以查看发生了什么。例如:

#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++ 代码。

【讨论】:

【参考方案3】:

编译器生成的方法是抽象的,它们不存在于源代码中。 看下面的例子,我试着解释一下四个编译器生成的方法应该在源代码级别做什么。从这里你应该能够推断出任何正常的类。

如果你有这样的课程:

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;

【讨论】:

【参考方案4】:

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

【讨论】:

【参考方案5】:

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

默认情况下,编译器通过在每个成员变量上调用复制构造函数或赋值运算符来创建它们。

问题在于,当您使用使用引用计数来管理数据的对象时,默认的复制构造函数将创建对象的副本,而不是对象指向的数据的副本。这也是指针的情况。因此,如果您有这样的课程:

class aClass

  int one;
  int *ptwo;
;

默认的 Copy 构造函数只复制数据 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++”一书。 它有一整章关于这些东西,解释了类的默认函数做什么,不做什么,他们应该做什么,什么时候写你自己的。如果您只想了解此功能,我相信您可以在网络上获得数字版本。

【讨论】:

以上是关于查看编译器生成的默认函数?的主要内容,如果未能解决你的问题,请参考以下文章

默认的虚拟析构函数是不是会阻止编译器生成的移动操作?

C++空类编译器自动生成的6个成员函数关于构造函数拷贝构造函数的解释

如果不想使用编译器默认生成的函数,请明确拒绝它!

C++如何拒绝编译器自动生成的函数

编译器生成的默认构造函数是否会将std :: array中的指针初始化为nullptr?

若不想使用编译器自动生成的函数,就该明确拒绝