c++のstruct構造関数を深く理解する
5345 ワード
structで定義されたデータ構造タイプが、私が構造をコピーしたり、割り当て操作をしたりしたときに何が起こるか知りたいですか?もし私の構造にポインタ参照オブジェクトが存在する場合、正しく処理できますか?これらの疑問を持ってstructの構造関数を研究し,以下のいくつかの疑問を解いた.
1)コンパイラがstructの構造関数を自動的に合成するタイミング
2)ポインタ参照オブジェクトを携帯するstructの正確なコピーまたはコピー構造を保証する方法
まず最初の問題を見て、次のコードを考えてみましょう.ServerConfigには2つの単純なメンバーしかいません.逆アセンブリ可視コンパイラによってServerConfigのコンストラクション関数が合成され、そのメンバーのコンストラクション関数が呼び出されます.addrメンバーを削除すると、コンパイラはServer Configのコンストラクタを合成しません.これにより、structメンバーにコンストラクション関数が存在する場合、コンパイラは自動的にコンストラクション関数を生成することがわかります.
しかしclassのデフォルト構造関数は必須ではないことに注意してください.つまり.デフォルトのコンストラクション関数は、仮想テーブルポインタを初期化するなど、プログラムの正確な実行を保証するためにコンパイラに必要です.プログラムにデフォルトの初期値などを提供するわけではありません.classがデフォルトコンストラクション関数を含む親クラスから継承された場合、デフォルトコンストラクション関数を持つメンバーが存在する場合、virtual functionが存在する場合、またはvirtual継承が存在する場合.コンパイラがデフォルトのコンストラクション関数を合成するのをトリガーします.
しかし、上記のコードは危険です.サーバコンフィグに対してコピー構造または付与操作を参照した場合、double freeが発生します.それはなぜですか?次のコードを考えてみましょう.コンパイル後、逆アセンブリを行います.ServerConfigがコピーコンストラクタを合成するのではなく、ビット単位でコピーされていることがわかります.したがってconfig 2はconfig 1内のaddrメンバーが持つポインタ値をポインタ参照オブジェクトではなくコピーし、重複解放を引き起こす.
次に、ポインタリファレンスオブジェクトを携帯するstructの正確なコピーまたはコピー構造を保証する方法について、2番目の質問に答えます.ポインタリファレンスのようなメンバーが含まれている場合は、コピーコンストラクション関数と付与操作関数を正しく宣言する必要があります.この例でCStringが次のように正しく宣言されている場合、コンパイラはサーバコンフィグのコピー構造関数と割り当て操作関数を正しく合成します.
コピーが許可されていないことを明確にする場合は、コピー構造と付与操作関数を禁止する必要があります.次のようにベースクラスのコピーを禁止すればよい.
1)コンパイラがstructの構造関数を自動的に合成するタイミング
2)ポインタ参照オブジェクトを携帯するstructの正確なコピーまたはコピー構造を保証する方法
まず最初の問題を見て、次のコードを考えてみましょう.ServerConfigには2つの単純なメンバーしかいません.逆アセンブリ可視コンパイラによってServerConfigのコンストラクション関数が合成され、そのメンバーのコンストラクション関数が呼び出されます.addrメンバーを削除すると、コンパイラはServer Configのコンストラクタを合成しません.これにより、structメンバーにコンストラクション関数が存在する場合、コンパイラは自動的にコンストラクション関数を生成することがわかります.
しかしclassのデフォルト構造関数は必須ではないことに注意してください.つまり.デフォルトのコンストラクション関数は、仮想テーブルポインタを初期化するなど、プログラムの正確な実行を保証するためにコンパイラに必要です.プログラムにデフォルトの初期値などを提供するわけではありません.classがデフォルトコンストラクション関数を含む親クラスから継承された場合、デフォルトコンストラクション関数を持つメンバーが存在する場合、virtual functionが存在する場合、またはvirtual継承が存在する場合.コンパイラがデフォルトのコンストラクション関数を合成するのをトリガーします.
#include
#include
#include
class CString
{
public:
CString()
{
m_str = strdup("");
}
CString(const char *str)
{
m_str = strdup(str);
}
~CString()
{
delete m_str; m_str= NULL;
}
private:
char *m_str;
};
typedef struct {
int port;
CString addr;
}ServerConfig;
int main(int argc, char *argv[])
{
ServerConfig config1;
return 0;
}
(gdb) disassemble main
Dump of assembler code for function main(int, char**):
...
0x000000000040065d : lea -0x20(%rbp),%rax # config1 rax
0x0000000000400661 : mov %rax,%rdi # rdi this
0x0000000000400664 : callq 0x4006ce <:serverconfig> # config1
0x0000000000400669 : mov $0x0,%ebx
0x000000000040066e : lea -0x20(%rbp),%rax
0x0000000000400672 : mov %rax,%rdi
0x0000000000400675 : callq 0x4006ec <:>
...
(gdb) disassemble ServerConfig::ServerConfig
Dump of assembler code for function ServerConfig::ServerConfig():
...
0x00000000004006d6 : mov %rdi,-0x8(%rbp) # this
0x00000000004006da : mov -0x8(%rbp),%rax #
0x00000000004006de : add $0x8,%rax # this + 0x08, port addr
0x00000000004006e2 : mov %rax,%rdi #
0x00000000004006e5 : callq 0x400684 <:cstring> # CString
...
しかし、上記のコードは危険です.サーバコンフィグに対してコピー構造または付与操作を参照した場合、double freeが発生します.それはなぜですか?次のコードを考えてみましょう.コンパイル後、逆アセンブリを行います.ServerConfigがコピーコンストラクタを合成するのではなく、ビット単位でコピーされていることがわかります.したがってconfig 2はconfig 1内のaddrメンバーが持つポインタ値をポインタ参照オブジェクトではなくコピーし、重複解放を引き起こす.
int main(int argc, char *argv[])
{
ServerConfig config1;
ServerConfig config2 = config1;
return 0;
}
(gdb) disassemble main
Dump of assembler code for function main(int, char**):
...
0x000000000040065d : lea -0x30(%rbp),%rax # config1
0x0000000000400661 : mov %rax,%rdi
0x0000000000400664 : callq 0x4006ea <:serverconfig> # config1
0x0000000000400669 : mov -0x30(%rbp),%rax
0x000000000040066d : mov %rax,-0x20(%rbp) # config1.port config2.port
0x0000000000400671 : mov -0x28(%rbp),%rax
0x0000000000400675 : mov %rax,-0x18(%rbp) # config1.addr.m_str config2.addr.m_str
0x0000000000400679 : mov $0x0,%ebx
0x000000000040067e : lea -0x20(%rbp),%rax
0x0000000000400682 : mov %rax,%rdi
0x0000000000400685 : callq 0x400708 <:> # config2
0x000000000040068a : lea -0x30(%rbp),%rax
0x000000000040068e : mov %rax,%rdi
0x0000000000400691 : callq 0x400708 <:> # config1
次に、ポインタリファレンスオブジェクトを携帯するstructの正確なコピーまたはコピー構造を保証する方法について、2番目の質問に答えます.ポインタリファレンスのようなメンバーが含まれている場合は、コピーコンストラクション関数と付与操作関数を正しく宣言する必要があります.この例でCStringが次のように正しく宣言されている場合、コンパイラはサーバコンフィグのコピー構造関数と割り当て操作関数を正しく合成します.
class CString
{
public:
CString()
{
m_str = strdup("");
}
CString(const char *str)
{
m_str = strdup(str);
}
CString(const CString &cstr)
{
m_str = strdup(cstr.m_str);
}
CString &operator =(const CString &cstr){
delete m_str;
m_str = strdup(cstr.m_str);
return *this;
}
~CString()
{
delete m_str; m_str= NULL;
}
private:
char *m_str;
};
(gdb) disassemble main
Dump of assembler code for function main(int, char**):
...
0x0000000000400677 : callq 0x4007a6 <:serverconfig const="">
0x000000000040067c : lea -0x20(%rbp),%rdx
0x0000000000400680 : lea -0x30(%rbp),%rax
0x0000000000400684 : mov %rdx,%rsi
0x0000000000400687 : mov %rax,%rdi
0x000000000040068a : callq 0x4007e0 <:operator const="">
...
コピーが許可されていないことを明確にする場合は、コピー構造と付与操作関数を禁止する必要があります.次のようにベースクラスのコピーを禁止すればよい.
class IUncopyable
{
public:
~IUncopyable(){};
private:
IUncopyable(IUncopyable &);
IUncopyable & operator=(const IUncopyable&);
};