tao 的个人资料闲中看月照片日志 工具 帮助
4月13日

说说已经有malloc函数了为何还要引进new

一道考题:请说说已经有malloc函数了为何还要引进new?

答:当用在内置数据类型或者结构时,malloc能满足我们的需要,但用在类类型时不能,这就需要引用new,

new既分配堆内存,又自动调用类的构造函数来创建对象。

是本教科书都有提到上面这一点,可是这些公司为何还乐此不疲的考到这到道题呢?事情不是这么简单,我献

丑说这几句。

一、new 与 opeartor new

   首先问大家一个问题:new 与 opeartor new有何区别?
如果你回答:operator new就是new的重载运算符呗!

回答错误,new是C++内部定义的一种操作符,总像sizeof一样是一种操作符,而operator new是实作者定义的

一个全局函数。不信,你可以写下面的语句:

int *p = operator new(  //这时候,用的是VC的话,就会自动提示该函数的原型。嗯,有七个版本之多。好

,现在我们弄清楚了,一个是操作符,一个是全局函数,而不是原来所以为的是重载的关系。正同我们看看他

们各有什么用?

new操作符作了三件事.语句:

   MyClass * p = new MyClass;
的伪码大至如下:

void *memory = operator new(sizeof(MyClass)); //1调用全局函数: operator new
MyClass::MyClass();              //2调用构造函数创建对象,如带参数的话                            

      //调用的就是带参构造函数

MyClass * p = static_cast<MyClass*>(memory); //3转换指针类型,并赋给p


我们可以猜测到真正的内存分配是在operator new函数中完成的。

operator new函数其之的原型是:
void* operator new(size_t size) throw(std::bad_alloc)
返回类型是void* ,  参数类型是:size_t ,是一个在系统头文件 <cstddef>中定义的typedef。sizeof()的

操作结果就是该类型的。

void* operator new(size_t size)大至如下:

void *p ;
while((p = malloc(size)) == 0)
{
    //内存分配失败。
   if ( _callnewh(size) == 0)
   {
         _Nomemory();
   }
}

return p;

如果内存分配成功则直接返回所分配的内存的首地址。不然就循环调用if语句,首先判断 _callnewh(size)调

用是否成功,不成功则调用_Nomemory(),_Nomemory()实际上作用是抛出一个异常,所抛出的是一个标准库中

定义的std::bad_alloc对象。_callnewh()函数首先会判断一个全局变量 _pnhHeap是否为零, _pnhHeap存放的

是一个函数指针,我们可以为new操作指定一个出错处理函数,也就是说当new分配内存失败时就会调用我们刚

才指定的函数,出错处理函数原型必须是无参且返回类型是void的函数。可以通过全局函数set_new_handler

(new_handler pnew)来设定我们的出错处理函数,而他又是通过调用_PNH _set_new_handler(_PNH pnh)来实现

设定我们的出错处理函数的。下面是_PNH _set_new_handler(_PNH pnh)的定义:

_PNH __cdecl _set_new_handler(
       _PNH pnh
       )
{
       _PNH pnhOld;

       /* lock the heap */
       _mlock(_HEAP_LOCK);

       pnhOld = _pnhHeap;  //把原来在起作用的出错处理函数的指针赋给pnhOld
       _pnhHeap = pnh;     //我们新设定的出错处理函数的指针。

       /* unlock the heap */
       _munlock(_HEAP_LOCK);

       return(pnhOld);     //返回原来旧的出错处理函数的指针。
}


回到我们的_callnewh()函数,如果在MyClass * p = new MyClass;语句之前设定了出错处理函数,那么这里的

_pnhHeap就不为零,接着就会调用(*_pnhHeap)()即我们的出错处理函数,否则返回零。接着调用_Nomemory()

抛出异常。这个出错处理函数是个重要的函数,设计的好的话可以做很多事情,因为他是在while中被调用的有

多次被调用的机会。在我们的出错处理函数中如果没有使用exit(1)等退出程序的语句,也没有抛出异常的话,

执行完我们的出错处理函数后,又回到while((p = malloc(size)) == 0),再次分配内存并判断,还是失败的

话,再次调用我们的出错处理函数,当然这个出错处理函数和上面的那个出错处理函数不一样了,因为我们可

以在上面那个出错处理函数中调用set_new_handler(_PNH pnh)重新设定一个出错处理函数,也就是我们第二次

调用的这个出错处理函数。还不行的话,可以继续循环,直到你满意为止。如果你觉得累了,不玩了,最后就

会调用_Nomemory()抛出异常,函数返回到new调用的地方。

 好,现在我们清楚了操作符new作了三件事,首先调用全局operator new函数, 后者通过调用传统的malloc

函数分配内存,如果成功直接返回,不然,判断出错处理函数是否为零,不为零的话,调用我们的出错处理函

数。否则调用_Nomemory()抛出异常。如果p = malloc(size)成功,new接着做第二件事,创建对象,最后转换

指针类型并返回。

我们可以重写operator new函数。当编译器看到语句MyClass * p = new MyClass;

首先会检查我们的类定义看是否提供有operator new函数,如有,则调用该函数,接着调用构造函数,转换类

型并返回。如果没有重写operator new函数,则new操作符会调用全局中的那个operator new函数,也就是我们

上面说的这个函数。但是如果我们在new操作符前面限定了::即这样写 ::new MyClass则编译器不会去检查我们

的类的定义而直接调用全局的operator new函数。

   操作符new不可以重载,就像sizeof操作符一样是不可以重载的。我们重载的是operator new函数。所以有

一些限定,我们重载的operator new函数的返回类型必须是void*,第一个参数必须是size_t类型的。下面是一

个自定义的operator new函数:

class MyClass
{
 public:
    MyClass()
    {
      cout << "MyClass::MyClass()" << endl;
     }

   static void* operator new(size_t size);

 ~MyClass()
 {
    cout << "MyClass::~MyClass()" << endl;
 }
};

void* MyClass::operator new(size_t size)
{
  //在这里可以对类的静态成员数据做些控制。我们在这里有一句输出语句代替。
 
  cout << "MyClass::operator new" << endl;
 
  void* p = new MyClass;

  return p;
}
   
这样写是行不通的,因为在MyClass::operator new中的void* p = new MyClass的new是操作符new,他做三件

事,第一件就是调用MyClass::operator new(size_t size),所以这里
是递归调用了。把程序改成:

void* MyClass::operator new(size_t size)
{
  //在这里可以对类的静态成员数据做些控制。我们在这里有一句输出语句代替。
  cout << "MyClass::operator new" << endl;
 
  void* p = operator new(size);  //已修改。

  return p;
}

这样还是不行的,这样是直接递归(自己调用自己)。刚才是间接递归。应该改成:void* p = ::operator new

(size);  OK,使用的是全局中的operator new,或者写成:void* p = malloc( size),只是这样一来,出错后

不会自动调用出错处理函数了,只会简单的返回NULL,所以在使用new操作符的地方要注意先检测返回值是否为

零,所以最好不用malloc,还是用:: operator new(size)好,这里还可以用void* p = new char[size],用的

是new[]操作符,不会两次调用构造函数,也不会造成递归。只是要注意在我们重写的operator delete函数中

要调用delete[] 后释放。一般情况下,我们重写了operator new函数,都要重写operator delete函数,而且

后者中的释放资源的函数要与前者分配资源的函数的形式要搭配。


另外,要想把自己重写的operator new函数设计得好,还是有好些地方需要注意的。好在需要我们重写这个函

数的情况不多,真正需要重写时,还是先参考些这方面的资料才行,<effective c++>一书中就有相关的知识介

绍。在这里我只是提到一下,让大家知道有这么一回事,应付一下这道公司们乐此不疲的考题。洋洋洒洒写上

上千字,小样,看你还敢不敢考这样的考题。


二、new[] 与 operator new[]

 new[]操作符与new差不多,同样做三件事:调用operator new[]函数,历遍一个vector调用构造函数。转换

指向首地址的指针类型并返回。

operator new[]函数通过把操作符new A[number]中的A与number进行计算:size_t count = number * sizeof

(A), 然后调用全局函数operator new(count).

三 new(void*) Myclass 与 operator new(size_t, void*)
 
  指定位置创建操作符new()同样做三件事,第一件就是调用operator new(size_t, void*)函数,下面两件和

new操作符的最后二件事是一样的。让我们来看看vs.net中operator new(size_t, void*)的定义:

inline void *__cdecl operator new(size_t, void *_Where) _THROW0()
   {    // construct array with placement at _Where
   return (_Where);
   }

和operator new相比好简单哦,我们看到了,他并没有调用malloc函数,也没有调用operator new函数,他怎

么分配的内存啊?!对于operator new函数,他通过循环调用malloc函数来分配一块内存,最好把这块分配好的

内存return p返回给操作符new,让他在上面做第二,第三件事。我们这里return (_Where);按此推理,_Where
6应该指向一块已分配的可使用的内存。_Where从那里来的啊?答案是使用操作符new(void* _Where) MyClass

时所指定的。这就是指定位置创建操作符new()的用法,先在别处分配好一块内存,然后把这块内存的首地址做

为参数调用new(),new()就会在这块指定位置上创建对象,然后再把这块指定的内存的首地址自制一份给p,接

着转换类型并返回。这样子操作符new()并没有真正分配内存,所以不能调用delete来释放内存。当程度使用共

享内存或者memory-mapped I/O指定位置创建就比较有用,因为在这样的程序里对象必须放置在一个确定的地址

上或者一块被例程分配的内存里。下面看个例子。


#include <iostream>
#include <new>                     //要使用指定位置创建操作符发布包含该头文件。
using namespace std;

void* mallocShared(size_t size);  //用于分配共享内存,该函数是别的程序员写的,你只知道通过
                                  //调用他可以获得一块已分配而未初始化的内存。

class A
{
  public:
  A()
  {
     cout << "A::A()" << endl;
     m_n = 0;
  }

  int Get()
  {
     return m_n;
  }

  ~A()
  {
     cout << "A::~A()" << endl;
  }

private:
int m_n;
};

int main()
{
   void* p = mallocShared(sizeof(A)); //该句也有可能是在别的地方调用的,
                                      //然后把p传过来。这里为了简化而放在此调用

   A* pA = new(p) A;

   cout << pA->Get() << endl;

   delete pA;

   return 0;
}


////////////////////////////////////////////////////////////////////////////////////

/* 下面是另一个程序员在一个模块中写的mallocShared()函数,
 你不知道其具体实现,只是通过导出该函数来使用。*/


void* mallocShared(size_t size)
{
   void* p = malloc(size);
   
   if ( p == NULL)
   {
       cerr << "mallocShared(size_t size) failed!" << endl;

       exit(1);
   }

   return p;
}

这个程序有问题吗?抛开设计上的好坏不说,就说这个程序能否通过编译?如能,运行结果如何?请先思考三

分钟再往下看。


有疑问的可能是这一句delete pA; 函数mallocShared不是你写的,你不知道其内部是通过什么形式分配的内存

,就调用delete来释放能行吗?假如又让我们知道他内部是通过malloc函数来分配的内存,用delete来释放能

行吗?答案是:能通过编译,并能得出正确结果,通过malloc函数来分配的内存用delete来释放是没问题的,

new操作符不也是通过malloc来分配的内存,同样可以用delete来释放啊。如果mallocShared是下面这样子,情

况又怎么样呢?

void* mallocShared(size_t size)
{
   
   return (new char[size]);
}

结果是和上面是一样的,delete p没问题,只要用new[]来分配的内存块的大小和用delete释放的内存的大小是

一样的就没问题。回想一下看,我们用new A来分配内存时,实际上是通过operator new(size)来分配的,这里

的size = sizeof(A),这种情况下我们可以用 delete p来释放,只要p 的类型是A*,因为delete 是通过调用

operator delete(size)函数来释放的内存,这里的size也是等于sizeof(P),当我们调用new char[number]时

,先调用operator new[]函数,后者实际上也是调用 operator new(number* sizeof(char))来分配的内存,既

然都是通过调用operator new来分配的内存,所以调用delete 来释放应该也是没问题的。因为这

里:mallocShared(sizeof(A)) ---->  size == sizeof(A) == size* sizeof(char) == sizeof(P) 大小一样。
改成:

void* mallocShared(size_t size)
{
   
   return (operator new(size));
}

结果也是一样的! 当然这里是假设没有提供自己的operator new函数的情况下,如果重写了operator new函数

就要改成:

void* mallocShared(size_t size)
{
   
   return (::operator new(size));   //用的是全局函数
}

这个程序目前没问题,但是存在很多的安全隐患,很容易就出错,一不小心就阴沟里翻船,有“未定义”行为

产生,结果是啥事都有可能发生。应该谢绝写这样的程序。

上面说了mallocShared(sizeof(A));可能不是在你的程序中调用的,而是在别人那里调用的,然后别人给你传

来一个指针让你把这个指针作为参数调用你自己的A* pA = new(p) A,这样子你调用delete p来释放,别人那里

或者还需要用,因为这是共共享的内存,又或者别人那里在做完想要做的工作之后,调用delete ,这样子就出

问题了。同一块内存不能释放两次。就算mallocShared(sizeof(A));是在你这里调用的,那么你可以算是内存

的分配者,你有权利and义务把他释放,可是你也要先确定别人还需不需要用到这块内存,需要的话,你就不能

马上delete,又或者呆会你自己也还需要用到,再次在这块内存上指定位置创建。所以就不必再调用

mallocShared(sizeof(A));来分配内存。把主函数改成下面这样,结果又如何,能通过编译吗?

int main()
{
   void* p = mallocShared(sizeof(A)); //就限定是在这里调用的。

   A* pA = new(p) A;

   cout << pA->Get() << endl;

   A* pA1 = new(p) A;                 //再次指定位置创建。



   delete pA;

   return 0;
}

答案是:能通过编译,运行结果如下:

A::A()
0
A::A()
~A::A()

原来的那块内存是确实被释放掉了的,只是这里构造函数A::A()调用了两次而析构函数A::~A()只调用一次,
这显然不太好,如果你的类在其他地方分配了资源,需要通过析构函数来释放,这样子你少调用了一次析构函

数就会造成内存泄漏或者别的问题了。所以应该把程序改成下面这样:


int main()
{
   void* p = mallocShared(sizeof(A)); //就限定是在这里调用的。

   A* pA = new(p) A;

   cout << pA->Get() << endl;

   A* pA1 = new(p) A;                 //再次指定位置创建。

       pA->~A();         //这里显式调用析构函数来析构对象,但是内存并没有释放,还可以再次使用。

   A* pA1 = new(p) A;

      //在这里判断别的程序是否还需要用到该内存

   delete pA;      //当别人不再需要,自己也不会再用到,可以释放!

   return 0;
}

这句delete p总让我担心受怕,最好调用和mallocShared函数相对应的函数来释放内存,你写了mallocShared

函数来分配资源就有义务写一个freeShared函数来释放资源,分配资源函数和释放资源函数是一对的,一起提

供给别人使用。因为只有你自己最清楚你的mallocShared函数是怎么分配的资源,你的freeShared就应该做相

应的工作。比如在mallocShared中除了分配内存,还用到其他资源,如果直接调用delete p来释放那就成问题

了。应该调用freeShared来释放。如果你是老板而你的员工只写一个mallocShared函数却没有提供相应的

freeShared函数,建议你让他走人! 不然迟早会出问题的。如果void* p = mallocShared(sizeof(A)); 语句不

是在这里调用的,你既不能使用delete p,也不能使用freeShared(p),或者其他一切释放资源的函数。不是你

分配的资源你无权释放。当然你对整个程序把握得比较好,一切尽在你控制中,而你又和别人有协议由你来释

放的情况除外。

void * operator new(size_t, void *_Where)同样可以提供自己的版本,这时候第二个参数可以是别的类型,

_Where也不一定是指向一块已分配而未使用的内存,可以是一个指向可以分配内存的函数的指针,然后在

operator new(size_t, void *_Where)内部通过该指针来调用函数从而分配内存。也可以是其他东西,不一定

要是指针。总之可以传递你想传递的东西。下面来看个例子,这个例子来自<Bjarne Stroustrup的FAQ:C++的

风格与技巧>我懒得写了,就用他写的这个.

以下是原文:

////////////////////////////////////////////////////////////////////////////

有没有“指定位置删除”(placement delete)?



没有,不过如果你需要的话,可以自己写一个。



看看这个指定位置创建(placement new),它将对象放进了一系列Arena中;



class Arena {

public:

void* allocate(size_t);

void deallocate(void*);

// ...

};



void* operator new(size_t sz, Arena& a)

{

return a.allocate(sz);  //梁杨注:这里第二个参数传递的是一个引用,
                                       //然后通过其成员函数来分配内存.

}



Arena a1(some arguments);

Arena a2(some arguments);



这样实现了之后,我们就可以这么写:



X* p1 = new(a1) X;

Y* p2 = new(a1) Y;

Z* p3 = new(a2) Z;

// ...



但是,以后怎样正确地销毁这些对象呢?没有对应于这种“placement new”的内建的

“placement delete”,原因是,没有一种通用的方法可以保证它被正确地使用。在C++的

类型系统中,没有什么东西可以让我们确认,p1一定指向一个由Arena类型的a1分派的对象

。p1可能指向任何东西分派的任何一块地方。



然而,有时候程序员是知道的,所以这是一种方法:



template<class T> void destroy(T* p, Arena& a)

{

 if (p) {

      p->~T(); // explicit destructor call

      a.deallocate(p);

}

}



现在我们可以这么写:



destroy(p1,a1);

destroy(p2,a2);

destroy(p3,a3);



如果Arena维护了它保存着的对象的线索,你甚至可以自己写一个析构函数,以避免它发生

错误。
  这也是可能的:定义一对相互匹配的操作符new()和delete(),以维护《C++程序设计语

言》15.6中的类继承体系。参见《C++语言的设计和演变》10.4和《C++程序设计语言》

19.4.5。

///////////////////////////////////////////////////////////////////////////////

////////////////

C++中有指定位置创建操作符:new(),但没有指定位置删除操作符:delete()。

我们可以写:
               X* p1 = new(a1) X;  //指定位置创建
但是不可以写:
               delete(a1) p1       //这句不能通过编译。

他上面写的这个程序其实是有点问题的(靠,梁杨你也太牛B了吧,C++之父写的你也敢说有问题?!),用事实说话

,如果只提供 void* operator new(size_t sz, Arena& a)而不提供void operator delete(void* , Arena&)
函数,vs.net的编译器会提出一条警告: “void *operator new(size_t,Arena &)” : 未找到匹配的删除运

算符;如果初始化引发异常,则不会释放内存。

对于new操作符会作三件事,在第一件分配内存顺利完成之后,接着会调用构造函数,如果在调用构造函数中发

生异常,他就会调用operator delete(void*)函数来释放在第一件事中通过operator new(size_t)来分配的内

存,从而保证不会发生内存泄漏。同样定位创建操作符new()也做三件事:第一件调用我们重写的void*

operator new(size_t sz, Arena& a)来分配内存,这步成功之后接着调用构造函数,那么如果在调用构造函数

中发生异常时,怎么办呢?没有相应的释放函数来给编译器调用,所以我们必须自己提供一个void operator

delete(void* , Arena&)来释放内存。这个函数第一个参数的类型必须是void*, 第二个类型必须和operator

new()中的相同,不然当发生异常时,编译器不会调用该函数的。当我们提供了void operator delete(void* ,

Arena&)后还是不能写:delete(a1) pA1这样的语句的。可以写operator delete(pA1, a1),但是最好不要这样

调用,该函数是专为编译器写的,当发生构造异常时调用的。当我们成功的创建一个对象,做完一些事之后,

应该还象他上面写的那样,通过调用destroy(p1,a1);来释放。

注意这里内存分配是在指定创建函数operator new()中调用其他函数来分配的,所以我们需要提供一个相应的

delete( )来预防构造失败时来释放资源。但是在我们前面写的那个共享内存的例子,定位创建函数new()并没

有分配内存,内存分配是在其他地方完成的,这时候我们就不需要提供定位删除函数delete()来释放资源,你

构造函数失败就失败呗,内存又不是你分配的,你无权释放,不可能说你创建一个对象失败了,连内存都释放

了,呆会有权释放内存者(分配者有权释放)再释放一次,那问题就大了。也就是说当我们重写定位创建函数

new()时,如果内存是在其中分配的,那么就要提供相应的delete()函数给编译器专用。反之则不必提供

delete()函数。



四、new(nothrow) MyClass操作符

从字面就可以看出来了,这个版本的new不会抛出异常。他也做三件事,其中第一件调用的就是不抛出异常的

operator new()函数,其原型是:

void *__cdecl operator new(size_t, const std::nothrow_t&)
   _THROW0();

看到了,其后面的异常规范是空的,即不抛出任何异常。与常用的那个operator new()函数不同

void *__cdecl operator new(size_t) _THROW1(std::bad_alloc); ,这个可以抛出std::bad_alloc异常。
不允许抛出异常不并代其内部也不会发生异常,当内部内存分配失败时发生异常,因为规范中不允许抛出异常

,这就会抛出一个意外的异常,如果用户不对这个意外异常进行捕捉的话,默认处理是调用abort函数。

五、 new const MyClass

这个版本的new表示在堆内存中创建后个const对象,创建完之后就不能再修改,所以必须在创建时初始化(有无

参构造函数的类除外),并且返回的指针是一个指向const对象的指针,即要写:const int* p = new const

int(1); 而不能写int* p = new const int(1).


好累啊,写了这么多,没精力回头检察了,可能会有一些输入错误,只能请大家见谅了。头好痛。
4月11日

深入浅出Win32多线程程序设计之基本概念

引言

  从单进程单线程到多进程多线程是操作系统发展的一种必然趋势,当年的DOS系统属于单任务操作系统,最优秀的程序员也只能通过驻留内存的方式实现所谓的"多任务",而如今的Win32操作系统却可以一边听音乐,一边编程,一边打印文档。

  理解多线程及其同步、互斥等通信方式是理解现代操作系统的关键一环,当我们精通了Win32多线程程序设计后,理解和学习其它操作系统的多任务控制也非常容易。许多程序员从来没有学习过嵌入式系统领域著名的操作系统VxWorks,但是立马就能在上面做开发,大概要归功于平时在Win32多线程上下的功夫。

  因此,学习Win32多线程不仅对理解Win32本身有重要意义,而且对学习和领会其它操作系统也有触类旁通的作用。

   进程与线程

  先阐述一下进程和线程的概念和区别,这是一个许多大学老师也讲不清楚的问题。

  进程(Process)是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。程序只是一组指令的有序集合,它本身没有任何运行的含义,只是一个静态实体。而进程则不同,它是程序在某个数据集上的执行,是一个动态实体。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消,反映了一个程序在一定的数据集上运行的全部动态过程。

  线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

  线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。

  根据进程与线程的设置,操作系统大致分为如下类型:

  (1)单进程、单线程,MS-DOS大致是这种操作系统;

  (2)多进程、单线程,多数UNIX(及类UNIX的LINUX)是这种操作系统;

  (3)多进程、多线程,Win32(Windows NT/2000/XP等)、Solaris 2.x和OS/2都是这种操作系统;

  (4)单进程、多线程,VxWorks是这种操作系统。

  在操作系统中引入线程带来的主要好处是:

  (1)在进程内创建、终止线程比创建、终止进程要快;

  (2)同一进程内的线程间切换比进程间的切换要快,尤其是用户级线程间的切换。另外,线程的出现还因为以下几个原因:

  (1)并发程序的并发执行,在多处理环境下更为有效。一个并发程序可以建立一个进程,而这个并发程序中的若干并发程序段就可以分别建立若干线程,使这些线程在不同的处理机上执行。

  (2)每个进程具有独立的地址空间,而该进程内的所有线程共享该地址空间。这样可以解决父子进程模型中,子进程必须复制父进程地址空间的问题。

  (3)线程对解决客户/服务器模型非常有效。

   Win32进程

  1、进程间通信(IPC)

  Win32进程间通信的方式主要有:

  (1)剪贴板(Clip Board);

  (2)动态数据交换(Dynamic Data Exchange);

  (3)部件对象模型(Component Object Model);

  (4)文件映射(File Mapping);

  (5)邮件槽(Mail Slots);

  (6)管道(Pipes);

  (7)Win32套接字(Socket);

  (8)远程过程调用(Remote Procedure Call);

  (9)WM_COPYDATA消息(WM_COPYDATA Message)。

  2、获取进程信息

  在WIN32中,可使用在PSAPI .DLL中提供的Process status Helper函数帮助我们获取进程信息。

  (1)EnumProcesses()函数可以获取进程的ID,其原型为:

BOOL EnumProcesses(DWORD * lpidProcess, DWORD cb, DWORD*cbNeeded);
  参数lpidProcess:一个足够大的DWORD类型的数组,用于存放进程的ID值;

  参数cb:存放进程ID值的数组的最大长度,是一个DWORD类型的数据;

  参数cbNeeded:指向一个DWORD类型数据的指针,用于返回进程的数目;

  函数返回值:如果调用成功,返回TRUE,同时将所有进程的ID值存放在lpidProcess参数所指向的数组中,进程个数存放在cbNeeded参数所指向的变量中;如果调用失败,返回FALSE。

  (2)GetModuleFileNameExA()函数可以实现通过进程句柄获取进程文件名,其原型为:

DWORD GetModuleFileNameExA(HANDLE hProcess, HMODULE hModule,LPTSTR lpstrFileName, DWORD nsize);
  参数hProcess:接受进程句柄的参数,是HANDLE类型的变量;

  参数hModule:指针型参数,在本文的程序中取值为NULL;

  参数lpstrFileName:LPTSTR类型的指针,用于接受主调函数传递来的用于存放进程名的字符数组指针;

  参数nsize:lpstrFileName所指数组的长度;

  函数返回值:如果调用成功,返回一个大于0的DWORD类型的数据,同时将hProcess所对应的进程名存放在lpstrFileName参数所指向的数组中;加果调用失败,则返回0。

  通过下列代码就可以遍历系统中的进程,获得进程列表:

//获取当前进程总数
EnumProcesses(process_ids, sizeof(process_ids), &num_processes);
//遍历进程
for (int i = 0; i < num_processes; i++)
{
 //根据进程ID获取句柄
 process[i] = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0,
 process_ids[i]);
 //通过句柄获取进程文件名
 if (GetModuleFileNameExA(process[i], NULL, File_name, sizeof(fileName)))
  cout < < fileName < < endl;
}


Win32线程

  WIN32靠线程的优先级(达到抢占式多任务的目的)及分配给线程的CPU时间来调度线程。WIN32本身的许多应用程序也利用了多线程的特性,如任务管理器等。

  本质而言,一个处理器同一时刻只能执行一个线程("微观串行")。WIN32多任务机制使得CPU好像在同时处理多个任务一样,实现了"宏观并行"。其多线程调度的机制为:

  (1)运行一个线程,直到被中断或线程必须等待到某个资源可用;

  (2)保存当前执行线程的描述表(上下文);

  (3)装入下一执行线程的描述表(上下文);

  (4)若存在等待被执行的线程,则重复上述过程。

  WIN32下的线程可能具有不同的优先级,优先级的范围为0~31,共32级,其中31表示最高优先级,优先级0为系统保留。它们可以分成两类,即实时优先级和可变优先级:

  (1)实时优先级从16到31,是实时程序所用的高优先级线程,如许多监控类应用程序;

  (2)可变优先级从1到15,绝大多数程序的优先级都在这个范围内。。WIN32调度器为了优化系统响应时间,在它们执行过程中可动态调整它们的优先级。

  多线程确实给应用开发带来了许多好处,但并非任何情况下都要使用多线程,一定要根据应用程序的具体情况来综合考虑。一般来说,在以下情况下可以考虑使用多线程:

  (1)应用程序中的各任务相对独立;

  (2)某些任务耗时较多;

  (3)各任务需要有不同的优先级。

  另外,对于一些实时系统应用,应考虑多线程。

   Win32核心对象

  WIN32核心对象包括进程、线程、文件、事件、信号量、互斥体和管道,核心对象可能有不只一个拥有者,甚至可以跨进程。有一组WIN32 API与核心对象息息相关:

  (1)WaitForSingleObject,用于等待对象的"激活",其函数原型为:

DWORD WaitForSingleObject(
 HANDLE hHandle, // 等待对象的句柄
 DWORD dwMilliseconds // 等待毫秒数,INFINITE表示无限等待
);

  可以作为WaitForSingleObject第一个参数的对象包括:Change notification、Console input、Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread和Waitable timer。

  如果等待的对象不可用,那么线程就会挂起,直到对象可用线程才会被唤醒。对不同的对象,WaitForSingleObject表现为不同的含义。例如,使用WaitForSingleObject(hThread,…)可以判断一个线程是否结束;使用WaitForSingleObject(hMutex,…)可以判断是否能够进入临界区;而WaitForSingleObject (hProcess,… )则表现为等待一个进程的结束。

  与WaitForSingleObject对应还有一个WaitForMultipleObjects函数,可以用于等待多个对象,其原型为:

DWORD WaitForMultipleObjects(DWORD nCount,const HANDLE* pHandles,BOOL bWaitAll,DWORD dwMilliseconds);
  (2)CloseHandle,用于关闭对象,其函数原型为:

BOOL CloseHandle(HANDLE hObject);
  如果函数执行成功,则返回TRUE;否则返回FALSE,我们可以通过GetLastError函数进一步可以获得错误原因。

   C运行时库

  在VC++6.0中,有两种多线程编程方法:一是使用C运行时库及WIN32 API函数,另一种方法是使用MFC,MFC对多线程开发有强大的支持。
标准C运行时库是1970年问世的,当时还没有多线程的概念。因此,C运行时库早期的设计者们不可能考虑到让其支持多线程应用程序。
Visual C++提供了两种版本的C运行时库,-个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库有两个重大差别:

  (1)类似errno的全局变量,每个线程单独设置一个;

  这样从每个线程中可以获取正确的错误信息。

  (2)多线程库中的数据结构以同步机制加以保护。

  这样可以避免访问时候的冲突。

  Visual C++提供的多线程运行时库又分为静态链接库和动态链接库两类,而每一类运行时库又可再分为debug版和release版,因此Visual C++共提供了6个运行时库。如下表:


C运行时库                                 库文件

Single thread(static link)              libc.lib

Debug single thread(static link)        Libcd.lib

MultiThread(static link)                libcmt.lib

Debug multiThread(static link)          libcmtd.lib

MultiThread(dynamic link)               msvert.lib

Debug multiThread(dynamic link)         msvertd.lib

                                        如果不使用VC多线程C运行时库来生成多线程程
                                        序,必须执行下列操作:
                                       (1)使用标准 C 库(基于单线程)并且只允
                                             许可重入函数集进行库调用;
                                       (2)使用 Win32 API 线程管理函数,如 
                                             CreateThread;
                                       (3)通过使用 Win32 服务(如信号量和
                                             EnterCriticalSection 及
                                             LeaveCriticalSection 函数),为
                                             不可重入的函数提供自己的同步。

  如果使用标准 C 库而调用VC运行时库函数,则在程序的link阶段会提示如下错误:
    error LNK2001: unresolved external symbol __endthreadex
    error LNK2001: unresolved external symbol __beginthreadex

4月10日

关于进程通信中的读者-写者问题(写者优先)的例子

#include <windows.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#define  MAX_PERSON   100
#define  READER       0
#define  WRITER       1
#define  END         -1
#define  R            READER
#define  W            WRITER
typedef struct _Person
{
 HANDLE m_hThread;
 int    m_nType;
 int    m_nStartTime;
 int    m_nWorkTime;
 int    m_nID;
}Person;
Person g_Persons[MAX_PERSON];
int    g_NumPerson = 0;
long   g_CurrentTime= 0;
int    g_PersonLists[] = {
                           1, R, 3, 5,
                           2, W, 4, 5,
                           3, R, 5, 2,
                           4, R, 6, 5,
                           END,
      };
int    g_NumOfReading = 0;
int    g_NumOfWriteRequest = 0;
HANDLE g_hReadSemaphore;
HANDLE g_hWriteSemaphore;

void             CreatePersonList(int *pPersonList);
bool             CreateReader(int StartTime,int WorkTime,int ID);
bool             CreateWriter(int StartTime,int WorkTime,int ID);
DWORD  WINAPI    ReaderProc(LPVOID lpParam);
DWORD  WINAPI    WriterProc(LPVOID lpParam);
int main()
{
 g_hReadSemaphore  = CreateSemaphore(NULL,1,100,NULL);
 g_hWriteSemaphore = CreateSemaphore(NULL,1,100,NULL);

 CreatePersonList(g_PersonLists); // Create All the reader and writers
 printf("Created all the reader and writer\n...\n");
 g_CurrentTime = 0;
 while(true)
 {
  g_CurrentTime++;
  Sleep(300); // 300 ms
  printf("CurrentTime = %d\n",g_CurrentTime);
 }
 return 0;
}
void CreatePersonList(int *pPersonLists)
{
 int  i=0;
 int  *pList = pPersonLists;
 bool Ret;
 while(pList[0] != END)
 {
  switch(pList[1])
  {
  case R:
   Ret = CreateReader(pList[2],pList[3],pList[0]);
   break;
  case W:
   Ret = CreateWriter(pList[2],pList[3],pList[0]);
   break;
  }
  if(!Ret)
   printf("Create Person %d is wrong\n",pList[0]);
  pList += 4; // move to next person list
 }
}
DWORD  WINAPI ReaderProc(LPVOID lpParam)
{
 Person *pPerson = (Person*)lpParam;
 // wait for the start time
 while(g_CurrentTime != pPerson->m_nStartTime) {}
 printf("Reader %d is Requesting ...\n",pPerson->m_nID);
 // wait for the write request
 // 这里体现了写着优先!每个读者在申请前先要考虑写者的申请数目,如果有写者
 // 提出了申请,则读者放弃申请,转入等待中!
 while(g_NumOfWriteRequest != 0) {}
 WaitForSingleObject(g_hReadSemaphore,INFINITE);//排斥对g_NumOfReading的访问 
 //此处体现了:一旦读者申请成功,则暂时忽视写者的请求而执行自己的操作,
 //但同时也关闭其他读者的申请。
 //当属于第一个读者时,也要等待写者的操作完成!但是如果不是第一个,则说明有其他
 //读操作,因此忽视写者,等待所有已在操作的写操作完成。
 if(g_NumOfReading ==0)
 {
  WaitForSingleObject(g_hWriteSemaphore,INFINITE);
 }
 g_NumOfReading++;
 ReleaseSemaphore(g_hReadSemaphore,1,NULL);//恢复对g_NumOfReading的访问
 // modify the reader's real start time
 pPerson->m_nStartTime = g_CurrentTime;
 printf("Reader %d is Reading the Shared Buffer...\n",pPerson->m_nID);
 while(g_CurrentTime <= pPerson->m_nStartTime + pPerson->m_nWorkTime)
 {
  // ..  perform read operations
 }
 printf("Reader %d is Exit...\n",pPerson->m_nID);

 WaitForSingleObject(g_hReadSemaphore,INFINITE);
 g_NumOfReading--; 
 //此处体现了:如果在读为当前操作时,若已经有多个读者申请成功,
 //则写者要等这些读者全部退出后才能进行写操作!
 if(g_NumOfReading == 0)
 {
  ReleaseSemaphore(g_hWriteSemaphore,1,NULL);
 }
 ReleaseSemaphore(g_hReadSemaphore,1,NULL);
 ExitThread(0);
 return 0;
}
DWORD  WINAPI WriterProc(LPVOID lpParam)
{
 Person *pPerson = (Person*)lpParam;
 // wait for the start time
 while(g_CurrentTime != pPerson->m_nStartTime) {}
 printf("Writer %d is Requesting ...\n",pPerson->m_nID);
 g_NumOfWriteRequest++;//此处写者申请时不需要考虑读者状态
 WaitForSingleObject(g_hWriteSemaphore,INFINITE);
 // modify the writer's real start time
 pPerson->m_nStartTime = g_CurrentTime;
 printf("Writer %d is Writting the Shared Buffer...\n",pPerson->m_nID);
 while(g_CurrentTime <= pPerson->m_nStartTime + pPerson->m_nWorkTime)
 {
  // ..  perform write operations
 }
 printf("Writer %d is Exit...\n",pPerson->m_nID);
 g_NumOfWriteRequest--;
 ReleaseSemaphore(g_hWriteSemaphore,1,NULL);
 ExitThread(0);
 return 0;
}
bool CreateReader(int StartTime,int WorkTime,int ID)
{
 DWORD dwThreadID;
 if(g_NumPerson >= MAX_PERSON)
  return false;
 Person *pPerson       = &g_Persons[g_NumPerson];
 pPerson->m_nID        = ID;
    pPerson->m_nStartTime = StartTime;
 pPerson->m_nWorkTime  = WorkTime;
 pPerson->m_nType      = READER;
 g_NumPerson++;
 // Create an New Thread
 pPerson->m_hThread = CreateThread(NULL,0,ReaderProc,(LPVOID)pPerson,0,&dwThreadID);
 if(pPerson->m_hThread == NULL)
  return false;
 return true;
}
bool CreateWriter(int StartTime,int WorkTime,int ID)
{
 DWORD dwThreadID;
 if(g_NumPerson >= MAX_PERSON)
  return false;
 Person *pPerson       = &g_Persons[g_NumPerson];
 pPerson->m_nID        = ID;
 pPerson->m_nStartTime = StartTime;
 pPerson->m_nWorkTime  = WorkTime;
 pPerson->m_nType      = WRITER;
 g_NumPerson++;
 // Create an New Thread
 pPerson->m_hThread = CreateThread(NULL,0,WriterProc,(LPVOID)pPerson,0,&dwThreadID);
 if(pPerson->m_hThread == NULL)
  return false;
 return true;
}
 
3月9日

句柄的本质介绍

一、书上定义:

<<Microsoft Windows 3 Developer's Workshop>>(Microsoft Press,by Richard Wilton)
在Windows环境中,句柄是用来标识项目的,这些项目包括:模块(module)、任务(task)、实例 (instance)、文件(file)、内存块(block of memory)、菜单(menu)、控制(control)、字体(font)、资源(resource),包括图标(icon),光标 (cursor),字符串(string)等、GDI对象(GDI object),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域 (region),以及设备描述表(device context)。

<<WINDOWS编程短平快>>(南京大学出版社):

一、书上定义:

<<Microsoft Windows 3 Developer's Workshop>>(Microsoft Press,by Richard Wilton)
在Windows环境中,句柄是用来标识项目的,这些项目包括:模块(module)、任务(task)、实例 (instance)、文件(file)、内存块(block of memory)、菜单(menu)、控制(control)、字体(font)、资源(resource),包括图标(icon),光标 (cursor),字符串(string)等、GDI对象(GDI object),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域 (region),以及设备描述表(device context)。

<<WINDOWS编程短平快>>(南京大学出版社):
句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。

二、MFC源代码:

#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif

DECLARE_HANDLE(HMODULE);
DECLARE_HANDLE(HINSTANCE);
DECLARE_HANDLE(HLOCAL);
DECLARE_HANDLE(HGLOBAL);
DECLARE_HANDLE(HDC);
DECLARE_HANDLE(HRGN);
DECLARE_HANDLE(HWND);
DECLARE_HANDLE(HMENU);
DECLARE_HANDLE(HACCEL);
DECLARE_HANDLE(HTASK);

三、理解:
HANDLE就是PVOID,也就是无类型指针,
上面这些资源的句柄Handles都不过是指向struct的指针,至于这个struct的用处,连M$都说unused了,现在解释下M$这么做的意义,这就是所谓数据封装,你可以在你的程序中把M$的内部结构指针传来传去,可是你却不知道它到底指向的内容是什么。

句柄与指针确实是完全不同的两个概念。句柄仅仅是一个32位整数,WIN32中用于标记某个系统或进程的对象,可以理解为对象索引(由于M$未完全公开相关技术,在一定程度上只能如此理解),这个索引更像是一种映射关系(从句柄到对象指针的映射),而不是纯粹意义上的“数组下标”。

句柄可以理解为用于指向或标识内存的一块“资源”,这些资源如:文件(file)、内存块(block of memory)、菜单(menu)等等。操作系统通过句柄来定位核心对象和系统资源。
指针即为指向内存的“数据或指令”某一单元。

说的确切一点,句柄实际上是一种指向某种资源的指针,但与指针又有所不同:指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针,但不要直接操作它),平时你只是在调用API函数时利用这个句柄来说明要操作哪段内存。


四、引喻:
牧童遥指杏花村
牧童的手为指针,杏花村的牌子为句柄,杏花村酒店为对象的实例.

附注:获得窗口句柄三种方法

1.HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)

HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName)

2.HWND WindowFromPoint(POINT& Point)//获得当前鼠标光标位置的窗口HWND

3.BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)

BOOL CALLBACK EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc,LPARAM lParam)

BOOL CALLBACK EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)

在Windows中所谓句柄不过是系统为了隔离用户直接操作系统中所定义的对象所

给出的一种唯一码而已,其实Java中为了不让用户直接操作对象,采用了引用并

通过HashCode标示对象也是其同一道理

 

是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。

二、MFC源代码:

#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif

DECLARE_HANDLE(HMODULE);
DECLARE_HANDLE(HINSTANCE);
DECLARE_HANDLE(HLOCAL);
DECLARE_HANDLE(HGLOBAL);
DECLARE_HANDLE(HDC);
DECLARE_HANDLE(HRGN);
DECLARE_HANDLE(HWND);
DECLARE_HANDLE(HMENU);
DECLARE_HANDLE(HACCEL);
DECLARE_HANDLE(HTASK);

三、理解:
HANDLE就是PVOID,也就是无类型指针,
上面这些资源的句柄Handles都不过是指向struct的指针,至于这个struct的用处,连M$都说unused了,现在解释下M$这么做的意义,这就是所谓数据封装,你可以在你的程序中把M$的内部结构指针传来传去,可是你却不知道它到底指向的内容是什么。

句柄与指针确实是完全不同的两个概念。句柄仅仅是一个32位整数,WIN32中用于标记某个系统或进程的对象,可以理解为对象索引(由于M$未完全公开相关技术,在一定程度上只能如此理解),这个索引更像是一种映射关系(从句柄到对象指针的映射),而不是纯粹意义上的“数组下标”。

句柄可以理解为用于指向或标识内存的一块“资源”,这些资源如:文件(file)、内存块(block of memory)、菜单(menu)等等。操作系统通过句柄来定位核心对象和系统资源。
指针即为指向内存的“数据或指令”某一单元。

说的确切一点,句柄实际上是一种指向某种资源的指针,但与指针又有所不同:指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针,但不要直接操作它),平时你只是在调用API函数时利用这个句柄来说明要操作哪段内存。


四、引喻:
牧童遥指杏花村
牧童的手为指针,杏花村的牌子为句柄,杏花村酒店为对象的实例.

附注:获得窗口句柄三种方法

1.HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)

HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName)

2.HWND WindowFromPoint(POINT& Point)//获得当前鼠标光标位置的窗口HWND

3.BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)

BOOL CALLBACK EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc,LPARAM lParam)

BOOL CALLBACK EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)

在Windows中所谓句柄不过是系统为了隔离用户直接操作系统中所定义的对象所

给出的一种唯一码而已,其实Java中为了不让用户直接操作对象,采用了引用并

通过HashCode标示对象也是其同一道理

 

3月8日

C++ 实现字符串格式调整

功能:
"AAAAAAAA"     \t\t\t    "BBBBBBBBB"   \t\t    \t "CCCCCCCCCCCC"
--->
"AAAAAAAA"\t"BBBBBBBBB"\t"CCCCCCCCCCC"
 
/**************************************************/
 
void reform(char* Line)
{
// char * tmpLine;
// tmpLine = new char[strlen(Line)+1];
// strcpy(tmpLine, Line);
// memset( Line, 0, strlen(Line));
 char strArray[2048];
 for (int k = 0; k<2048; k++) {
  strArray[k] = '\0';
 }
 
 char* temp = Line;
 
 for (int kk = 0; kk<2048; kk++) {
  strArray[kk] = *(Line+kk);
 }
 
 char* tmpLine = strArray;
 
 while((*tmpLine != ' ') && (*tmpLine != '\t')){
  *Line++ = *tmpLine++;
 }
 *Line++ = '\t';
 while((*tmpLine == ' ') || (*tmpLine == '\t')){
  tmpLine++;
 }

 if (*tmpLine == '\"')
  *Line++ = '\"';
 tmpLine++;
 while(*tmpLine != '\"')
 {
  *Line++ = *tmpLine++;
 }
 *Line++ = '\"';
 *Line++ = '\t';
 tmpLine++;
 while((*tmpLine == ' ') || (*tmpLine == '\t')){
  tmpLine++;
 }
 while((*Line++) = (*tmpLine++))
 {
  ;
 }
 
 Line = temp;
}
6月14日

人和人的差距是在一分一秒钟产生的!

抓紧时间,在一分一秒中超越别人,超越自己!

5月30日

困惑的周末

      快乐的时光总是那么的短暂,以至于我已经感觉不到周末的存在了,只能稍微感觉到身上的一丝疲倦。躺在自己那张狭窄的床上,虽然已经过了子时,但是思绪怎么也平静不下来,深深地困惑在今天的琐事上。

      话题应该扯回到今天下午的南京路上。我是个馋猫,每次来市区闲逛,总会来到这个十字街口吃上几串鸡肉串,那细腻香滑的肉味总能让我陶醉上一段时间。如同往常的我,今天买了5串肉串,递给同学两串后,就开始撕咬起来。买肉串的门口总会放上几个铁箱子,来回收肉钎,因为这里的肉钎是用铁丝做的,需要回收重用。

      正当我的手里还剩一根肉串的时候,人群里过来一个拄着拐杖的老人,步履蹒跚,满头的白发自然不用去说,还有长长的白须因为缺少清洗都粘成了一缕一缕。肩上一个偌大的塑料袋装着他全部的家当。“肯定又是过来要钱的。”我心里如是嘀咕着。但是接下的一幕让我停止了手的动作:他原来弯身去捡铁箱里的那串被人丢掉的肉串,并且就在他捡东西的那刹那,还有人全然不顾地往里面抛铁钎。我心里顿时涌上一个念头:把手中的肉串给他,让他不要去吃那串那么脏的肉串了。可是很惭愧,我没有去做,感觉周围没有一种气氛能够肯定我的行动,感觉我那样的行为一定会引来一群异样的眼光。看着他慢慢远去的背影,那几块鸡肉在我的喉咙艰难地咽下,已经没了当初的那种美味了。

      回到人民广场,在博物馆前闲坐了一个钟头后,又到了我们回嘉定的时间,于是一群人在黄陂北路的一个公交站台上等车来接我们回去。忽然,左边的人群一阵骚动,原来是一只被遗弃的幼猫。旁边一只纸盒子,它在地上乱爬乱叫,浑身瑟瑟发抖。人们七嘴八舌地议论开了,都说谁丢的那个人太狠心了。说的人很多,真正愿意领养的人却是零,只有一个内敛而又清秀的长发女孩觉得它实在是太可怜了,叫我们把它放回盒子,准备把它带走送给宠物市场。“如果没人要它,我来养它吧。”……

      坐在车上,整天的疲倦开始向我的全身发起了进攻。但脑子里总是浮现着那只猫和那个流浪老人,为什么会有这样的差别?难道正是因为他们的差别在于一个是猫,而另一个是人?

 

5月27日

又开始决定写日记了

      记得最后一次写日记是n年前的事了。按理说,自从拥有了电脑和网络,应该更方便于这项操作。可是,每次开始了决定,却总是虚晃一枪,让时间就这样浪费掉了。如今年刚半半百,应该是人生的黄金时间的起点,现在开始记录自己的人生,还应该为时不晚吧?

      桌上案卷如山,但是心里仍是一片倦怠。每次高呼口号,行动却是举步维艰。明年的现在,我的人生应该步上了行程,高中时候的那幅“十年磨一剑”,我觉得更适合现在的我。可是,我现在在磨剑吗?磨的是一把剑吗?我心里没有数!但是我认为现在能做的只有认认真真地磨,只要下了功夫,就是一块钝铁也能成为降龙宝器!

      努力吧!不息吧!有更多的事业等着自己去争取,去努力!切莫白了少年头!