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 ;
}


附注:以上是一个很无聊的话题,但几周前被人问及,特记录于此。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 34
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值