windows2000提供了如下几种线程池函数用于线程管理:
一、异步调用函数:
BOOL QueueUserWorkItem(
PTHREAD_START_ROUTINE pfnCallback,
PVOID pvContext,
ULONG dwFlags);
该函数将“工作项目”放入线程池并且立即返回。工作项目是指一个用pfnCallback参数标识的函数。它被调用并且传递单个参数pvContext.工作项目函数原型如下:
DWORD WINAPI WorkItemFunc(PVOID pvContext);
dwFlags参数:WT_EXECUTEDEFAULT 工作项目放入非I/O组件得线程中
WT_EXECUTEINIOTHREAD 工作项目放入I/O组件的线程中,这样的线程在I/O请求没有完成之前不会被终止运行 ,防止因为线程被终止导致I/O请求丢失。
WT_EXECUTEINPERSISTENTTHREAD 放入永久线程池,
WT_EXECUTELONGFUNCTION 工作项目需要长时间的工作,系统会据此安排更多的线程。
线程池不能设置线程个数的上限,否则排队个数超过线程个数上限的时候,会导致所有的线程都被中断。
工作项目函数如果访问了已经被卸载的DLL,会产生违规访问。
二、按规定的时间间隔调用函数
创建定时器队列:
HANDLE CreateTimerQueue();
在队列中创建定时器:
BOOL CreateTimerQueueTimer(
PHANDLE phNewTimer,
HANDLE hTimerQueue,
WAITORTIMERCALLBACK pfnCallback,
PVOID pvContext,
DWORD dwDueTime,
DWORD dwPeriod,
ULONG dwFlags);
工作回调函数原型如下:
VOID WINAPI WaitOrTimerCallback(
PVOID pvContext,
BOOL fTimerOrWaitFired);
dwFlags比前面的多了一个标志:WT_EXECUTEINTIMERTHREAD,表示由组件的定时器线程(定时器组件只有一个线程)运行这个
工作函数,此时的工作函数必须是很快返回的,否则定时器组件将无法处理其他的请求。
删除定时器:
BOOL DeleteTimerQueueTimer(
HANDLE hTimerQueue,
HANDLE hTimer,
HANDLE hCompletionEvent);
在定时器线程中删除定时器会造成死锁。设定hCompletionEvent为INVALID_HANDLE_VALUE,那么在定时器的所有排队工作项目没有完成之前,DeleteTimerQueueTimer不会返回,也就是说在工作项目中对定时器进行中断删除会死锁。可以给hCompletionEvent传递事件句柄,函数会立即返回,在排队工作完成之后,会设置该事件。
重新设定定时器://不能修改已经触发的单步定时器。
BOOL ChangeTimerQueueTimer(
HANDLE hTimerQueue,
HANDLE hTimer,
ULONG dwDueTime,
ULONG dwPeriod;
删除定时器队列:
BOOL DeleteTimerQueueEx(
HANDLE hTimerQueue,
HANDLE hCompletionEvent);
三、当单个内核对象变为已通知状态时调用函数
BOOL RegisterWaitForSIngleObject(
PHANDLE phNewWaitObject,
HANDLE hObject,
WAITORTIMERCALLBACK pfnCallback,
PVOID pvContext,
ULONG dwMilliseconds,
ULONG dwFlags);
pfnCallBack原型:
VOID WINAPI WaitOrTimerCallbadkFunc(
PVOID pvContext,
BOOLEAN fTimerorWaitFired);
如果等待超时,fTimerorWaitFired==TRUE,如果是已通知状态,则为FALSE.
dwFlags可以传递参数:WT_EXECUTEINWAITTHREAD,它让等待组件得线程之一运行工作项目函数。注意项同前。
如果等待的内核对象是自动重置的,那么会导致工作函数被反复调用,传递WT_EXECUTEONLYONCE会避免这种情况。
取消等待组件的注册状态:
BOOL UnregisterWaitEx(
HANDLE hWaitHandle,
HANDLE hCompletionEvent);
四、当异步I/O请求完成时调用函数
将设备和线程池的非I/O组件关联
BOOL BindIoCompletionCallback(
HANDLE hDevice,
POVERLAPPED_COMPLETION_ROUTINE pfnCallback,
ULONG dwFlags//始终为0);
工作函数原型:
VOID WINAPI OverlappedCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
,
POVERLAPPED pOverlapped);
Windows的内存结构
从98,2000,到64位的windows,内存管理方式都是不同的,32位的win2000用户内存是从0x10000到0x7fffffff(64kB-2G),2000 Advanced server可以达到(64kB-3G),其中最高64kB也是禁止进入的。再往上则由系统使用。98则是从0x400000-0x7fffffff(4M-2G),2G-3G是系统用来存放32位共享数据的地方,如很多系统动态连接库。0-4M是为了兼容16位程序保留的。3G-4G由系统自身使用。98的内核区是不受保护的,2000受保护。
对虚拟地址空间的分配称作保留,使用虚拟内存分配函数(VirtualAlloc),释放使用VirtualFree(),目前,所有cpu平台的分配粒度都是64kB,页面大小则不同,x86是4kB,Alpha是8kB,系统在保留内存的时候规定要从分配粒度边界开始,并且是页面的整数倍,用户使用VirtualAlloc都遵守这个规定,但系统不是,它是从页面边界开始分配的。
将物理存储器映射到保留的内存区域的过程称为提交物理存储器,提交是以页面为单位进行的,也使用VirtualAlloc函数。
物理存储器是由内存和(硬盘上的)页文件组成的,如果访问的数据是在页文件中,则称为页面失效,cpu会把访问通知操作系统,操作系统负责将数据调入内存,并指导cpu再次运行上次失效的指令。
当启动一个程序的时候,系统并不是将整个文件读入内存或者页文件,而是将这个文件直接映射到虚拟内存空间,并将需要的数据读入内存,即将硬盘上的文件本身当作页文件(虽然不是)。当硬盘上的一个程序的文件映像(这是个exe文件或者dll文件)用作地址空间的物理存储器,它称为内存映射文件。当一个.exe或者dll文件被加载时,系统将自动保留一个地址空间的区域,并将该文件映射到该区域中。但系统也提供了一组函数,用于将数据文件映射到一个地址空间的区域中。
物理存储器的页面具有不同的保护属性:
PAGE_NOACESS
PAGE_READONLY
PAGE_READWRITE
PAGE_EXECUTE
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE
PAGE_WRITECOPY
PAGE_EXECUTE_WRITECOPY
后两个属性是配合共享页面机制使用的。WINDOWS支持多个进程共享单个内存块,比如运行notepad的10个实例,可以让他们共享应用程序的代码和数据,这样可以大大提高性能,但要求该内存块是不可写的。于是系统在调入.exe或者dll的时候,会计算那些页面是可以写入的,为这些页面分配虚拟内存。然后同其他的页面一起映射到一块虚拟内存,但赋PAGE_WRITECOPY或者PAGE_EXECUTE_WRITECOPY属性(通常包含代码的块是PAGE_EXECUTE_READ,包含数据的块是PAGE_READWRITE)。当一个进程试图将数据写入共享内存块时,系统会进行如下操作:寻找预先分配的一个空闲页面,将试图修改的页面拷贝到这个空闲页面,赋予PAGE_READWRITE或者PAGE_EXECUTE_READWRITE属性,然后更新进程的页面表,使得用户可以对新的页面进行写入。
还有三个特殊的保护属性:PAGE_NOCACHE PAGE_WRITECOMBINE PAGE_GUARD,前两个用于驱动程序开发,最后一个可以让应用程序在页面被写入的时候获得一个异常。
块的意思是一组相邻的页面,它们具有相同的保护属性,并且受相同类型的物理存储器支持。
赋予虚拟内存页面保护属性的意义是为了提高效率,而且这个属性总会被物理存储器的保护属性取代。
如果数据在内存中没有对齐,那么cpu要多次访问才能得到数据,效率很低。
内存管理函数:
获得系统信息:
VOID GetSystemInfo(LPSYSTEM_INFO psinf);//可以得到页面大小,分配粒度,最大内存地址,最小内存地址。
获得内存状态:
VOID GlobalMemoryStatus(LPMEMORYSTATUS pmst);
获得内存地址的某些信息:
DWORD VirtualQuery(
LPVOID pvAddress,
PMEMORY_BASIC_INFORMATION pmbi,
DWORD dwLength);
DWORD VirtualQuery(
HANDLE hProcess,
LPVOID pvAddress,
PMEMORY_BASIC_INFORMATION pmbi,
DWORD dwLength);
内存映射文件的优点:
1 节省页面文件;
2 加快程序启动;
3 在多个进程间共享数据。
进程的启动过程:
系统首先将.exe文件映射到地址空间,缺省基地址是0x400000,然后查询.exe的输入表,将其使用的所有.dll也映射到地址空间(基地址在每个.dll文件中,如果不能满足,需要重定位),然后将执行.exe的启动代码。此时.exe文件还在硬盘上。每次代码跳到一个尚未加载到内存的指令地址,就会出现一个错误,系统会发现这个错误,并将代码加再到内存中。
如果再创建这个.exe文件的一个实例。那么直接将原来的地址空间中的内容映射到新的地址空间就可以了。这样多个实例就可以共享相同的代码和数据。如果某个实例要改变共享内容,系统就为要更改的页面申请一个新的页面,将内容拷贝一份,然后用新的页面代替地址空间中原来页面的映射就可以了。98同2000不同,它不待修改便立即为所有的实例分配新的页面。
使用内存映射文件:
1 创建或打开一个文件内核对象:
HANDLE CreateFile(
PCSTR pszFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
失败的返回值是INVALID_HANDLE_VALUE
2 创建一个文件映射内核对象:
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD fdwProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
如果给函数的fdwProtect传递PAGE_READWRITE标志,那么磁盘上文件的大小会变为同映像文件相同大小。
失败的返回值是NULL。
3 将文件映射到进程的地址空间:
PVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap);
windows2000会根据要求将部分文件映射到地址空间,而win98总是把全部内容映射到地址空间,并且仅能映射到2G-3G空间,此空间为共享空间,所有的进程如果映射相同的文件,那么都会映射到相同的地址,一个进程甚至不必映射就可以访问这个空间里其他进程的映射文件,win2000多个进程映射同一个文件返回的地址通常是不同的。
4 从进程的地址空间中撤销文件数句的映像
BOOL UnmapViewOfFile(PVOID pvBaseAddress);
将文件映像写入磁盘:
BOOL FlushViewOfFile(
PVOID pvAddress,
SIZE_T dwNumberOfBytesToFlush);
windows保证单个文件映射对象的多个视图具有相关性。但不保证但个文件的多个映射对象有相关性。
使用MapViewOfFileEx代替MapViewOfFile可以设定文件映射的基地址:
PVOID MapViewOfFileEx(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap,
PVOID pvBaseAddress);
使用内存映射文件在进程间共享数据
共享机制:RPC ,COM,OLE,DDE,窗口消息(WM_COPYDATA),剪贴板,邮箱,管道,套接字。
在单机上,它们的底层实现方法都是内存映射文件。
可以在页文件中直接创建文件映射对象,方法是给CreateFileMapping函数的hFile参数传递INVALID_HANDLE_VALUE.注意,
CreateFile()函数运行失败也会返回这个参数,因此一定要检查CreateFile()的返回值。记住,文件函数运行失败的可能性太大了。
第三章:多个进程共享对象。
堆栈:优点:可以不考虑分配粒度和页面边界之类的问题,集中精力处理手头的任务,缺点是:分配和释放内存块的速度比其他机制慢,并且无法直接控制物理存储器的提交和回收。
进程的默认堆栈是1MB,可以使用/HEAP链接开关调整大小,DLL没有相关的堆栈。
堆栈的问题在于:很多windows函数要使用临时内存块,进程的多个线程要分配内存块,这些内存都是在默认堆栈上分配的,但规定时间内,每次只能由一个线程能够分配和释放默认堆栈的内存块,其他想要处理内存块的线程必须等待。这种方法对速度又影响。可以为进程的线程创建辅助堆栈,但windows函数只能使用默认堆栈。
获取进程默认堆栈句柄:
HANDLE GetProcessHeap();
创建辅助堆栈的理由
1 保护组件:
多个组件的数据混合交叉的存放在一块内存里,那么一个组件的错误操作很容易影响到另外一个组件。而要定位错误的来源将十分困难。
2 更有效的内存管理
通过在堆栈中分配同样大小的对象,可以更加有效的管理内存,减少内存碎片。
3 进行本地访问:
将同种数据集中到一定的内存块,可以在操作的时候访问较少的页面,这就减少了RAM和硬盘对换的可能.
4 减少线程同步的开销:
通过告诉系统只有一个线程使用堆栈(创建堆栈时使用HEAP_NO_SERIALIZE标志给fdwOptions),可以避免堆栈函数执行额外的用于保证堆栈安全性的代码,提高效率,但此时用户必须自己维护线程的安全性,系统不再对此负责。
5 迅速释放堆栈。
因为数据单一,因此释放的时候只要释放堆栈即可,不必显示的释放每个内存块。
创建辅助堆栈:
HANDLE HeapCreate(
DWORD fdwOptions,
SIZE_T dwInitialSize,
SIZE_T dwMaximumSize);
从堆栈中分配内存:
PVOID HeapAlloc(
HANDLE hHeap,
DWORD fdwFlags,
SIZE_T dwBytes);注意:当分配超过(1MB)内存块的时候,最好使用VirtualAlloc();
改变内存块的大小:
PVOID HeapReAlloc(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem,
SIZE_T dwBytes);
检索内存块的大小:
SIZE_T HeapSize(
HANDLE hHeap,
DWORD fdwFlags,
LPVOID pvMem);
释放内存块:
BOOL HeapFree(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem);
撤销堆栈:
BOOL HeapDestroy(HANDLE hHeap);
使用辅助堆栈的方法:重载对象的new操作符,在辅助堆上分配内存,并给对象添加一个静态变量用于保存堆句柄。
其它堆栈函数:
获取进程中所有堆栈得句柄:
DWORD GetProcessHeaps(DWORD dwNumHeaps,PHANDLE pHeaps);
验证堆栈完整性:
BOOL HeapValidate(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
合并地址中的空闲块
UINT HeapCompact(
HANDLE hHeap,
DWORD fdwFlags);
BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnlock(HANDLE
遍历堆栈:
BOOL HeapWalk(
HANDLE hHeap,
PProcess_HEAP_ENTRY pHeapEntry);
各个dll也可以有自己的输入表。
如何编写DLL:
在DLL的头文件中,有如下代码:
#ifdef MYLIB
#else
#define MYLIB extern "C" __declspec(dllimport)
#endif
在每个输出变量和输出函数的声明前,用MYLIB修饰。
在DLL的实现文件中,有如下代码:
#i nclude "windows.h"
#define MYLIB extern "C" __declspec(dllexport)
#i nclude "Mylib.h"
其它的同编写普通C++程序完全相同。 "C" 表示按C方式链接和调用函数。C++编译器缺省按照__stdcall方式编译和调用,这种方式会改变函数的内部名字。此处如果把"C"都去掉也可以,但C程序将无法调用。另外,使用GetProcAddress函数时也会发生困难,因为
编译程序已经把函数名字改变了,无法用原来的名字得到函数地址。(核心编程说的不明白,没想到这本书错误这么多)
发行的时候,将头文件、.lib文件和DLL文件给用户就可以了。lib文件的作用是说明了头文件中函数所在的DLL文件,如果没有lib文件,编译器将在链接过程中提示错误:unresolved external symbol 函数名。
事实上,调用DLL有两种方式,第一种是比较常用,即包含DLL的头文件,并在链接的时候将动态链接库同exe文件像连接,建立输入表。这个时候需要.lib文件。第二种方法exe文件中没有输入表,程序使用LoadLibrary(Ex)和GetProcAddress()显式的加载DLL文件(卸载用FreeLibrary()),这个时候不需要.lib文件。
HINSTANCE LoadLibrary(PCTSTR pszDLLpathName);
HINSTANCE LoadLibraryEx(PCTSTR pszDLLpathName,NULL,0);
两次调用LoardLibrary并不会装载两次dll文件,只是将dll映射进进程的地址空间。系统会自动为每个进程维护一个dll的计数。FreeLiabray会使计数减一,如果计数为0,系统就会将dll从进程的地址空间卸载。
HINSTANCE GetModuleHandle(PCTSTR pszModuleName);//确定dll是否已经被映射进地址空间。
HINSTANCE hinstDll=GetModuleHandle("MyLib");
if(hinstDll==NULL)
{
hinstDll=LoadLibrary("MyLib");
}
DWORD GetModuleFileName(
HINSTANCE hinstModule,
PTSTR pszPathName,
DWORD cchPath
}
可以获得某个模块(.exe或者dll)的全路径名。
几个函数的用法:(注意GetProcAddress()函数的用法,如何定义和使用一个函数指针)
typedef int (*MYPROC)(int,int);
int main()
{
HINSTANCE t;
t=LoadLibraryEx(TEXT("tt.dll"),NULL,0);
if(t)
{
cout<<TEXT("load success")<<endl;
}
HINSTANCE hinstDll=GetModuleHandle("tt.dll");
if(hinstDll==NULL)
{
cout<<TEXT("first load failed")<<endl;
hinstDll=LoadLibrary("MyLib");
}
size_t sz=100;
PTCHAR str=new TCHAR[sz];
GetModuleFileName(t,str,sz);
cout<<str<<endl;
delete str;
MYPROC add=NULL;
add=(MYPROC)GetProcAddress(t,"add");
if(NULL!=add)
{
cout<<(*add)(1,2)<<endl;
}
FreeLibrary(t);
return 0;
}
UNICODE
ANSI/UNICODE通用的定义方法(转换只需要在编译的时候使用_UNICODE和UNICODE):
TCHAR _TEXT("success") PTSTR PCTSTR _tcscpy(),_tcscat();
使用BYTE PBYTE定义字节,字节指针和数据缓冲。
传递给函数的缓存大小:sizeof(szBuffer)/sizeof(TCHAR)
给字符串分配内存:malloc(nCharacters*sizeof(TCHAR));
其它的字符串函数:
PTSTR CharLower(PTSTR pszString);
PTSTR CharUpper(PTSTR pszString);
转换单个字符:
TCHAR c=CharLower((PTSTR)szString[0]);
转换缓存中的字符串(不必以0结尾):
DWORD CharLowerBuff(
PTSTR pszString,
DWORD cchString);
DWORD CharUpperBuff(
PTSTR pszString,
DWORD cchString);
BOOL IsCharAlpha(TCHAR ch);
BOOL IsCharAlpahNumeric(TCHAR ch);
BOOL IsCharLower(TCHAR ch);
BOOL IsCharUpper(TCHAR ch);
线程本地存储(TLS):为进程的每个线程存储私有数据。用于那些一次传递参数后多次调用的函数(函数会保存上次调用的数据)。
实现方法:进程中有一个位标志树组(win2000的这个数组大小超过1000)。在每个线程中有一个对应的PVOID数组。通过设定位标志树组的某个位来分配每个线程中的PVOID数组得相应单元。函数需要每次检索线程的PVOID数组,获得该线程的相应数据。
DWORD TlsAlloc(); //为每个线程分配一个空的PVOID数组单元。
BOOL TlsSetValue( //线程设定自己的PVOID数组单元。
DWORD dwTlsIndex,
PVOID pvTlsValue);
PVOID TlsGetValue(
DWORD dwTlsIndex); //检索PVOID数组。
BOOL TLSFree(
DWORD dwTlsIndex); //释放PVOID数组单元
静态TLS:__declspec(thread) DWORD gt_dwStartTime=0;//只能修饰全局或者静态变量。
DLL挂接(进程注入):让自己的DLL插入到其他进程的地址空间。
1 使用注册表插入DLL
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
将你的DLL路径放入这个关键字下面。当User32.dll被映射到进程中的时候,它会加在这个关键字下的每个库。
注意:(1)对win98无效
(2)由于加载事间比较早,你的DLL可能无法调用kernel32以外的dll.
(3) 如果进程没有使用user32.dll,这个方法无效。
(4) 需要重新启动。
(5)user32不会检查每个库是否加载成功。
2 使用windows钩子
HOOK hHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hinstDll,0);
BOOL UnhookWindowsHookEx(HHOOK hhook);
具体过程如下:
(1)进程B的一个线程准备发送消息给一个窗口。
(2)系统察看线程上是否已经安装了WH_GETMESSAGE钩子。
(3)系统察看GetMsgProc的DLL是否已经映射到了进程B的地址空间,如果没有,系统将把DLL映射到B的地址空间,并自动增加引用计数。
(4)调用GetMsgProc函数,返回时,系统会自动为DLL的引用计数减一。
3 使用远程线程来插入DLL
(1)使用VirtualAllocEx,分配远程进程的地址空间的内存。
(2)使用WriteProcessMemory,将Dll的路径名拷贝到第一个步骤中已经分配的内存中。
(3)使用GetProcAddress,获得LoadLibrary的实际地址。
(4)使用CreateRemoteThread,在远程进程中创建一个线程。
退出:
(5)使用VirtualFreeEx,释放内存
(6)使用GetProcAddress,获得FreeLiabary的地址。
(7)使用CreateRemoteThread,在远程进程中创建一个线程,调用FreeLiabary函数。
4 使用特洛伊DLL插入
替换dll.
5 将DLL作为调试程序插入
6 win98内存映射文件,creatprocess
结构化异常处理:
结束处理程序:__try{} __finally{}
除非__try执行中进程或者线程结束,否则总会执行__finally,并且__finally中的return会替代__try中的return;好的习惯是将return ,continue,break,goto语句拿到结构化异常处理语句外面,可以节省开销。将__try中的return 换成__leave,可以节省开销。在__finally总确定时正常进入还是展开进入:
BOOL AbnormalTermination();//正常进入返回FALSE,局部展开或者全局展开返回TRUE;
异常处理程序:__try{}__exception(异常过滤表达式){}
EXCEPTION_EXECUTE_HANDLE
表示处理异常,处理后转到exception块后面的代码继续执行。
EXCEPTION_CONTINUE_EXECUTION
EXCEPTION_CONTINUE_SEARCH
可以对异常过滤表达式进行硬编码,也可以用一个调用一个函数来决定过滤表达式,函数的返回值是LONG.例如,可以进行一定处理,然后返回EXCEPTION_CONTINUE_EXECUTION,再次执行出错语句。但可能再次出错,因此这种方法必须小心,防止生成死循环。
DWORD GetExceptionCode() 可以获得异常种类。它只能在__except后的括号或者异常处理程序中调用。
发生异常后,操作系统会像引起异常的线程的栈里压入三个结构:EXCEPTION_RECORD CONTEXT EXCEPTION_POINTERS,其中第三个结构包含两个成员指针,分别指向前两个结构,使用函数可以获得第三个结构的指针:
PEXCEPTION_POINTERS GetExceptionInformation();//仅可以在异常过滤器中调用,既__exception后面的小括号。
逗号表达式:从左到右对所有的表达式求值,并返回最有面的表达式的值。
引发软件异常:
VOID RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
CONST ULONG_PTR *pArguments);
缺省调试器所在注册表:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug Debugger
win98是存放在win.ini里
调试器挂接到被调试进程
BOOL DebugActiveProcess(DWORD dwProcessID);
while(*str++!='\0');
应该注意的是在不满足条件后,str仍然会自加1。
位操作符>>和<<不会做循环位移,即不会把移出的位放到另一头。
多态性和动态联编的实现过程分析
一、基础:
1、多态性:使用基础类的指针动态调用其派生类中函数的特性。
2、动态联编:在运行阶段,才将函数的调用与对应的函数体进行连接的方式,又叫运行时联编或晚捆绑。
二、过程描述:
1、编译器发现一个类中有虚函数,编译器会立即为此类生成虚拟函数表 vtable(后面有对vtable的分析)。虚拟函数表的各表项为指向对应虚拟函数的指针。
2、编译器在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)。
有一个办法可以让你感知这个隐含指针的存在,虽然你不能在类中直接看到它,但你可以比较一下含有虚拟函数时的类的尺寸和没有虚拟函数时的类的尺寸,你能够发现,这个指针确实存在。
3、在调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable。这就将类与此类的vtable联系了起来。
4、在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,从而实现了多态性。在此时才能真正与函数体进行连接,这就是动态联编。
定义纯虚函数方法:virtual returntype function()= 0;
所有的类型都可以用new动态创建,包括类,结构,内置数据类型。
exit()函数包含在"cstdlib"中
泛型编程,使用STL库
总共有近75个泛型算法
所有容器的共通操作:
== != = empty() size() clear(),begin(),end(),以及insert和erase,不过后者随容器的的不同而不同
序列式容器:
vector(数组):插入和删除的效率较低,但存取效率高。
list(双向链表):与前者相反,插入和删除的效率较高,但存取效率低。每个元素包含三个字段:value back front.
deque(队列):在前端和末尾操作效率高。
生成序列式容器的五种方法:
1 产生空的容器:
list<string> slist;
vector<int> vtor;
2 产生特定大小的容器,容器中的每个元素都以其默认值为初值(发现VC中的int double等没有默认值)。
list<int> ilist(1024);
vector<string> svec(24);
3 产生特定大小的容器,并为每个元素指定初值:
list<int ilist(1024,0);
vector<string> svec(24,"default");
4 通过一对迭代器产生容器,这对迭代器用来表示数组作为初值的区间:
int ia[10]={1,2,3,4,5,6,7,8,9,0};
vector<int> iv(ia+2,ia+8);
5 复制某个现有的容器的值:
vector<int> ivec1;
//填充ivec1;
vector<int> ivec2(ivec1);
有6个方法用于操作开始和末尾的元素:push_front() pop_front() push_back() pop_back(),由于pop操作仅删除元素而不返回元素,因此还需要front() back()方法取开始和末尾的元素,另外,vector不包括push_front() pop_front方法,很显然,无法实现。
intert的四种变形:
iterator insert(iterator position,elemType value):将value插到position前。返回值指向被插入的元素。
void insert(iterator position,int count,elemType value):在position前插入count个元素,每个元素都是value.
void insert(iterator1 position,iterator2 first,iterator2 last):将first,last之间的元素插到position前.
iterator insert( iterator position):在position前插入元素,初值为所属类型的默认值。
erase的两种变形:
1 iterator erase(iterator posit):删除posit指向的元素。
list<string>::iterator it=find(slist.begin(),slist,end(),str);
slist.erase(it);
2 iterator erase(iterator first,iterator last):删除first,last间的元素。
list不支持iterator的偏移运算
对于常值容器,使用常迭代器:
const vector<string> cs_vec;
vector<string::const_iterator iter_cs_vec.begin();
iterator可以当作指针用,可以用*取内容,也可以用->调用对象的成员。
使用泛型算法
#i nclude <algorithm>
find():线性搜索无序集合
binary_search():二分搜索有序集合。
count():返回元素个数。
search():搜索序列,如果存在返回的iterator指向序列首部,否则指向容器末尾。
max_element(begin,end):返回区间内的最大值。
copy(begin,end,begin):元素复制。
sort(begin,end):排序。
function objects:#i nclude <functional>
算术运算:
plus<type> minus<type> negate<type> multiplies<type> divides<type> modules<type>
关系运算:
less<type> less equal<type> greater<type greater equal<type> equal_to<type> not_equal_to<type>
逻辑运算:
logical_and<type> logical_or<type> logical_not<type>
adapter:适配器。
bind1st:将数值绑定到function object的第一个参数。
bind2nd:将数值绑定到function object的第二个参数。
使用map:
#i nclude <map>
#i nclude <string>
map<string,int> words;
words["vermeer"]=1;
map<string,int>::iterator it=words.begin();
for(;it!=words.end();++it)
cout<<"key:"<<it->first<<"value:"<<it->second<<endl;
查找map元素的方法:
words.find("vermeer");//返回iterator,指向找到的元素,找不到返回end();
还可以:
if(words.count(search_word))
count=words[search_word];
使用set:
#i nclude <set>
#i nclude <string>
set<string> word_exclusion;
//判断是否存在某个元素
if(word_exclusion.count(tword))
//默认情况下,所有元素按less-than运算排列
//加入元素
iset.insert(ival);
iset.insert(vec.begin(),vec.end());
与set相关的算法
set_intersection() set_union() set_difference() set_symmetric_difference()
使用insertion adapters:
#i nclude <iterator>
back_inserter()
inserter()
front_inserter()
使用STL通常会有很多警告,为了避免在调试模式(debug mode)出现恼人的警告,使用下面的编译器命令:
#pragma warning(disable: 4786)
strncpy(dest,source,count) if(count〈=strlen(source)),那么null结尾不会被加在dest的尾部,如果count>strlen(source),那么不足的部分会用null填充。
windows内存是由高地址向底地址分配的,但变量的存储是从底地址到高地址的,如INT类型的四个字节,数组的每个元素。
内存复制的时候不能用字符串拷贝函数,因为即使使用strncpy指定了复制的长度,拷贝函数也会遇到'\0'自动终止,要使用MEMSET。
由于对齐的关系,下面两个结构使用sizeof,前者是12,后者是16。
struct DNSAnswer
{
unsigned short name;
unsigned short type;
unsigned short cla;
unsigned short length;
unsigned int ttl;
};
struct DNSAnswer
{
unsigned short name;
unsigned short type;
unsigned short cla;
unsigned int ttl;
unsigned short length;
};
子类可以使用父类的保护成员,而友元比子类的权限还大,可以使用类的私有和保护成员。
在内存分配失败的情况下,系统只有在出错处理函数为空的情况下,才会抛出异常:std::bad_alloc(),否则会反复调用处理函数并再次尝试分配内存。
如果重载了NEW,那么在继承的时候要小心,如果子类没有覆盖NEW,那么它会去使用父类的NEW ,因此应该在new,delete中做检查
if (size != sizeof(base)) // 如果数量“错误”,让标准operator new,base为类名
return ::operator new(size); // 去处理这个请求
if (size != sizeof(base)) { // 如果size"错误",
::operator delete(rawmemory); // 让标准operator来处理请求
return;
}
c++标准规定,要支持0内存请求(分配一个字节),并且可以删除NULL指针(直接返回)。
在创建线程的时候,传递的变量一定要是全局或者静态的变量,因为传递的是变量的地址,如果是局部变量地址很快就会失效。
主线程退出后,其子线程自动结束。
智能指针:它可以避免内存泄露,因为智能指针是在栈上创建的;还可以避免堆上内存的重复释放错误,因为它保证只有一个指针拥有这块内存的所有权。