Windows内存管理

Windows内存管理
   内存管理对于编写出高效率的Windows程序是非常重要的,这是因为Windows是多任务系统,它的内存管理和单任务的DOS相比有很大的差异。 DOS是单任务操作系统,应用程序分配到内存后,如果它不主动释放,系统是不会对它作任何改变的;但Windows却不然,它在同一时刻可能有多个应用程 序共享内存,有时为了使某个任务更好地执行,Windows系统可能会对其它任务分配的内存进行移动,甚至删除。因此,我们在Windows应用程序中使 用内存时,要遵循Windows内存管理的一些约定,以尽量提高Windows内存的利用率。
  
   Windows内存对象
    Windows应用程序可以申请分配属于自己的内存块,内存块是应用程序操作内存的单位,它也称作内存对象,在Windows中通过内存句柄来操作内存对 象。内存对象根据分配的范围可分为全局内存对象和局部内存对象;根据性质可分为固定内存对象,可移动内存对象和可删除内存对象。
  
   固定内存对象,特别是局部固定内存对象和DOS的内存块很类似,它一旦分配,就不会被移动或删除,除非应用程序主动释放它。并且对于局部固定内存对象来 说,它的内存句柄本身就是内存对象的16位近地址,可供应用程序直接存取,而不必象其它类型的内存对象那样要通过锁定在内存某固定地址后才能使用。
  
    可移动内存对象没有固定的地址,Windows系统可以随时把它们移到一个新地址。内存对象的可移动使得Windows能有效地利用自由内存。例如,如果 一个可移动的内存对象分开了两个自由内存对象,Windows可以把可移动内存对象移走,将两个自由内存对象合并为一个大的自由内存对象,实现内存的合并 与碎片回收。
  
   可删除内存对象与可移动内存对象很相似,它可以被Windows移动,并且当Windows需要大的内存空间满足新的任务时,它可以将可删除内存对象的长度置为0,丢弃内存对象中的数据。
  
    可移动内存对象和可删除内存对象在存取前必须使用内存加锁函数将其锁定,锁定了的内存对象不能被移动和删除。因此,应用程序在使用完内存对象后要尽可能快 地为内存对象解锁。内存需要加锁和解锁增加了程序员的负担,但是它却极大地改善了Windows内存利用的效率,因此Windows鼓励使用可移动和可删 除的内存对象,并且要求应用程序在非必要时不要使用固定内存对象。
  
   不同类型的对象在它所处的内存堆中的位置是不一样的,图6.2说明内存对象在堆中的位置:固定对象位于堆的底部;可移动对象位于固定对象之上;可删除对象从堆的顶部开始分配。
  
  图6.1 内存对象分配位置示意图
  
  6.1.2 局部内存对象管理
    局部内存对象在局部堆中分配,局部堆是应用程序独享的自由内存,它只能由应用程序的特定实例访问。局部堆建立在应用程序的数据段中,因此,用户可分配的局 部内存对象的最大内存空间不能超过64K。局部堆由Windows应用程序在模块定义文件中用HEAPSIZE语句申请,HEAPSIZE指定以字节为单 位的局部堆初始空间尺寸。Windows提供了一系列函数来操作局部内存对象。
  
  6.1.2.1 分配局部内存对象
   LocalAlloc函数用来分配局部内存,它在应用程序局部堆中分配一个内存块,并返回内存块的句柄。LocalAlloc函数可以指定内存对象的大小 和特性,其中主要特性有固定的(LMEM_FIXED),可移动的(LMEM_MOVEABLE)和可删除的(LMEM_DISCARDABLE)。如果 局部堆中无法分配申请的内存,则LocalAlloc函数返回NULL。下面的代码用来分配一个固定内存对象,因为局部固定内存对象的对象句柄其本身就是 16位内存近地址,因此它可以被应用程序直接存取。代码如下:
  
  char NEAR * pcLocalObject;
  
  if (pcLocalObject = LocalAlloc(LMEM_FIXED, 32)) {
  
  /* Use pcLocalObject as the near address of the Locally allocated object, It is not necessary to lock
  
   and unlock the fixed local object */
  
   .…..
  
  }
  
  else {
  
   /* The 32 bytes cannot be allocated .React accordingly. */
  
  }
  
  6.1.2.2 加锁与解锁
   上面程序段分配的固定局部内存对象可以由应用程序直接存取,但是,Windows并不鼓励使用固定内存对象。因此,在使用可移动和可删除内存对象时,就要经常用到对内存对象的加锁与解锁。
  
    不管是可移动对象还是可删除对象,在它分配后其内存句柄是不变的,它是内存对象的恒定引用。但是,应用程序无法通过内存句柄直接存取内存对象,应用程序要 存取内存对象还必须获得它的近地址,这通过调用LocalLock函数实现。LocalLock函数将局部内存对象暂时固定在局部堆的某一位置,并返回该 地址的近地址值,此地址可供应用程序存取内存对象使用,它在应用程序调用 LocalUnlock函数解锁此内存对象之前有效。怎样加锁与解锁可移动内存对象,请看如下代码:
  
  HLOCAL hLocalObject;
  
  char NEAR *pcLocalObject;
  
  if (hLocalObject = LocalAlloc(LMEM_MOVEABLE, 32)) {
  
   if (pcLocalObject = LocalLock(hLocalObject)) {
  
   /*Use pcLocalObject as the near address of the locally allocated object */
  
   .…..
  
   LocalUnlock(hLocalObject);
  
   }
  
   else {
  
   /* The lock failed. React accordingly. */
  
   }
  
  }
  
  else {
  
   /* The 32 bytes cannot be allocated. React accordingly. */
  
  }
  
   应用程序在使用完内存对象后,要尽可能早地为它解锁,这是因为Windows无法移动被锁住了的内存对象。当应用程序要分配其它内存时,Windows不能利用被锁住对象的区域,只能在它周围寻找,这会降低Windows内存管理的效率。
  
  6.1.2.3 改变局部内存对象
    局部内存对象分配之后,还可以调用LocalReAlloc函数进行修改。LocalReAlloc函数可以改变局部内存对象的大小而不破坏其内容:如果 比原来的空间小,则Windows将对象截断;如果比原来大,则Windows将增加区域填0(使用LMEM_ZEROINIT选项),或者不定义该区域 内容。另外,LocalReAlloc函数还可以改变对象的属性,如将属性从LMEM_MOVEABLE改为LMEM_DISCARDABLE,或反过 来,此时必须同时指定LMEM_MODIFY选项。但是,LocalReAlloc函数不能同时改变内存对象的大小和属性,也不能改变具有 LMEM_FIXED属性的内存对象和把其它属性的内存对象改为LMEM_FIXED属性。如何将一个可移动内存对象改为可删除的,请看下面的例子:
  
  hLocalObject = LocalAlloc(32, LMEM_MOVEABLE);
  
  .…..
  
  hLocalObject = LocalReAlloc(hLocalObject, 32, LMEM_MODIFY| LMEM_KISCARDABLE);
  
  6.1.2.4 释放与删除
   分配了的局部内存对象可以使用LocalDiscard和LocalFree函数来删除和释放,删除和释放只有在内存对象未锁住时才有效。
  
    LocalFree函数用来释放局部内存对象,当一个局部内存对象被释放时,其内容从局部堆移走,并且其句柄也从有效的局部内存表中移走,原来的内存句柄 变为不可用。LocalDiscard 函数用来删除局部内存对象,它只移走对象的内容,而保持其句柄有效,用户在需要时,还可以使用此内存句柄用LocalReAlloc函数重新分配一块内 存。
  
   另外,Windows还提供了函数LocalSize用于检测对象所占空间;函数LocalFlags用于检测内存对象是否可删除,是否已删除,及其锁计数值;函数LocalCompact用于确定局部堆的可用内存。
  
  6.1.3 全局内存对象管理
   全局内存对象在全局堆中分配,全局堆包括所有的系统内存。一般来说,应用程序在全局堆中进行大型内存分配(约大于1KB),在全局堆还可以分配大于64K的巨型内存,这将在后面介绍。
  
  6.1.3.1 分配全局内存对象
   全局内存对象使用GlobalAlloc函数分配,它和使用LocalAlloc分配局部内存对象很相似。使用GlobalAlloc的例子我们将和GlobalLock一起给出。
  
  6.1.3.2 加锁与解锁
   全局内存对象使用GlobalLock函数加锁,所有全局内存对象在存取前都必须加锁。GlobalLock将对象锁定在内存固定位置,并返回一个远指针,此指针在调用GlobalUnlock之前保持有效。
  
    GlobalLock和LocalLock稍有不同,因为全局内存对象可能被多个任务使用,因此在使用GlobalLock加锁某全局内存对象时,对象可 能已被锁住,为了处理这种情况,Windows增加了一个锁计数器。当使用GlobalLock加锁全局内存对象时,锁计数器加1;使用 GlobalUnlock解锁对象时,锁计数器减1,只有当锁计数器为0时,Windows才真正解锁此对象。GlobalAlloc和 GlobalLock的使用见如下的例子:
  
  HGLOBAL hGlobalObject;
  
  char FAR * lpGlobalObject;
  
  if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE, 1024)) {
  
   if (lpGlobalObject = GlobalLock(hGlobalObject)) {
  
   /* Use lpGlobalObject as the far address of the globally allocated object. */
  
   .…..
  
   GlobalUnlock (hGlobalObject);
  
   }
  
   else {
  
   /* The lock failed .React accordingly. */
  
   }
  
  }
  
  else {
  
   /* The 1024 bytes cannot be allocated. React accordingly. */
  
  }
  
  6.1.3.3 修改全局内存对象
   修改全局内存对象使用GlobalReAlloc函数,它和LocalReAlloc函数很类似,这里不再赘述。修改全局内存对象的特殊之处在于巨型对象的修改上,这一点我们将在后面讲述。
  
  6.1.3.4 内存释放及其它操作
    全局内存对象使用GlobalFree函数和GlobalDiscard来释放与删除,其作用与LocalFree和LocalDiscard类似。 GlobalSize函数可以检测内存对象大小;GlobalFlags函数用来检索对象是否可删除,是否已删除等信息;GlobalCompact函数 可以检测全局堆可用内存大小。
  
  6.1.3.5 巨型内存对象
   如果全局内存对象的大小为64KB或更大,那它就是一个巨型内存对象,使用GlobalLock函数加锁巨型内存对象将返回一个巨型指针。分配一个128KB的巨型内存对象,使用下面的代码段:
  
  HGLOBAL hGlobalObject;
  
  char huge * hpGlobalObject;
  
  if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE, 0x20000L)) {
  
   if (hpGlobalObject = (char huge *)GlobalLock(hGlobalObject)) {
  
   /* Use hpGlobalObject as the far address of the globally allocated object. */
  
   ...
  
   GlobalUnlock (hGlobalObject);
  
   }
  
   else {
  
   /* The lock failed. React accordingly. */
  
   }
  
  }
  
  else {
  
   /* The 128K cannot be allocated. React accordingly. */
  
  }
  
   巨型内存对象的修改有一点特殊性,当对象大小增加并超过64K的倍数时,Windows可能要为重新分配的内存对象返回一个新的全局句柄,因此,巨型内存对象的修改应采用下面的形式:
  
  if (hTempHugeObject = GlobalReAlloc(hHugeObject,0x20000L,GMEM_MOVEABLE)){
  
   hHugeObject = hTempObject;
  
  }
  
  else {
  
   /* The object could not be Reallocated. React accordingly. */
  
  }
  
  6.1.4 段
   Windows采用段的概念来管理应用程序的内存,段有代码段和数据段两种,一个应用程序可有多个代码段和数据段。代码段和数据段的数量决定了应用程序的内存模式,图6.2说明了内存模式与应用程序代码段和数据段的关系。
  
  
  
  
   代码段数
  
  单段
   多段
  
  数据段数
   单段
   小内存模式
   中内存模式
  
  多段
   压缩内存模式
   大内存模式
  
  
  图6.2 内存模式图
  
  
  
    段的管理和全局内存对象的管理很类似,段可以是固定的,可移动的和可删除的,其属性在应用程序的模块定义文件中指定。段在全局内存中分配空间, Windows鼓励使用可移动的代码段和数据段,这样可以提高其内存利用效率。使用可删除的代码段可以进一步减小应用程序对内存的影响,如果代码段是可删 除的,在必要时Windows将其删除以满足对全局内存的请求。被删除的段由Windows监控,当应用程序利用该代码段时,Windows自动地将它们 重新装入。
  
  6.1.4.1 代码段
   代码段是不超过64K字节的机器指令,它代表全部或部分应用程序指令。代码段中的数据是只读的,对代码段执行写操作将引起通用保护(GP)错误。
  
    每个应用程序都至少有一个代码段,例如我们前面几章的例子都只有一个代码段。用户也可以生成有多个代码段的应用。实际上,多数Windows应用程序都有 多个代码段。通过使用多代码段,用户可以把任何给定代码段的大小减少到完成某些任务所必须的几条指令。这样,可通过使某些段可删除,来优化应用程序对内存 的使用。
  
   中模式和大模式的应用程序都使用多代码段,这些应用程序的每一个段都有一个或几个源文件。对于多个源文件,将它们分开各自编译,为编译过的代码所属的每个 段命名,然后连接。段的属性在模块定义文件中定义,Windows使用SEGMENTS语句来完成此任务,如下面的代码定义了四个段的属性:
  
  SEGMENTS
  
  MEMORY_MAIN PRELOAD MOVEABLE
  
  MEMORY_INIT LOADONCALL MOVEABLE DISCARDABLE
  
  MEMORY_WNDPROC PRELOAD MOVEABLE
  
  MEMORY_ABOUT LOADONCALL MOVEABLE DISCARDABLE
  
   用户也可以在模块定义文件中用CODE语句为所有未显式定义过的代码段定义缺省属性。例如,要将未列在SEGMENTS语句中的所有段定义为可删除的,可用下面的语句:
  
  CODE MOVEABLE DISCARDABLE。
  
  6.1.4.2 数据段
    每个应用程序都有一个数据段,数据段包含应用程序的堆栈、局部堆、静态数据和全局数据。一个数据段的长度也不能超过64K。数据段可以是固定的或可移动 的,但不能是可删除的。如果数据段是可移动的,Windows在将控制转向应用程序前自动为其加锁,当应用程序分配全局内存,或试图在局部堆中分配超过当 前可分的内存时,可移动数据段可能被移动,因此在数据段中不要保留指向变量的长指针,当数据段移动时,此长指针将失效。
  
   在模块定义文件中用DATA语句定义数据段的属性,属性的缺省值为MOVEABLE和MULTIPLE。MULTIPLE属性使Windows为应用程序的每一个实例拷贝一个应用程序数据段,这就是说每个应用程序实例中数据段的内容都是不同的。
  
  6.1.5 内存管理程序示例Memory
    应用程序Memory示例了部分内存管理,它是一个使用了可删除代码段的中模式Windows应用程序。Memory程序有四个C语言源程序,在模块定义 文件中显示定义了四个代码段,相应地模块定义文件和makefile文件有地些修改,读者可通过比较Memory程序和5.1.2节的例子来体会它们之间 的不同。另外,读者在编译和连接应用程序Memory后,可用Visual C++提供的Windows Heap Walker (HEAPWALK.EXE)来观察Memory运行时的各个段。
  
  
  
  //模块1:MEMORY_MAIN
  
  #include "windows.h"
  
  #include "memory.h"
  
  HANDLE hInst;
  
  /****************************************************************************
  
   MODULE: memory1.c
  
   FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
  
   PURPOSE: calls initialization function, processes message loop
  
  ****************************************************************************/
  
  int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
  
  HANDLE hInstance;
  
  HANDLE hPrevInstance;
  
  LPSTR lpCmdLine;
  
  int nCmdShow;
  
  {
  
   MSG msg;
  
  
  
   if (!hPrevInstance)
  
   if (!InitApplication(hInstance))
  
   return (FALSE);
  
   if (!InitInstance(hInstance, nCmdShow))
  
   return (FALSE);
  
   while (GetMessage(&msg, NULL, NULL, NULL)) {
  
   TranslateMessage(&msg);
  
   DispatchMessage(&msg);
  
   }
  
   return (msg.wParam);
  
  }
  
  
  
  //模块2:MEMORY_INIT
  
  #include "windows.h"
  
  #include "memory.h"
  
  /****************************************************************************
  
   MODULE: memory2.c
  
   FUNCTION: InitApplication(HANDLE)
  
   PURPOSE: Initializes window data and registers window class
  
  ****************************************************************************/
  
  BOOL InitApplication(hInstance)
  
  HANDLE hInstance;
  
  {
  
   WNDCLASS wc;
  
  
  
   wc.style = NULL;
  
   wc.lpfnWndProc = MainWndProc;
  
   wc.cbClsExtra = 0;
  
   wc.cbWndExtra = 0;
  
   wc.hInstance = hInstance;
  
   wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  
   wc.hbrBackground = COLOR_WINDOW+1;
  
   wc.lpszMenuName = "MemoryMenu";
  
   wc.lpszClassName = "MemoryWClass";
  
  
  
   return (RegisterClass(&wc));
  
  }
  
  
  
  /****************************************************************************
  
   MODULE: memory2.c
  
   FUNCTION: InitInstance(HANDLE, int)
  
   PURPOSE: Saves instance handle and creates main window
  
  ****************************************************************************/
  
  BOOL InitInstance(hInstance, nCmdShow)
  
   HANDLE hInstance;
  
   int nCmdShow;
  
  {
  
   HWND hWnd;
  
  
  
   hInst = hInstance;
  
   hWnd = CreateWindow("MemoryWClass", "Memory Sample Application",
  
   WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
  
   CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
  
  
  
   if (!hWnd)
  
   return (FALSE);
  
   ShowWindow(hWnd, nCmdShow);
  
   UpdateWindow(hWnd);
  
   return (TRUE);
  
  }
  
  
  
  //模块3:MEMORY_WNDPROC
  
  #include "windows.h"
  
  #include "memory.h"
  
  /****************************************************************************
  
   MODULE: memory3.c
  
   FUNCTION: MainWndProc(HWND, UINT, WPARAM, LPARAM)
  
   PURPOSE: Processes messages
  
   MESSAGES:
  
   WM_COMMAND - application menu (About dialog box)
  
   WM_DESTROY - destroy window
  
  ****************************************************************************/
  
  long FAR PASCAL __export MainWndProc(hWnd, message, wParam, lParam)
  
  HWND hWnd;
  
  UINT message;
  
  WPARAM wParam;
  
  LPARAM lParam;
  
  {
  
   FARPROC lpProcAbout;
  
  
  
   switch (message) {
  
   case WM_COMMAND:
  
   if (wParam == IDM_ABOUT) {
  
   lpProcAbout = MakeProcInstance(About, hInst);
  
   DialogBox(hInst, "AboutBox", hWnd, lpProcAbout);
  
   FreeProcInstance(lpProcAbout);
  
   break;
  
   }
  
   else
  
   return (DefWindowProc(hWnd, message, wParam, lParam));
  
  
  
   case WM_DESTROY:
  
   PostQuitMessage(0);
  
   break;
  
  
  
   default:
  
   return (DefWindowProc(hWnd, message, wParam, lParam));
  
   }
  
   return (NULL);
  
  }
  
  
  
  //模块4:MEMORY_ABOUT
  
  #include "windows.h"
  
  #include "memory.h"
  
  /****************************************************************************
  
   MODULE: memory4.c
  
   FUNCTION: About(HWND, unsigned, WORD, LONG)
  
   PURPOSE: Processes messages for "About" dialog box
  
   MESSAGES:
  
   WM_INITDIALOG - initialize dialog box
  
   WM_COMMAND - Input received
  
  ****************************************************************************/
  
  BOOL FAR PASCAL __export About(hDlg, message, wParam, lParam)
  
  HWND hDlg;
  
  unsigned message;
  
  WORD wParam;
  
  LONG lParam;
  
  {
  
   switch (message) {
  
   case WM_INITDIALOG:
  
   return (TRUE);
  
   case WM_COMMAND:
  
   if (wParam == IDOK || wParam == IDCANCEL) {
  
   EndDialog(hDlg, TRUE);
  
   return (TRUE);
  
   }
  
   break;
  
   }
  
   return (FALSE);
  
  }
  
   下面是模块定义文件中的一小段,它在编译每个模块时,使用/NT选项为每个段进行命名。
  
  MEMORY1.OBJ: MEMORY1.C $(MEMORY1_DEP)
  
   $(CC) $(CFLAGS) $(CCREATEPCHFLAG) /c /NT MEMORY_MAIN MEMORY1.C
  
  MEMORY2.OBJ: MEMORY2.C $(MEMORY2_DEP)
  
   $(CC) $(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_INIT MEMORY2.C
  
  MEMORY3.OBJ: MEMORY3.C $(MEMORY3_DEP)
  
   $(CC) $(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_WNDPROC MEMORY3.C
  
  MEMORY4.OBJ: MEMORY4.C $(MEMORY4_DEP)
  
   $(CC) $(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_ABOUT MEMORY4.C
  
  
  
  6.2 动态连接库DLL
   使用动态连接库是Windows的一个很重要的特点,它使得多个Windows应用程序可以共享函数代码、数据和硬件,这可以大大提高Windows内存的利用率。
  
    动态连接库是一个可执行模块,它包含的函数可以由Windows应用程序调用执行,为应用程序提供服务。它和我们以前用的C函数库相比,在功能上是很类似 的,其主要区别是动态连接库在运行是连接,C函数库(静态连接库)是在生成可执行文件时由连接器(LINK)连接。静态连接库中的代码在应用程序生成以后 已经连接到应用程序模块之中,但动态连接库中的代码只有在应用程序要用到该代码段时才动态调入DLL中的相应代码。为了让应用程序在执行时能够调入DLL 中正确的代码,Windows提供了动态连接库的引入库。Windows在连接生成应用程序时,如果使用动态连接库函数,连接器并不拷贝DLL中的任何代 码,它只是将引入库中指定所需函数在DLL中位置的信息拷贝在应用程序模块中,当应用程序运行时,这些定位信息在可执行应用程序和动态连接库之间建立动态 连接。静态库、引入库和动态库之间的区别如表6.1所示。
  
  
  
  表6.1 静态库、引入库和动态库之间的区别
  
  库类型
   连接时间
   范例库
   函数范例
   说明
  
  静态库
   连接时
   MLIBCEW.LIB
   strcpy
   函数代码
  
  引入库
   连接时
   LIBW.LIB
   TextOut
   定位信息
  
  动态库
   运行时
   GDI.EXE
   TextOut
   函数代码
  
  
  
  
   DLL不能独立执行,也不能使用消息循环。每个DLL都有一个入口点和一个出口点,具有自己的实例句柄、数据段和局部堆,但DLL没有堆栈,它使用调用 程序的堆栈。DLL也包括有.C文件,.H文件,.RC文件和.DEF文件,另外,在连接时一般要加入SDK库中的LIBENTRY.OBJ文件。
  
  6.2.1 创建动态连接库
   要创建动态连接库,至少有三个文件:
  
  · C语言源文件;
  
  · 一个模块定义文件(.DEF);
  
  · makefile文件。
  
   有了这些文件后,就可以运行Microsoft的程序维护机制(NMAKE),编译并连接源代码文件,生成DLL文件。
  
  6.2.1.1 创建C语言源文件
   和其它C应用程序一样,动态连接库可包含多个函数,每个函数要在被其它应用程序或库使用之前用FAR声明,并且在库的模块定义文件中用EXPORTS语句引出。下面给出一个完整的C语言源文件:
  
  
  
  
  
  /****************************************************************************
  
   PROGRAM: Dlldraw.c
  
   PURPOSE: Contains library routines for drawing
  
  *******************************************************************************/
  
  #include "windows.h"
  
  #include "stdlib.h"
  
  #include "dlldraw.h"
  
  /****************************************************************************
  
   FUNCTION: LibMain(HANDLE, WORD, WORD, LPSTR)
  
   PURPOSE: Is called by LibEntry. LibEntry is called by Windows when the DLL is loaded.
  
   The LibEntry routine is provided in the LIBENTRY.OBJ in the SDK Link Libraries
  
   disk. (The source LIBENTRY.ASM is also provided.)
  
   LibEntry initializes the DLL's heap, if a HEAPSIZE value is specified in the DLL's DEF file.
  
   Then LibEntry calls LibMain. The LibMain function below satisfies that call.
  
   The LibMain function should perform additional initialization tasks required by the DLL.
  
   In this example, no initialization tasks are required. LibMain should return a value of 1
  
   if the initialization is successful.
  
  *******************************************************************************/
  
  int FAR PASCAL LibMain(hModule, wDataSeg, cbHeapSize, lpszCmdLine)
  
  HANDLE hModule;
  
  WORD wDataSeg;
  
  WORD cbHeapSize;
  
  LPSTR lpszCmdLine;
  
  {
  
   return 1;
  
  }
  
  
  
  /****************************************************************************
  
   FUNCTION: WEP(int)
  
   PURPOSE: Performs cleanup tasks when the DLL is unloaded. WEP() is called
  
  automatically by Windows when the DLL is unloaded (no remaining tasks still have
  
  the DLL loaded). It is strongly recommended that a DLL have a WEP() function,
  
  even if it does nothing but returns success (1), as in this example.
  
  *******************************************************************************/
  
  int FAR PASCAL __export _WEP (bSystemExit)
  
  int bSystemExit;
  
  {
  
   return(1);
  
  }
  
  
  
  /****************************************************************************
  
   FUNCTION: RandRect(RECT *) - Get a Rand Rectangle position
  
  ****************************************************************************/
  
  void RandRect(rc)
  
  RECT FAR *rc;
  
  {
  
   rc->top = rand() % 400;
  
   rc->left = rand() % 600;
  
   rc->bottom = rand() % 400;
  
   rc->right = rand() % 600;
  
  }
  
  
  
  /****************************************************************************
  
   FUNCTION: DrawBox(HWND, HPEN, HBRUSH) - Draw a Box
  
   PURPOSE: Draw a box with specified pen and brush.
  
  ****************************************************************************/
  
  void FAR PASCAL __export DrawBox(hWnd, hPen, hBrush)
  
  HWND hWnd;
  
  HPEN hPen;
  
  HBRUSH hBrush;
  
  {
  
   HDC hDC;
  
   HPEN hOldPen;
  
   HBRUSH hOldBrush;
  
   RECT rc;
  
  
  
   RandRect((RECT FAR *)&rc);
  
   hDC = GetDC(hWnd);
  
   hOldPen = SelectObject(hDC, hPen);
  
   hOldBrush = SelectObject(hDC, hBrush);
  
   Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
  
   SelectObject(hDC, hOldPen);
  
   SelectObject(hDC, hOldBrush);
  
   ReleaseDC(hWnd, hDC);
  
  }
  
  
  
  /****************************************************************************
  
   FUNCTION: DrawCircle(HWND, HPEN, HBRUSH) - Draw a Circle
  
   PURPOSE: Draw a circle with specified pen.
  
  ****************************************************************************/
  
  void FAR PASCAL __export DrawCircle(hWnd, hPen, hBrush)
  
  HWND hWnd;
  
  HPEN hPen;
  
  HBRUSH hBrush;
  
  {
  
   HDC hDC;
  
   HPEN hOldPen;
  
   RECT rc;
  
  
  
   RandRect((RECT FAR *)&rc);
  
   hDC = GetDC(hWnd);
  
   hOldPen = SelectObject(hDC, hPen);
  
   Arc(hDC, rc.left, rc.top, rc.right, rc.bottom, rc.left, rc.top, rc.left, rc.top);
  
   SelectObject(hDC, hOldPen);
  
   ReleaseDC(hWnd, hDC);
  
  }
  
  
  
  /****************************************************************************
  
   FUNCTION: DrawPie(HWND, HPEN, HBRUSH) - Draw a pie
  
   PURPOSE: Draw a pie with specified pen and brush.
  
  ****************************************************************************/
  
  void FAR PASCAL __export DrawPie(hWnd, hPen, hBrush)
  
  HWND hWnd;
  
  HPEN hPen;
  
  HBRUSH hBrush;
  
  {
  
   HDC hDC;
  
   HPEN hOldPen;
  
   HBRUSH hOldBrush;
  
   RECT rc;
  
  
  
   RandRect((RECT FAR *)&rc);
  
   hDC = GetDC(hWnd);
  
   hOldPen = SelectObject(hDC, hPen);
  
   hOldBrush = SelectObject(hDC, hBrush);
  
   Pie(hDC, rc.left, rc.top, rc.right, rc.bottom, rc.left, rc.top, rc.right, rc.top);
  
   SelectObject(hDC, hOldPen);
  
   SelectObject(hDC, hOldBrush);
  
   ReleaseDC(hWnd, hDC);
  
  }
  
   在上面的源代码中,有两个函数是DLL源代码所必需的,这就是DLL入口函数LibMain和出口函数WEP。
  
    LibMain函数是DLL的入口点,它由DLL 自动初始化函数LibEntry调用,主要用来完成一些初始化任务。LibMain有四个参数:hint, wDataSeg, cbHeapSize和lpszCmdLine。其中hInst是动态连接库的实例句柄;wDataSeg是数据段(DS)寄存器的值; cbHeapSize是模块定义文件定义的堆的尺寸,LibEntry函数用该值来初始化局部堆;lpszCmdLine包含命令行的信息。
  
   WEP函数是DLL的标准出口函数,它在DLL被卸出之前由Windows调用执行,以完成一些必要的清除工作。WEP函数只使用一个参数nParameter,它用来指示终止状态。
  
   源文件中的其它函数则是DLL为应用程序提供的库函数,DLL设计者可以给它加入自己所需要的功能,如DrawBox,DrawPie和DrawCircle。
  
  6.2.1.2 建立DLL模块定义文件
   每个DLL必须有一个模块定义文件,该文件在使用LINK连接时用于提供定义库属性的引入信息。下面给出一个简单的模块定义文件实例:
  
  LIBRARY DLLDRAW
  
  EXETYPE WINDOWS
  
  CODE PRELOAD MOVEABLE DISCARDABLE
  
  DATA PRELOAD SINGLE
  
  HEAPSIZE 1024
  
  EXPORTS
  
   WEP @1 RESIDENTNAME
  
   DrawBox @2
  
   DrawCircle @3
  
   DrawPie @4
  
   关键字LIBRARY用来标识这个模块是一个动态连接库,其后是库名DRAWDLL,它必须和动态连接库文件名相同。
  
   DATA语句中关键字SINGLE是必须的,它表明无论应用程序访问DLL多少次,DLL均只有单个数据段。
  
   其它关键字的用法同Windows应用程序的模块定义文件一样,这在前面已有叙述,请参见5.1.2.3。
  
  6.2.1.3 编制Makefile文件
   NMAKE是Microsoft的程序维护机制,它控制执行文件的创建工作,以保证只有必要的操作被执行。有五种工具用来创建动态连接库:
  
  CL
   Microsoft C优化编译器,它将C语言源文件编译成目标文件.OBJ。
  
  LINK
    Microsoft 分段可执行连接器,它将目标文件和静态库连接生成动态连接库。LINK命令行有五个参数,用逗号分开:第一个参数列出所有动态连接库用到的目标文件 (.OBJ),如果使用了标准动态连接初始化函数,则必须包括LIBENTRY.OBJ文件;第二个参数指示最终可执行文件名,一般用.DLL作为扩展 名;第三个参数列出创建动态连接库所需要的引入库和静态库;第五个参数是模块定义文件。
  
  IMPLIB
   Microsoft引入库管理器,它根据动态连接库的模块定义文件创建一个扩展名为.LIB的引入库。
  
  RC
   Microsoft Windows资源编译器。所有动态连接库都必须用RC编译,以使它们与Windows 3.1版兼容。
  
  MAPSYM
   Microsoft符号文件生成器,它是可选工具,只用于调试版本。
  
  
  下面给出一个makefile文件的实例:
  
  # Microsoft Visual C++ generated build script - Do not modify
  
  PROJ = DLLDRAW
  
  DEBUG = 1
  
  PROGTYPE = 1
  
  CALLER =
  
  ARGS =
  
  DLLS =
  
  D_RCDEFINES =
  
  R_RCDEFINES =
  
  ORIGIN = MSVC
  
  ORIGIN_VER = 1.00
  
  PROJPATH = D:/JDX/WINSAMP/DLLDRAW/
  
  USEMFC = 0
  
  CC = cl
  
  CPP = cl
  
  CXX = cl
  
  CCREATEPCHFLAG =
  
  CPPCREATEPCHFLAG =
  
  CUSEPCHFLAG =
  
  CPPUSEPCHFLAG =
  
  FIRSTC = SELECT.C
  
  FIRSTCPP =
  
  RC = rc
  
  
  
  CFLAGS_D_WDLL = /nologo /YX /Zp1 /Od /D "_DEBUG" /D "_WINDOWS" /D "_WINDLL" /G2 /W3 /ASw /FR /GD /Zi
  
  CFLAGS_R_WDLL = /nologo /YX /Zp1 /Ox /D "NDEBUG" /D "_WINDOWS" /D "_WINDLL" /G2 /W3 /ASw /FR /GD /Gs
  
  LFLAGS_D_WDLL = /NOLOGO /NOD /ONERROR:NOEXE /ALIGN:16 /CO
  
  LFLAGS_R_WDLL = /NOLOGO /NOD /ONERROR:NOEXE /ALIGN:16
  
  LIBS_D_WDLL = oldnames libw commdlg shell olecli olesvr sdllcew LIBW
  
  LIBS_R_WDLL = oldnames libw commdlg shell olecli olesvr sdllcew LIBW
  
  RCFLAGS = /NOLOGO
  
  RESFLAGS = /NOLOGO -t
  
  RUNFLAGS =
  
  DEFFILE = DLLDRAW.DEF
  
  OBJS_EXT =
  
  LIBS_EXT =
  
  
  
  !if "$(DEBUG)" == "1"
  
  CFLAGS = $(CFLAGS_D_WDLL)
  
  LFLAGS = $(LFLAGS_D_WDLL)
  
  LIBS = $(LIBS_D_WDLL)
  
  MAPFILE = nul
  
  RCDEFINES = $(D_RCDEFINES)
  
  !else
  
  CFLAGS = $(CFLAGS_R_WDLL)
  
  LFLAGS = $(LFLAGS_R_WDLL)
  
  LIBS = $(LIBS_R_WDLL)
  
  MAPFILE = nul
  
  RCDEFINES = $(R_RCDEFINES)
  
  !endif
  
  
  
  !if [if exist MSVC.BND del MSVC.BND]
  
  !endif
  
  SBRS = DLLDRAW.SBR
  
  
  
  all: $(PROJ).DLL $(PROJ).BSC
  
  DLLDRAW.OBJ: DLLDRAW.C $(DLLDRAW_DEP)
  
   $(CC) $(CFLAGS) $(CUSEPCHFLAG) /c DLLDRAW.C
  
  $(PROJ).DLL:: DLLDRAW.OBJ $(OBJS_EXT) $(DEFFILE)
  
   echo >NUL @<<$(PROJ).CRF
  
  DLLDRAW.OBJ +
  
  $(OBJS_EXT)
  
  $(PROJ).DLL
  
  $(MAPFILE)
  
  d:/msvc/lib/+
  
  $(LIBS)
  
  $(DEFFILE);
  
  <<
  
   link $(LFLAGS) @$(PROJ).CRF
  
   $(RC) $(RESFLAGS) $@
  
   implib /nowep $(PROJ).LIB $(PROJ).DLL
  
  
  
  run: $(PROJ).DLL
  
   $(PROJ) $(RUNFLAGS)
  
  
  
  $(PROJ).BSC: $(SBRS)
  
   bscmake @<<
  
  /o$@ $(SBRS)
  
  <<
  
  6.2.2 应用程序访问DLL
   应用程序要访问动态连接库函数,它应该做下面三件事:建立库函数原型,调用库函数,引入库函数。建立库函数原型一般通过在C语言源文件中包含动态连接库的头文件解决,下面就是一个动态连接库的头文件实例:
  
  #define SL_BOX 1 /* Draw a solid border around the rectangle */
  
  #define SL_BLOCK 2 /* Draw a solid rectangle */
  
  #define SL_EXTEND 256 /* Extend the current pattern */
  
  #define SL_TYPE 0x00FF /* Mask out everything but the type flags */
  
  #define SL_SPECIAL 0xFF00 /* Mask out everything but the special flags */
  
  
  
  void FAR PASCAL __export DrawBox(HWND, HPEN, HBRUSH);
  
  void FAR PASCAL __export DrawCircle(HWND, HPEN, HBRUSH);
  
  void FAR PASCAL __export DrawPie(HWND, HPEN, HBRUSH);
  
   头文件中包含了每个库函数的原型语句,原型语句的目的是为编译器定义函数的参数和返回值,以使编译器能正确创建调用库函数的代码。原型语句定义好之后,应用程序就可以象调用静态连接库函数一样调用动态连接库的函数了。
  
   应用程序调用DLL中的引出函数还要在应用程序中对其进行引入,一般有三种方法:
  
   (1) 连接时隐式引入
  
   最常用也最简单的方法是连接时隐式引入,这种方法是在应用程序的连接命令行中列出为动态连接库创建的引入库,这样应用程序在使用DLL的引出函数时,就如同使用静态库中的函数一样了。
  
   (2) 连接时显式引入
  
   和隐式引入一样,显式引入也是在连接时进行的,它通过把所需函数列在应用程序的模块定义文件的IMPORTS语句中完成。对于在模块定义文件中定义了入口序号的DLL函数,采用引入函数名、动态连接库名和入口序号的形式,如:
  
  IMPORTS
  
   DrawBox=DllDraw.2
  
  如果DLL的模块定义文件没有定义引出函数的入口序号,则使用如下引入语句:
  
  IMPORTS
  
   DllDraw.DrawBox
  
   (3) 运行时动态引入
  
    应用程序可以在运行时动态连接DLL函数,当需要调用DLL的引出函数时,应用程序首先装入库,并直接检索所需函数地址,然后才调用该函数。例如,应用程 序如何动态地与Windows INFO.DLL库中的CreateInfo函数连接,使用下面的代码:
  
  
  
  HINSTANCE hLibrary;
  
  FARPROC lpFunc;
  
  hLibrary = LoadLibrary("INFO.DLL");
  
  if (hLibrary >= 32) {
  
   lpFunc = GetProcAddress(hLibrary, "CreateInfo");
  
   if (lpFunc != (FARPROC)NULL)
  
   (*lpFunc)((LPSTR)buffer, 512);
  
   FreeLibrary(hLibrary);
  
  }
  
  
  
  发表于 @ 2006年03月17日 01:34:00 | 评论 (0)
  
  
   [收]Windows Socket网络程序设计
  
  
  
   摘要:大网上找的,看了不错,收下,以后复习方便点。 (全文共127132字)——点击此处阅读全文
  
  
  
  发表于 @ 2006年03月17日 01:31:00 | 评论 (0)
  
  
  2006年03月10日
   [收]从csdn社区找到的关于sizeof(),还有字节对齐
  
  
  解析C语言中的sizeof
  
  一、sizeof的概念 
    sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。 
  
  二、sizeof的使用方法 
    1、用于数据类型 
  
    sizeof使用形式:sizeof(type) 
  
    数据类型必须用括号括住。如sizeof(int)。 
  
    2、用于变量 
  
    sizeof使用形式:sizeof(var_name)或sizeof var_name 
  
    变量名可以不用括号括住。如sizeof (var_name),sizeof var_name等都是正确形式。带括号的用法更普遍,大多数程序员采用这种形式。 
  
    注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。 
  
    如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式。 
  
  三、sizeof的结果 
    sizeof操作符的结果类型是size_t,它在头文件
  
  中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。 
  
    1、若操作数具有类型char、unsigned char或signed char,其结果等于1。 
  
    ANSI C正式规定字符类型为1字节。 
  
     2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long  、float、double、long double类型的sizeof 在ANSI C中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、 2、4、4、4、8、10。 
  
    3、当操作数是指针时,sizeof依赖于编译器。例如Microsoft C/C++7.0中,near类指针字节数为2,far、huge类指针字节数为4。一般Unix的指针字节数为4。 
  
    4、当操作数具有数组类型时,其结果是数组的总字节数。 
  
    5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。 
  
    让我们看如下结构: 
  
    struct {char b; double x;} a; 
  
    在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。 
  
    这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。 
  
    6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。 
  
  四、sizeof与其他操作符的关系 
    sizeof的优先级为2级,比/、%等3级运算符优先级高。它可以与其他操作符一起组成表达式。如i*sizeof(int);其中i为int类型变量。 
  
  五、sizeof的主要用途 
    1、sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如: 
  
    void *malloc(size_t size), 
  
    size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。 
  
    2、sizeof的另一个的主要用途是计算数组中元素的个数。例如: 
  
    void * memset(void * s,int c,sizeof(s))。 
  
  六、建议 
    由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。
  
  本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC中sizeof的主要用法。
  
  1、 sizeof应用在结构上的情况
  
  请看下面的结构:
  
  struct MyStruct
  
  {
  
  double dda1;
  
  char dda;
  
  int type
  
  };
  
  对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:
  
  sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
  
  但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?
  
   其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存 放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。
  
  类型
  对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
  
  Char
  偏移量必须为sizeof(char)即1的倍数
  
  int
  偏移量必须为sizeof(int)即4的倍数
  
  float
  偏移量必须为sizeof(float)即4的倍数
  
  double
  偏移量必须为sizeof(double)即8的倍数
  
  Short
  偏移量必须为sizeof(short)即2的倍数
  
  
  
   各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小 为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
  
  下面用前面的例子来说明VC到底怎么样来存放结构的。
  
  struct MyStruct
  
  {
  
  double dda1;
  
  char dda;
  
  int type
  
  };
  
   为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0 刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可 以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起 始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个 字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占 用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
  
  下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:
  
  struct MyStruct
  
  {
  
  char dda;
  
  double dda1;
  
  int type
  
  };
  
  这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)
  
  struct MyStruct
  
  {
  
  char dda;//偏移量为0,满足对齐方式,dda占用1个字节;
  
  double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8
  
  //的倍数,需要补足7个字节才能使偏移量变为8(满足对齐
  
  //方式),因此VC自动填充7个字节,dda1存放在偏移量为8
  
  //的地址上,它占用8个字节。
  
  int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍
  
  //数,满足int的对齐方式,所以不需要VC自动填充,type存
  
  //放在偏移量为16的地址上,它占用4个字节。
  
  };//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构
  
  //的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof
  
  //(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为
  
  //sizeof(double)=8的倍数。
  
  
  
  所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。
  
  
  
  VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
  
   VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏 移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条 件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;
  
  否则必须为n的倍数。下面举例说明其用法。
  
  #pragma pack(push) //保存对齐状态
  
  #pragma pack(4)//设定为4字节对齐
  
  struct test
  
  {
  
  char m1;
  
  double m4;
  
  int m3;
  
  };
  
  #pragma pack(pop)//恢复对齐状态
  
   以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开 始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字 节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍 数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)
  
  2、 sizeof用法总结
  
  在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。
  
  A. 参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。
  
  B. 参数为数组或指针。下面举例说明.
  
  int a[50]; //sizeof(a)=4*50=200; 求数组所占的空间大小
  
  int *a=new int[50];// sizeof(a)=4; a为一个指针,sizeof(a)是求指针
  
  //的大小,在32位系统中,当然是占4个字节。
  
  C. 参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。
  
  第二、没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一
  
  个实例在内存中都有唯一的地址。
  
  下面举例说明,
  
  Class Test{int a;static double c};//sizeof(Test)=4.
  
  Test *s;//sizeof(s)=4,s为一个指针。
  
  Class test1{ };//sizeof(test1)=1;
  
  D. 参数为其他。下面举例说明。
  
  int func(char s[5]);
  
  {
  
  cout<
  //数的参数在传递的时候系统处理为一个指针,所
  
  //以sizeof(s)实际上为求指针的大小。
  
  return 1;
  
  }
  
  sizeof(func(“1234”))=4//因为func的返回类型为int,所以相当于
  
  //求sizeof(int).
  
  
  
  以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配策略,这样的话可以避免一些错误。
  
  
  
  --------------------------------------------------------------------------------
  
  
  sizeof详解
  
  1、什么是sizeof
  
   首先看一下sizeof在msdn上的定义:
  
    The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t.
  
   看到return这个字眼,是不是想到了函数?错了,sizeof不是一个函数,你见过给一个函数传参数,而不加括号的吗?sizeof可以,所以 sizeof不是函数。网上有人说sizeof是一元操作符,但是我并不这么认为,因为sizeof更像一个特殊的宏,它是在编译阶段求值的。举个例子:
  
  cout<<
  cout<<<<
  
   在编译阶段已经被翻译为:
  
  cout<<4<
  cout<<1<
  
   这里有个陷阱,看下面的程序:
  
  int a = 0;
  cout<<
  cout< < <
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值