UNIX下对象析构在多进程中的行为分析

fork ( )的主要任务是初始化要创建进程的数据结构,其主要的步骤有 (以下内容取自joyfire笔记 )
1 )申请一个空闲的页面来保存task_struct;
2 )查找一个空的进程槽(find_empty_process ( ))
3 )为kernel_stack_page申请另一个空闲的内存页作为堆栈;
4 )将父进程的LDT表拷贝给子进程;
5 )复制父进程的内存映射信息;
6 )管理文件描述符和链接点。

那么,若在父进程中创建了一个对象,则经过fork,对象会有什么特殊表现呢?看看下面的程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

class CA
{

public :
        CA (){ printf ( "construct CA/n" ); }
        ~
CA () { printf ( "destruct CA/n" ); }
};


int  main ( int argc ,  char  **argv )
{

        pid_t pid ;
        pid_t wpid ;
        int status ;

        CA a ;

        pid  = fork ();

        if  ( pid  == (pid_t )- 1  )
        {

                fprintf (stderr ,  "%s: Failed to fork()/n" , strerror (errno ));
                exit ( 13 );
        }

        else if  ( pid  ==  0 )
        {

                printf ( "PID %ld: Child started, parent is %ld./n" ,
                        (
long )getpid (),
                        (
long )getppid ());
                return  0 ;
        }


        printf ( "PID %ld: Started child PID %ld./n" ,
                (
long )getpid (),
                (
long )pid );

        wpid  = wait (&status );
        if  ( wpid  == (pid_t )- 1  )
                perror ( "wait(2)" );

        return  0 ;
}

以下是程序某次运行结果:
construct CA
PID  29423 : Child started , parent is  29422.
destruct CA
PID  29422 : Started child PID  29423.
destruct CA
结果显示:对象被构造了一次,但是被析构了两次。
为什么会这样呢?这正是fork使得子进程复制父进程的内存映射信息的结果。对于类似这样的代码:
void  main ()
{

    CA a ;  //CA是一个类
}
其等价的汇编代码大概是下面这样:
10 :    void  main ()
11 :   {
00401030   push        ebp
00401031   mov         ebp ,esp
00401033   sub         esp ,44h
00401036   push        ebx
00401037   push        esi
00401038   push        edi
00401039   lea         edi ,[ebp -44h ]
0040103C   mov         ecx ,11h
00401041   mov         eax ,0CCCCCCCCh
00401046   rep stos    dword ptr  [edi ]
12 :       CA a ;
00401048   lea         ecx ,[ebp - 4 ]
0040104B   call        @ILT + 0 (CA ::CA ) ( 00401005 )
13 :   }
00401050   lea         ecx ,[ebp - 4 ]
00401053   call        @ILT + 5 (CA ::~CA ) (0040100a )
00401058   pop         edi
00401059   pop         esi
0040105A   pop         ebx
0040105B   add         esp ,44h
0040105E   cmp         ebp ,esp
00401060   call        __chkesp  ( 00401120 )
00401065   mov         esp ,ebp
00401067   pop         ebp
00401068   ret
也就是说,何时插入构造 /析构函数调用代码,完全是在编译期间确定的。fork后,子进程由于完全拷贝了父进程内存映射信息(含代码段和调用堆栈信息),将继续执行调用堆栈指定的指令,因此,后面的call        @ILT + 5 (CA ::~CA ) (0040100a )将被两次执行。
fork的以上特点有时被用于在父子进程间传递信息(由父进程 ->子进程,这种传递是单向的)。

那么,我们如何屏蔽上面重复输出的析构消息呢,通过在网上的讨论,有以下建议:
1、上策:不要在fork 中使用可重入的语句,诸如printf;
2、下策:在CA中添加一个标志,并在析构函数中根据该标志进行输出;
对于第二方案,修改后的程序如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

class CA
{

public :
        CA ();
        ~
CA ();

        int _childFlag ;
};


CA ::CA ()
{

        _childFlag  =  0 ;
        printf ( "construct CA/n" );
}


CA ::~CA ()
{

        if  (!_childFlag )
        {

                printf ( "destruct CA/n" );
        }
}


int  main ( int argc ,  char  **argv )
{

        pid_t pid ;
        pid_t wpid ;
        int status ;

        CA a ;

        pid  = fork ();

        if  ( pid  == (pid_t )- 1  )
        {

                fprintf (stderr ,  "%s: Failed to fork()/n" , strerror (errno ));
                exit ( 13 );
        }

        else if  ( pid  ==  0 )
        {

                a ._childFlag  =  1 ;
                printf ( "PID %ld: Child started, parent is %ld./n" ,
                        (
long )getpid (),
                        (
long )getppid ());
                return  0 ;
        }


        printf ( "PID %ld: Started child PID %ld./n" ,
                (
long )getpid (),
                (
long )pid );

        wpid  = wait (&status );
        if  ( wpid  == (pid_t )- 1  )
                perror ( "wait(2)" );

        return  0 ;
}


附注:以上是一个很无聊的话题,但几周前被人问及,特记录于此。

你可能感兴趣的:(UNIX下对象析构在多进程中的行为分析)