堆和栈的区别以及关于程序结束内存释放问题

一般认为在c中分为这几个存储区
1 栈 – 有编译器自动分配释放
2 堆 – 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
3 全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
 
– 程序结束释放
4另外还有一个专门放常量的地方。 – 程序结束释放在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的”adgfdf”这样的字符串存放在常量区。
比如:
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = “abc”;栈
char *p2; 栈
char *p3 = “123456”; 123456在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, “123456”); 123456放在常量区,编译器可能会将它与p3所指向的”123456″优化成一块。
}
还有就是函数调用时会在栈上有一系列的保留现场及传递参数的操作。栈的空间大小有限定,vc的缺省是2M。栈不够用的情况一般是程序中分配了大量数组和递归函数层次太深。有一点必须知道,当一个函数调用完返回后它会释放该函数中所有的栈空间。栈是由编译器自动管理的,不用你操心。
 
堆是动态分配内存的,并且你可以分配使用很大的内存。但是用不好会产生内存泄漏。并且频繁地malloc和free会产生内存碎片(有点类似磁盘碎片),因为c分配动态内存时是寻找匹配的内存的。而用栈则不会产生碎片。
在栈上存取数据比通过指针在堆上存取数据快些。
一般大家说的堆栈和栈是一样的,就是栈(stack),而说堆时才是堆heap.栈是先入后出的,一般是由高地址向低地址生长。
 
堆(heap)和栈(stack)是C/C++编程不可避免会碰到的两个基本概念。首先,这两个概念都可以在讲数据结构的书中找到,他们都是基本的数据结构,虽然栈更为简单一些。在具体的C/C++编程框架中,这两个概念并不是并行的。对底层机器代码的研究可以揭示,栈是机器系统提供的数据结构,而堆则是C/C++函数库提供的。
 
具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。这种机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构。因为栈的这种特点,对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把
返回地址推入栈,然后跳转至子程序地址的操作,而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因(因为 颜换指戳说饔们暗 状态)。
 
和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/realloc/free函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者
。当程序释放分配的内存空间时,这片内存空间被返回内部堆结构中,可能会被适当的处理(比如和其他空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因:
 
1. 系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分类来说会造成浪费。
 
2. 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。
 
3. 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片
 
 
堆和栈的对比
 
从以上知识可知,栈是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而栈是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定降低。栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。栈空间分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由alloca函数完成。栈的动态分配
无需释放(是自动的),也就没有释放函数。为可移植的程序起见,栈的动态分配操作是不被鼓励的!堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。
可以放一块思考堆和栈的生长方向恰好相反,
|————–| 低地址
| 堆 |
|————–|
| |  |
| I  |
| |  |
| ^  |
| 栈 | 高地址
—————–
所以计算机中的堆和栈经常时放一块讲的nod 一般不是必要就不要动态创建,最讨厌把new出来的东西当局部变量用,用万了马上delete 的做法.
理由
1.栈分配比堆快,只需要一条指令就呢给配所有的局部变量
2.栈不会出现内存碎片
3。栈对象好管理
 
当然,某些情况下也要那么写,比如
1.对象很大
2.对象需要在某个特定的时刻构造或析够
3.类只允许对象动态创建,比如VCL的大多数类
当然,必须用堆对象时也不能躲避
 
 
 
 
 
 
 
 
PS:
在Linux和Windows中用   c   语言开发程序,一些函数(例如   malloc   和   strdup   )动态分配的内存,有必要在程序中加入语句,在程序结束前释放么?操作系统会不会自动在程序结束的时候把这些内存释放而不用编程者关心?
 
 
1.你可以不关心,不过这样下去也许哪天你老板也不会关心你的工资了.
 
2.是的,理论上不需要释放,就像你不需要在程序结束时fclose()用fopen()打开的文件一样。但是,自己做这件事毕竟是个好习惯,它只会给你带来好处。
 
3.动态分配的内存,当然有必要在不用时释放掉,这样可以使所编的程序尽可能小的占用内存资源。尽管程序结束时这些内存会被自动释放,但及时的释放不用的动态内存,是程序员应该具备的好习惯。
 
4.如果你用的是JAVA,那么你不释放,老板都不会不关心你的工资的。
 
5.理论上是必须你自己释放的,如果你不释放,在程序结束的时候,系统会帮你释放,但我们不能依靠系统来做这件事,因为你不释放,在你的程序运行中,就会造成内存泄漏,当然,你的程序是一个小软件,每一次运行的时间不长,你可以不关心这个问题,但是这不是说,你可以这样做,因为成为习惯以后就很可怕,你不可能永远写小软件,一些大型的软件,比如电信系统用的软件,它可能一次就运行几年,甚至更长,这个时候任何一个小的内存泄漏,在长时间的情况下,都可能造成系统的死机,而且这样的错误很难除去,所以平时养成良好的习惯,才不会关键的时刻出错。如果你的习惯不好,你的经理在code   review的时候就会发现,这样对你的饭碗当然不利了。
 
6.C/C++中,内存问题是一个很特殊的地方,C++的创作者们也曾努力想使C++具有内存自动回收功能,但最后碍于效率等因素还是把它留给了程序员自己解决。在程序中动态分配的内存,除了你自己没人可以帮你摆平,所以自己分配的内存自己释放,不仅是一个习惯问题,而且是绝对必需的。不过你可以借助特殊的模版实现动态内存的自动回收。这在很多技术书籍里都有提到。
 
7.一定要释放,我现在的工作是电信软件方面,我们的软件系统一般都是7*24小时运行的,如果在一个循环里,malloc内存而忘了释放的话,最终很可能导致系统的内存被你的程序耗尽,当掉。所以一定要样成malloc,free和new,delete配对的习惯。
8.程序中没有释放内存不仅会有内存耗尽的危险,还有可能堆变量的内存空间挤占其他的变量空间,这样的现象有人遇到过,排除这样的错误及其困难.  
   
  不释放内存是十分危险的,因为它造成的后果”不确定”.  
  不确定的意思是:程序在你写的时候可以运行,测试的时候也不会发现问题,但是到了客户那里运行的时候,就崩溃了.
 
…… ……
 
相关网址:

ShellExecute用法

ShellExecute函数原型及参数含义如下:
 
ShellExecute(
HWND hwnd, //父窗口句柄
LPCSTR lpOperation, //操作类型
LPCSTR lpFile, //要进行操作的文件或路径
LPCSTR lpParameters, //当lpOperation为“explore”时指定要传递的参数,通常设为NULL
LPCSTR lpDirectory, //指定默认目录,通常设为NULL
INT nShowCmd //文件打开的方式,以通常方式还是最大化或最小化显示
)
例子如下:
 
//调用计算器
ShellExecute(NULL,”open”,”calc.exe”,NULL,NULL,SW_SHOWNORMAL);
//调用记事本
ShellExecute(NULL,”open”,”NOTEPAD.EXE”,NULL,NULL,SW_SHOWNORMAL);
 
●hWnd:用于指定父窗口句柄。当函数调用过程出现错误时,它将作为Windows消息窗口的父窗口。例如,可以将其设置为应用程序主窗口句柄,即Application.Handle,也可以将其设置为桌面窗口句柄(用GetDesktopWindow函数获得)。
 
●Operation:用于指定要进行的操作。其中“open”操作表示执行由FileName参数指定的程序,或打开由FileName参数指定的文件或文件夹;“print”操作表示打印由FileName参数指定的文件;“explore”操作表示浏览由FileName参数指定的文件夹。当参数设为nil时,表示执行默认操作“open”。
 
●FileName:用于指定要打开的文件名、要执行的程序文件名或要浏览的文件夹名。
 
●Parameters:若FileName参数是一个可执行程序,则此参数指定命令行参数,否则此参数应为nil或PChar(0)。
 
●Directory:用于指定默认目录。
 
●ShowCmd:若FileName参数是一个可执行程序,则此参数指定程序窗口的初始显示方式,否则此参数应设置为0。
 
若ShellExecute函数调用成功,则返回值为被执行程序的实例句柄。若返回值小于32,则表示出现错误。
上述仅仅是ShellExecute函数的标准用法,下面将介绍它的特殊用法。
 
 
2.特殊用法
如果将FileName参数设置为“http:”协议格式,那么该函数将打开默认浏览器并链接到指定的URL地址。若用户机器中安装了多个浏览器,则该函数将根据Windows 9x/NT注册表中http协议处理程序(Protocols Handler)的设置确定启动哪个浏览器。
 
格式一:http://网站域名。
如:ShellExecute(handle, ‘open’, http:// ;
www.neu.edu.cn’, nil, nil, SW_SHOWNORMAL);
 
格式二:http://网站域名/网页文件名。
如:ShellExecute(handle, ‘open’, http:// ;
如果将FileName参数设置为“mailto:”协议格式,那么该函数将启动默认邮件客户程序,如Microsoft Outlook(也包括Microsoft Outlook Express)或Netscape Messanger。若用户机器中安装了多个邮件客户程序,则该函数将根据Windows 9x/NT注册表中mailto协议处理程序的设置确定启动哪个邮件客户程序。
 
格式一:mailto:
如:ShellExecute(handle,‘open’, ‘mailto:’, nil, nil, SW_SHOWNORMAL);打开新邮件窗口。
 
格式二:mailto:用户账号@邮件服务器地址
如:ShellExecute(handle, ‘open’,‘ mailto:who@mail.neu.edu.cn’, nil, nil, SW_SHOWNORMAL);打开新邮件窗口,并自动填入收件人地址。若指定多个收件人地址,则收件人地址之间必须用分号或逗号分隔开(下同)。
 
格式三:mailto:用户账号@邮件服务器地址?subject=邮件主题&body=邮件正文
如:ShellExecute(handle, ‘open’, ‘ mailto:who@mail.neu.edu.cn?subject=Hello&Body=This is a test’, nil, nil, SW_SHOWNORMAL);打开新邮件窗口,并自动填入收件人地址、邮件主题和邮件正文。若邮件正文包括多行文本,则必须在每行文本之间加入换行转义字符%0a。
 
例子(delphi):
 
在一个应用程序调用c:\Project1.exe;
ShellExecute(handle, ‘open’,’c:\Project1.exe’,’字串内容’,nil, SW_SHOWNORMAL);
在Project1.exe里可以调用:
procedure TForm1.FormCreate(Sender: TObject);
var i:integer;
begin
for i:=1 to paramcount do
if ParamStr(i)<>” then showmessage(ParamStr(i));
end;
 
最后的那个参数,为窗口指定可视性方面的一个命令。
请用下述任何一个常数
SW_HIDE 隐藏窗口,活动状态给令一个窗口
SW_MINIMIZE 最小化窗口,活动状态给令一个窗口
SW_RESTORE 用原来的大小和位置显示一个窗口,同时令其进入活动状态
SW_SHOW 用当前的大小和位置显示一个窗口,同时令其进入活动状态
SW_SHOWMAXIMIZED 最大化窗口,并将其激活
SW_SHOWMINIMIZED 最小化窗口,并将其激活
SW_SHOWMINNOACTIVE 最小化一个窗口,同时不改变活动窗口
SW_SHOWNA 用当前的大小和位置显示一个窗口,不改变活动窗口
SW_SHOWNOACTIVATE 用最近的大小和位置显示一个窗口,同时不改变活动窗口
SW_SHOWNORMAL 与SW_RESTORE相同
 

SHOpenFolderAnd-SelectItems的用法

[language=CPP]

   LPITEMIDLIST  pidl;
   LPCITEMIDLIST cpidl;
   LPCITEMIDLIST cpidl1;
   LPSHELLFOLDER pDesktopFolder;
   char          szPath[MAX_PATH];
   OLECHAR       olePath[MAX_PATH];
   ULONG         chEaten;
   ULONG         dwAttributes;
   HRESULT       hr;
   char          szPath1[MAX_PATH];

   //
   // Get the path to the file we need to convert.
   //
   //  GetCurrentDirectory(MAX_PATH, szPath);
   sprintf(szPath, “D:\\code管理\\Demo”);
   sprintf(szPath1, “D:\\code管理\\Demo\\HookManager_src.zip”);
   //
   // Get a pointer to the Desktop’s IShellFolder interface.
   //
   if (SUCCEEDED(SHGetDesktopFolder(&pDesktopFolder)))
   {
       //
       // IShellFolder::ParseDisplayName requires the file name be in
       // Unicode.
       //
       MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1,
                           olePath, MAX_PATH);

       //
       // Convert the path to an ITEMIDLIST.
       //
       hr = pDesktopFolder->ParseDisplayName(NULL,0,olePath,&chEaten,&pidl,&dwAttributes);
       if (FAILED(hr))
       {
     AfxMessageBox(“ERROR”);
           // Handle error.
       }
    cpidl = pidl;

       MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath1, -1,
                           olePath, MAX_PATH);

       //
       // Convert the path to an ITEMIDLIST.
       //
       hr = pDesktopFolder->ParseDisplayName(NULL,0,olePath,&chEaten,&pidl,&dwAttributes);
       if (FAILED(hr))
       {
     AfxMessageBox(“ERROR”);
           // Handle error.
       }
  cpidl1 = pidl;
    HRESULT RE = CoInitialize(NULL);
    int re = SHOpenFolderAndSelectItems(cpidl,1,&cpidl1,NULL);

       //
       // pidl now contains a pointer to an ITEMIDLIST for .\readme.txt.
       // This ITEMIDLIST needs to be freed using the IMalloc allocator
       // returned from SHGetMalloc().
       //

       //release the desktop folder object
         pDesktopFolder->Release();
   } 

 

[/language]

 

 

 

打开文件夹并选定文件还有一种最简便的方法,就是使用ShellExecute:

 

[language=CPP]

BOOL OpenFolderAndSelectItem(LPSTR pFilePath)
{
 char szCmdLine[MAX_PATH]={0};  
 strcpy(szCmdLine,   “/Select,   “);  
 strcat(szCmdLine,   pFilePath);  
 ShellExecute(NULL,   NULL,   “Explorer”,   szCmdLine,   NULL,   SW_SHOWNORMAL);
 return TRUE;
}

[/language]




Modified At 2008-05-22 18:56:44

如何获取其它程序的命令行参数

    我们都知道,在程序里获取命令行参数很简单,WinMain函数会以参数的形式传递给我们,或者可以调用API GetCommandLine 获取。但是GetCommandLine函数不接受参数,获取的只是自己程序的命令行参数。那么如果我们想获取别的应用程序的命令行参数应该怎么办呢?

  有的同学说,既然GetCommandLine只能获取本程序的命令行参数,我们可以在其它进程里插入一个Dll,在那个进程的地址空间调用GetCommandLine函数,然后传回来就可以了。这样好像有点儿不太友好。让我们想想还有没有别的办法。

  我们想,自己的命令行参数既然随时都可以获取到,那么在该进程里一定有一个地方存放它。那么在哪儿呢?看一下GetCommandLine函数的反汇编代码,我们发现,原来世界是如此的美好!

  以下是WinXP系统的GetCommandLine函数反汇编代码:

.text:7C812C8D GetCommandLineA proc near
.text:7C812C8D mov eax, dword_7C8835F4   //dword_7C8835F4 就是命令行参数字符串的地址
            //该指令机器码为 A1 F4 35 88 7C,从第2个字节开始的4个字节就是我们要的地址
.text:7C812C92 retn
.text:7C812C92 GetCommandLineA endp既然知道了放在哪儿了,我们自己去拿就可以了。因为GetCommandLine函数的地址在各个进程内都是一样的,所以可以直接用我们进程里的地址。 win2000/xp系统很简单,98下稍微麻烦一点儿,需要进行一些简单的计算。 以下是GetCommandLine函数在win98下的汇编代码:.text:BFF8C907 GetCommandLineA proc near
.text:BFF8C907 mov eax, dword_BFFCADE4
.text:BFF8C90C mov ecx, [eax]
.text:BFF8C90E mov eax, [ecx+0C0h]
.text:BFF8C914 test eax, eax
.text:BFF8C916 jnz short locret_BFF8C91E
.text:BFF8C918 mov eax, [ecx+40h]
.text:BFF8C91B mov eax, [eax+8] //算到这儿,才是我们想要的地址
.text:BFF8C91E
.text:BFF8C91E locret_BFF8C91E: ; CODE XREF: GetCommandLineA+F.
.text:BFF8C91E retn这样,我们就可以调用OpenProcess函数打开其它进程,然后用ReadProcessMemory读取相应的数据即可。 示例代码:

 



 

用户自定义消息

    ClassWizard不允许增加用户自定义消息,所以你必须手工输入。输入后,ClassWizard就可以象处理其它消息一样处理你自定义的消息了。
    下面是增加自定义消息的步骤:
 
    第一步:定义消息。开发Windows95应用程序时,Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。
 
    第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。
 
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
           // TODO: 处理用户自定义消息
          …
            return 0;
}
 
    第三步:在类头文件的AFX_MSG块中说明消息处理函数:
 
class CMainFrame:public CMDIFrameWnd
{
    …
    // 一般消息映射函数
    protected:
        // {{AFX_MSG(CMainFrame)
        afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
        afx_msg void OnTimer(UINT nIDEvent);
        afx_msg LRESULT OnMyMessage(WPARAM wParam,  LPARAM lParam);
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
}
 
    第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。
 
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
    ON_WM_CREATE()
    ON_WM_TIMER()
    ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
    如果用户需要一个整个系统唯一的消息,可以调用SDK函数RegisterWindowMessage并使用ON_REGISTER_MESSAGE宏指令取代ON_MESSAGE宏指令,其余步骤同上。

如何获得指定进程加载的DLL

Enumerating All Modules For a Process
To determine which processes have loaded a particular DLL, you must enumerate the modules for each process. The following sample code uses the EnumProcessModules function to enumerate the modules of current processes in the system.

 



 

The main function obtains a list of processes by using the EnumProcesses function. For each process, the main function calls the PrintModules function, passing it the process identifier. PrintModules in turn calls the OpenProcess function to obtain the process handle. If OpenProcess fails, the output shows only the process identifier. For example, OpenProcess fails for the Idle and CSRSS processes because their access restrictions prevent user-level code from opening them. Next, PrintModules calls the EnumProcessModules function to obtain the module handles function. Finally, PrintModules calls the GetModuleFileNameEx function, once for each module, to obtain the module names.