[转](1条消息)深入理解c 之struct构造函数

        是否曾好奇struct定义的数据结构类型,当我拷贝构造时,或者赋值操作时会发生什么?倘若我结构中存在指针引用对象时,又能否正确处理?带着这些疑问,我们来对struct的构造函数进行研究,以解答以下几个疑问:

        1) 何时编译器会自动为struct合成构造函数

        2) 如何能保证携带指针引用对象的struct正确拷贝或拷贝构造

        让我们先来看第一个问题,考虑如下代码。ServerConfig只有两个简单的成员,通过反汇编可见编译器合成了ServerConfig的构造函数,并调用其成员的构造函数。若我们移除addr成员,编译器则不会为ServerConfig合成构造函数。由此不难发现,当struct成员存在构造函数时,编译器会自动为其生成构造函数。

        但是值得注意class的默认构造函数不是必须的,也就是说。默认构造函数是编译器所需要的,它用以保证程序的正确运行,如初始化虚表指针;并非为程序提供默认初始值之类。当class继承自含默认构造函数的父类时,具有默认构造函数的成员时,存在virtual function时,或者virtual继承时; 会触发编译器合成默认构造函数。

 
  1.  
    #include <stdio.h>
  2.  
    #include <string.h>
  3.  
    #include <stdint.h>
  4.  
     
  5.  
    class CString
  6.  
    {
  7.  
    public:
  8.  
    CString()
  9.  
    {
  10.  
    m_str = strdup("");
  11.  
    }
  12.  
    CString(const char *str)
  13.  
    {
  14.  
    m_str = strdup(str);
  15.  
    }
  16.  
    ~CString()
  17.  
    {
  18.  
    delete m_str; m_str= NULL;
  19.  
    }
  20.  
    private:
  21.  
    char *m_str;
  22.  
    };
  23.  
    typedef struct {
  24.  
    int port;
  25.  
    CString addr;
  26.  
    }ServerConfig;
  27.  
    int main(int argc, char *argv[])
  28.  
    {
  29.  
    ServerConfig config1;
  30.  
    return 0;
  31.  
    }
 

    

 
  1.  
    (gdb) disassemble main
  2.  
    Dump of assembler code for function main(int, char**):
  3.  
    ...
  4.  
    0x000000000040065d <+16>: lea -0x20(%rbp),%rax # config1地址放入rax
  5.  
    0x0000000000400661 <+20>: mov %rax,%rdi # 通过rdi传入this指针
  6.  
    0x0000000000400664 <+23>: callq 0x4006ce <ServerConfig::ServerConfig()> # 构造config1
  7.  
    0x0000000000400669 <+28>: mov $0x0,%ebx
  8.  
    0x000000000040066e <+33>: lea -0x20(%rbp),%rax
  9.  
    0x0000000000400672 <+37>: mov %rax,%rdi
  10.  
    0x0000000000400675 <+40>: callq 0x4006ec <ServerConfig::~ServerConfig()>
  11.  
    ...
  12.  
     
  13.  
    (gdb) disassemble ServerConfig::ServerConfig
  14.  
    Dump of assembler code for function ServerConfig::ServerConfig():
  15.  
    ...
  16.  
    0x00000000004006d6 <+8>: mov %rdi,-0x8(%rbp) # 获取this指针
  17.  
    0x00000000004006da <+12>: mov -0x8(%rbp),%rax #
  18.  
    0x00000000004006de <+16>: add $0x8,%rax # this + 0x08, 偏移掉port计算得到addr的地址
  19.  
    0x00000000004006e2 <+20>: mov %rax,%rdi #
  20.  
    0x00000000004006e5 <+23>: callq 0x400684 <CString::CString()> # 调用CString构造函数
  21.  
    ...
 

        但是上面的代码是危险的,只要我们对ServerConfig引用了拷贝构造或者赋值操作时,会引发double free。那这又是为什么呢?让我们考虑如下代码,编译后我们进行反汇编。不难发现ServerConfig并未合成拷贝构造函数,而是进行了按位拷贝。因此config2拷贝了config1内addr成员携带的指针值而非指针引用对象,引起重复释放。

 
  1.  
    int main(int argc, char *argv[])
  2.  
    {
  3.  
    ServerConfig config1;
  4.  
    ServerConfig config2 = config1;
  5.  
    return 0;
  6.  
    }
 
 
  1.  
    (gdb) disassemble main
  2.  
    Dump of assembler code for function main(int, char**):
  3.  
    ...
  4.  
    0x000000000040065d <+16>: lea -0x30(%rbp),%rax # 获取config1地址
  5.  
    0x0000000000400661 <+20>: mov %rax,%rdi
  6.  
    0x0000000000400664 <+23>: callq 0x4006ea <ServerConfig::ServerConfig()> # 构造config1
  7.  
    0x0000000000400669 <+28>: mov -0x30(%rbp),%rax
  8.  
    0x000000000040066d <+32>: mov %rax,-0x20(%rbp) # 拷贝config1.port 到 config2.port
  9.  
    0x0000000000400671 <+36>: mov -0x28(%rbp),%rax
  10.  
    0x0000000000400675 <+40>: mov %rax,-0x18(%rbp) # 拷贝config1.addr.m_str 到 config2.addr.m_str
  11.  
    0x0000000000400679 <+44>: mov $0x0,%ebx
  12.  
    0x000000000040067e <+49>: lea -0x20(%rbp),%rax
  13.  
    0x0000000000400682 <+53>: mov %rax,%rdi
  14.  
    0x0000000000400685 <+56>: callq 0x400708 <ServerConfig::~ServerConfig()> # 析构config2
  15.  
    0x000000000040068a <+61>: lea -0x30(%rbp),%rax
  16.  
    0x000000000040068e <+65>: mov %rax,%rdi
  17.  
    0x0000000000400691 <+68>: callq 0x400708 <ServerConfig::~ServerConfig()> # 析构config1
 

        我们现在开始回答第二个问题,如何能保证携带指针引用对象的struct正确拷贝或拷贝构造。那就是其含有指针引用之类的成员,都应正确声明拷贝构造函数和赋值操作函数。如本例中CString正确声明如下,这样编译器会正确为ServerConfig合成拷贝构造函数和赋值操作函数。

 
  1.  
    class CString
  2.  
    {
  3.  
    public:
  4.  
    CString()
  5.  
    {
  6.  
    m_str = strdup("");
  7.  
    }
  8.  
    CString(const char *str)
  9.  
    {
  10.  
    m_str = strdup(str);
  11.  
    }
  12.  
    CString(const CString &cstr)
  13.  
    {
  14.  
    m_str = strdup(cstr.m_str);
  15.  
    }
  16.  
    CString &operator =(const CString &cstr){
  17.  
     
  18.  
    delete m_str;
  19.  
    m_str = strdup(cstr.m_str);
  20.  
    return *this;
  21.  
    }
  22.  
    ~CString()
  23.  
    {
  24.  
    delete m_str; m_str= NULL;
  25.  
    }
  26.  
    private:
  27.  
    char *m_str;
  28.  
    };
 
 
  1.  
    (gdb) disassemble main
  2.  
    Dump of assembler code for function main(int, char**):
  3.  
    ...
  4.  
    0x0000000000400677 <+42>: callq 0x4007a6 <ServerConfig::ServerConfig(ServerConfig const&)>
  5.  
    0x000000000040067c <+47>: lea -0x20(%rbp),%rdx
  6.  
    0x0000000000400680 <+51>: lea -0x30(%rbp),%rax
  7.  
    0x0000000000400684 <+55>: mov %rdx,%rsi
  8.  
    0x0000000000400687 <+58>: mov %rax,%rdi
  9.  
    0x000000000040068a <+61>: callq 0x4007e0 <ServerConfig::operator=(ServerConfig const&)>
  10.  
    ...
 

        当我们明确不允许拷贝的时候,一定要禁止拷贝构造和赋值操作函数。可以继承如下禁止拷贝基类即可。

 
  1.  
    class IUncopyable
  2.  
    {
  3.  
    public:
  4.  
    ~IUncopyable(){};
  5.  
    private:
  6.  
    IUncopyable(IUncopyable &);
  7.  
    IUncopyable & operator=(const IUncopyable&);
  8.  
    };
 

 


---------------------
作者:EINPROGRESS
来源:CSDN
原文:https://blog.csdn.net/kwanson/article/details/82320728
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值