Profil de Libin绿色家园PhotosBlogListes Outils Aide

Blog


27 juin

可变参数的机制


一)写一个简单的可变参数的C函数

下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的
C函数要在程序中用到以下这些宏:
void va_start( va_list arg_ptr, prev_param );

type va_arg( va_list arg_ptr, type );

void va_end( va_list arg_ptr );
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个
头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数
参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.
void simple_va_fun(int i, ...)
{
va_list arg_ptr;
int j=0;

va_start(arg_ptr, i);
j=va_arg(arg_ptr, int);
va_end(arg_ptr);
printf("%d %d\n", i, j);
return;
}
我们可以在我们的头文件中这样声明我们的函数:
extern void simple_va_fun(int i, ...);
我们在程序中可以这样调用:
simple_va_fun(100);
simple_va_fun(100,200);
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变
量是指向参数的指针.
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第
一个可变参数的前一个参数,是一个固定的参数.
3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个
参数是你要返回的参数的类型,这里是int型.
4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使
用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获
取各个参数.
如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:
1)simple_va_fun(100);
结果是:100 -123456789(会变的值)
2)simple_va_fun(100,200);
结果是:100 200
3)simple_va_fun(100,200,300);
结果是:100 200
我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果
正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果
的原因和可变参数在编译器中是如何处理的.

(二)可变参数在编译器中的处理

我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,
由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下
面以VC++中stdarg.h里x86平台的宏定义摘录如下('\'号表示折行):

typedef char * va_list;

#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

#define va_arg(ap,t) \
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define va_end(ap) ( ap = (va_list)0 )

定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函
数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我
们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再
看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的
地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆
栈的地址,如图:

高地址|-----------------------------|
|函数返回地址 |
|-----------------------------|
|....... |
|-----------------------------|
|第n个参数(第一个可变参数) |
|-----------------------------|<--va_start后ap指向
|第n-1个参数(最后一个固定参数)|
低地址|-----------------------------|<-- &v
图( 1 )

然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我
们看一下va_arg取int型的返回值:
j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回
ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址
(图2).然后用*取得这个地址的内容(参数值)赋给j.

高地址|-----------------------------|
|函数返回地址 |
|-----------------------------|
|....... |
|-----------------------------|<--va_arg后ap指向
|第n个参数(第一个可变参数) |
|-----------------------------|<--va_start后ap指向
|第n-1个参数(最后一个固定参数)|
低地址|-----------------------------|<-- &v
图( 2 )

最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再
指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不
会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.
在这里大家要注意一个问题:由于参数的地址用于va_start宏,所
以参数不能声明为寄存器变量或作为函数或数组类型.
关于va_start, va_arg, va_end的描述就是这些了,我们要注意的
是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.

(三)可变参数在编程中要注意的问题

因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,
可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能
地识别不同参数的个数和类型.
有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数
printf是从固定参数format字符串来分析出参数的类型,再调用va_arg
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通
过在自己的程序里作判断来实现的.
另外有一个问题,因为编译器对可变参数的函数的原型检查不够严
格,对编程查错不利.如果simple_va_fun()改为:
void simple_va_fun(int i, ...)
{
va_list arg_ptr;
char *s=NULL;

va_start(arg_ptr, i);
s=va_arg(arg_ptr, char*);
va_end(arg_ptr);
printf("%d %s\n", i, s);
return;
}
可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现
core dump(Unix) 或者页面非法的错误(window平台).但也有可能不出
错,但错误却是难以发现,不利于我们写出高质量的程序.
以下提一下va系列宏的兼容性.
System V Unix把va_start定义为只有一个参数的宏:
va_start(va_list arg_ptr);
而ANSI C则定义为:
va_start(va_list arg_ptr, prev_param);
如果我们要用system V的定义,应该用vararg.h头文件中所定义的
宏,ANSI C的宏跟system V的宏是不兼容的,我们一般都用ANSI C,所以
用ANSI C的定义就够了,也便于程序的移植.

如何减小可执行文件的大小

前一阵下载了个keyboard hook的 实例,发现我编译后的dll比它的大了很多,因此想做个比较小巧的hook dll,减小到3.5KB后没办法继续了,但是觉得还可以继续减小,因为用UltraEdit打开看还有很多为0的部分,于是google,找到了这方面比较好的参考资料,把其中比较重要的简单总结了一下.


  减少exe(dll)的代码,主要从几个方面能够入手:
  1.代码质量.Consider refactoring when you try to copy & paste code. 拷贝代码不仅是  造成代码的难以修改和维护,而且也增加最后可执行文件的大小.
  2.尽量的使用系统的动态链接库,比如kernel32.dll 等各个版本的系统必备的DLL. 象有些dll就不  行,例如msvcrt.dll在 win98上就没有. 但是项目使用到别的库经常是没法避免的,比如STL.
  3.编译器选项(特别是链接器选项)优化. 用AppWizards建立一个Hello,world的GUI Win32程序,不加入任何代码,Release编译后也有40KB. 用Denpendency walker打开看看, 发现Kernel32.dll中引用了很多函数,但是实际上你的代码里面都没有引用到,那是因为默认链接器是把C Runtime library静态链接到你的代码里面去了,而你可能并没有使用到任何C Runtime Library的函数 .因此第一步就是在linker选项里面勾上ignore default libraries(这将使得链接器不在默认链接CRT). 然后加上你需要引用的库,例如kernel32.lib, 另外还需要设置EntryPoint(因为默认使用的是C Runtime Libray中的_mainCRTStartup 或者_WinMainCRTStartup函数). 第二步,加上/OPT:REF /OPT:ICF /OPT:NOWIN98 最后一个参数一般都能让exe进一步减少.另外是段合并(merge section)的优化,即把.rdata .text段都合并到.data段里面,但是这个优化并不推荐. 最后就是对齐的优化, /FILEALIGN(只能用在VC6上面),通过减少这个值能够去掉代码中由于对齐而产生的多余代码. 详细参考可以见参考资料第二个链接. 编译器的选项 ,minize code size以及globaloptimization + favor small code 也能够减少,但是通常不会减少得很多.
  4. exe压缩器. aspack,upxshell是用得最多的exe压缩器,通常可以把可执行文件减少一半左右.

下面是最小的win32程序,VC6下面编译后只有480字节.

#include <windows.h>

 

// Make section alignment really small

#pragma comment(linker, "/FILEALIGN:16")

#pragma comment(linker, "/ALIGN:16")

 

// Merge sections

#pragma comment(linker, "/MERGE:.rdata=.data")

#pragma comment(linker, "/MERGE:.text=.data")

#pragma comment(linker, "/MERGE:.reloc=.data")

 

// Favour small code

#pragma optimize("gsy", on)

 

// Single entrypoint

int WinMainCRTStartup()

{

return 0;

}

 

上面的代码是从一篇文章中COPY过来的, 见后面的参考.做一个win32 GUI hello,world,上面的法则仍然适用.用AppWizards生成一个Hello,world以后,把所有多余的全部去掉,例如资源文件. 甚至函数调用也不用了全部合并到WinMain中去(因为通过/OPT:NOWIN98和/FILEALIGN设置后,即使增加减少一丁点的代码也能够立即反应最后的EXE大小中去,而这些函数都只调用了一次,因此去掉了),生成的代码1.14KB. 如果再去掉WM_PAINT中的画Hello,world的代码, 最后大小为1.04KB, 并且只依赖于kernel32.dll 和user32.dll



// MiniWinGUI.cpp : Defines the entry point for the application.
//
// Windows Header Files:
#include <windows.h>

#pragma comment(linker, "/FILEALIGN:16")
#pragma comment(linker, "/ALIGN:16")

#pragma comment(linker, "/OPT:REF")
#pragma comment(linker, "/OPT:ICF")
#pragma comment(linker, "/OPT:NOWIN98")

// Merge sections
#pragma comment(linker, "/MERGE:.rdata=.data")
#pragma comment(linker, "/MERGE:.text=.data")
#pragma comment(linker, "/MERGE:.reloc=.data")

// Favour small code
#pragma optimize("gsy", on)


#pragma comment(linker, "/ENTRY:WinMain")

// Global Variables:
HINSTANCE hInst;                                // current instance
TCHAR * szTitle = "MiniWinGUI";

// Foward declarations of functions included in this code module:
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
//    hInst = hInstance; // Store instance handle in our global variable

    WNDCLASSEX wcex;
   
    wcex.cbSize = sizeof(WNDCLASSEX);
   
    wcex.style             = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = (WNDPROC)WndProc;
    wcex.cbClsExtra        = 0;
    wcex.cbWndExtra        = 0;
    wcex.hInstance        = hInstance;
    wcex.hIcon            = NULL;
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground    = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName    = NULL;
    wcex.lpszClassName    = szTitle;
    wcex.hIconSm        = NULL;
   
    RegisterClassEx(&wcex);
   
   
    HWND hWnd;
   
   
    hWnd = CreateWindow(szTitle,szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
   

    if (!hWnd)
    {
        return FALSE;
    }


    ShowWindow(hWnd, SW_SHOW);
    UpdateWindow(hWnd);
   

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
   
    return msg.wParam;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   
    switch (message)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}


 

参考资料:

Matt Pietrek :Reduce EXE and DLL size  with libctiny.lib: http://msdn.microsoft.com/msdnmag/issues/01/01/hood/default.aspx

James :Techniques for reducing  executable size:

http://www.catch22.net/tuts/minexe.asp

瘦身你的执行文件

在网上,有好多绿色软件,不仅功能强大,而且软件本身的体积非常小。有的通常
只在几十K左右。那他们是怎么做到把软件做的怎么小的呢?现在我手把手的告诉
你如何通过修改程序的编译选项来瘦身你的执行文件。

先看一个最典型的程序:
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}

上面的程序之所以被称之为典型,是因为他有如下的内容:
1、系统函数调用:printf
2、有静态数据段

好,现在把此文件放到VisualStudio6.0中进行编译,看看文件有多大。
1、用VisualStudio6.0打开HelloWorld.cpp文件,直接按F7。然后点击OK,生成
Project文件,然后进行编译。编译完成了以后,看看Debug目录下的执行文件的大
小,为172,096Bytes。

2、刚才编译的Debug文件,现在修改成Release文件看看。选择Win32 Release,再
编译。察看执行文件大小,现在成了40,960Bytes。看来Release版本的要比Debug
的小。

3、检查代码优化:发现执行文件的优化是Maximize Speed。那么修改成Minimize
Size看看。重新编译,得到执行文件的大小为:40,960Bytes。看来大小没什么变
化。其实这是由于我们的代码本身太小的缘故,导致即使变化了也看不出来。

4、想想我们程序的main函数是由CRT类库进行引导的。在我们现在的设定当中,由
于采取的是系统缺省的编译连接方式(缺省为编译为Single Thread,Static
Library),所以,在我们的执行文件当中,包含了CRT的二进制代码。好,修改编
译选项:C/C++ => Category:Code Generation => Use run-time
library:MutiThreaded Dll。编译看看:执行文件大小变成了16,384Bytes。

5、刚才的设定确实不错,一下子把执行文件大小减小到了16K。现在用UltraEdit
看看执行文件都是些什么内容。结果大吃一惊:基本上都是0。看来这个有减小的
必要了。都知道,执行文件都有自己的代码段,数据段等等,每个段的大小也是采
用编译器缺省设定的。好,我们来修改一下段的大小看看:
5.1 连接选项中有一个是/opt:nowin98,意思是将段的大小设定成为Win2000适应
的。编译看看:哇塞,变成了2,560byte。看来这个选项确实把文件变小了N多。
5.2 在查察连接选项中还有没有什么特别的。发现/align:xx还可以将段大小缩
小。通过UltraEdit察看刚才/opt:nowin98编译出来的文件,发现每个段的大小都
是4K的整数倍。看来/align:xx还有减小的趋势。试一把再说:添加连接选项:
/align:16(这个大小已经是能够设定的最小的了)。看看结果:1,408Bytes。厉
害,现在代码更小了。
5.3 现在回想起来,执行文件大小有数据段,执行代码段等等,如果把这些段都合
并起来,是不是就会把段之间的冗余有减小了呢?再试试看:添加选项:
/merge:.data=.text /merge:.rdata=.text。再看看文件大小:1,328bytes。真的
很不错了。

6、刚才的设定确实不错,似乎达到了我们想要的极限了。但是回头想一下,如果
没有CRT库的话,会不会更小了?实际上确实这样。添加连接选项: /entry:
main,把入口地址直接指向我们的main函数看看。得到592Bytes。

最终我们得到我们最后的大小592Bytes了。我想这也许是我们通过编译器能够编译
出来的最小的代码了。

结论:
通过上述的步骤,我们了解了如何修改那些编译连接选项来达到执行文件瘦身的目
的。但是,通常来讲,在我们的Release文件当中,并不需要如此小的执行文件。
如果想达到瘦身的目的,修改为library:MutiThreaded Dll和添加/opt:nowin98已
经是很好的选择了。其他别的选项在编译的时候或多或少的有警告出现,而且,带
有那些编译选项编出来的执行文件也不一定在各个平台上能够适用。

另外:如果你的执行文件即使通过了这些设定还是比较大的话,也可以通过一些
EXE文件压缩工具来进行压缩。比如UPX等等。在此不再细说了。

以上部分的不足之处,还请多多指正。

tuyang 2004/09/20

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=140523

结果和目标

"我并不仅仅在寻求'结果'……如果只为了寻求'结果'的话,人是容易走捷径的……在走捷径时,也许就会迷失真实,连满腔的热忱也会逐渐丧失。我认为重要的是'寻求真实的意志'!只要有了这种向真实前进的意志,即使这次失败了,我也终于会达到目标的,因为我仍在继续前进……这是不会错的!"

static用法小结

static关键字是C, C++中都存在的关键字, 它主要有三种使用方式, 其中前两种在C/C++语言中使用, 第三种只在C++中使用(C,C++中具体细微操作不尽相同, 本文以C++为准).
(1)局部静态变量
(2)外部静态变量/函数
(3)静态数据成员/成员函数
下面就这三种使用方式及注意事项分别说明

一、局部静态变量
在C/C++中, 局部变量按照存储形式可分为三种auto, static, register
(<C语言程序设计(第二版)>谭浩强, 第174-175页)
与auto类型(普通)局部变量相比, static局部变量有三点不同
1. 存储空间分配不同
auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.
2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次
3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的. (对于C++中的class对象例外, class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)

特点: static局部变量的"记忆性"与生存期的"全局性"
所谓"记忆性"是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值.
示例程序一
#include <iostream>

using namespace std;

void staticLocalVar()
{
 static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初始化工作
 cout<<"a="<<a<<endl;
 ++a;
}

int main()
{
 staticLocalVar(); // 第一次调用, 输出a=0
 staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出a=1
 return 0;
}

应用:
 利用"记忆性", 记录函数调用的次数(示例程序一)
   利用生存期的"全局性", 改善"return a pointer / reference to a local object"的问题. Local object的问题在于退出函数, 生存期即结束,. 利用static的作用, 延长变量的生存期.
示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
 static char strBuff[16]; // static局部变量, 用于返回地址有效
 const unsigned char *pChIP = (const unsigned char *)&IpAddr;
 sprintf(strBuff, "%u.%u.%u.%u",  pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
 return strBuff;
}

注意事项:
1. "记忆性", 程序运行很重要的一点就是可重复性, 而static变量的"记忆性"破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
2. "生存期"全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 ---- 不可重入性!!!
这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.
(不可重入性的例子可以参见<effective C++ (2nd)>(影印版)第103-105页)
下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标上行号)
① const char * IpToStr(UINT32 IpAddr)
② {
③  static char strBuff[16]; // static局部变量, 用于返回地址有效
④  const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤  sprintf(strBuff, "%u.%u.%u.%u",  pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
⑥  return strBuff;
⑦ }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP地址转换成点分10进制的字符串形式. 现A先获得执行机会, 执行IpToStr(), 传入的参数是0x0B090A0A, 顺序执行完应该返回的指针存储区内容是:"10.10.9.11", 现执行到⑥时, 失去执行权, 调度到B线程执行, B线程传入的参数是0xA8A8A8C0, 执行至⑦, 静态存储区的内容是192.168.168.168. 当再调度到A执行时, 从⑥继续执行, 由于strBuff的全局唯一性, 内容已经被B线程冲掉, 此时返回的将是192.168.168.168字符串, 不再是10.10.9.11字符串.

二、外部静态变量/函数
在C中static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。, 但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处"static"的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
示例程序三:
 
//file1.cpp

static int varA;
int varB;
extern void funA()
{
……
}

static void funB()
{
……
}

//file2.cpp

extern int varB; // 使用file1.cpp中定义的全局变量
extern int varA; // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定义的函数
extern void funB(); // 错误! 无法使用file1.cpp文件中static函数

 

三、静态数据成员/成员函数(C++特有)
C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此类中的唯一性. 这也是"属于一个类而不是属于此类的任何特定对象的变量和函数"的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static, 在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针, static成员函数调用时, 没有this指针. )
请看示例程序四(<effective c++ (2nd)>(影印版)第59页)
class EnemyTarget {
public:
  EnemyTarget() { ++numTargets; }
  EnemyTarget(const EnemyTarget&) { ++numTargets; }
  ~EnemyTarget() { --numTargets; }
  static size_t numberOfTargets() { return numTargets; }
  bool destroy();   // returns success of attempt to destroy EnemyTarget object
private:
  static size_t numTargets;               // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;

在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的.
另外, 在设计类的多线程操作时, 由于POSIX库下的线程函数pthread_create()要求是全局的, 普通成员函数无法直接做为线程函数, 可以考虑用Static成员函数做线程函数.
20 juin

看了被误解的C++——学习和使用

有一定的深度,特别是对boost实例性说明,真是抛砖引玉了
12 juin

便利的开发工具-log4cpp快速使用指南

log4cpp是个基于LGPL的开源项目,是基于优秀的日志处理跟踪项目Java语言的log4j移植过来的。log4j介绍的文档很多,在java领域使用的也比较广泛,而这个功能强大的库对国内的C++语言开发人员却使用的不多。这里从开发人员使用的角度介绍这个库,使开发人员用最少的代价尽快掌握这种技术。下面先简单介绍一下这个项目的优点(也是log4j的优点),然后分原理,手动使用步骤,配置文件驱动方式使用步骤,其他考虑等方面进行讨论。 以下讨论基于log4cpp0.3.4b。

0. 优点

  • 提供应用程序运行上下文,方便跟踪调试;
  • 可扩展的、多种方式记录日志,包括命令行、文件、回卷文件、内存、syslog服务器、Win事件日志等;
  • 可以动态控制日志记录级别,在效率和功能中进行调整;
  • 所有配置可以通过配置文件进行动态调整;
  • 多语言支持,包括Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(log4p)等;




回页首


1. 原理

log4cpp有3个主要的组件:categories(类别)、appenders(附加目的地)、和 layouts(布局)。(为了方便大家理解,文中尽量使用英文原词)

layout类控制输出日志消息的显示样式(看起来像什么)。log4cpp当前提供以下layout格式:

log4cpp::BasicLayout 	   // 以"时间戳 优先级(priority,下文介绍)
                        // 类别(category,下文介绍)
		   // NDC标签(nested diagnostic contexts 下文介绍): 日志信息"。
	             // 如:1056638652 INFO main : This is some info
log4cpp::PatternLayout 	// 让用户根据类似于 C 语言 printf 函数的转换模式来指定输出格式。格式定义见代码附带文档。
log4cpp::SimpleLayout 	// 以"优先级(priority) - 日志信息"格式显示。 

appender类用来输出日志(被layout格式化后的)到一些设备上。比如文件、syslog服务、某个socket等。可以定义自己的appender类输出日志信息到别的设备上,比如应用自身的日子处理进程、数据库等。appender和layout的关系是layout附在appender上,appender类调用layout处理完日志消息后,记录到某个设备上。log4cpp当前提供以下appender:

log4cpp::IdsaAppender 			// 发送到IDS或者logger, 详细见 http://jade.cs.uct.ac.za/idsa/
log4cpp::FileAppender 			// 输出到文件
log4cpp::RollingFileAppender 	// 输出到回卷文件,即当文件到达某个大小后回卷
log4cpp::OstreamAppender 		// 输出到一个ostream类
log4cpp::RemoteSyslogAppender 	// 输出到远程syslog服务器
log4cpp::StringQueueAppender 	// 内存队列
log4cpp::SyslogAppender 		// 本地syslog
log4cpp::Win32DebugAppender 	// 发送到缺省系统调试器
log4cpp::NTEventLogAppender 	// 发送到win 事件日志

category 类真正完成记录日志功能,两个主要组成部分是appenders和priority(优先级)。优先级控制哪类日志信息可以被这个category记录,当前优先级分为:NOTSET, DEBUG, INFO, NOTICE, WARN, ERROR, CRIT, ALERT 或 FATAL/EMERG 。每个日志信息有个优先级,每个category有个优先级,当消息的优先级大于等于category的优先级时,这个消息才会被category记录,否则被忽略。优先级的关系如下。category类和appender的关系是,多个appender附在category上,这样一个日志消息可以同时输出到多个设备上。

NOTSET < DEBUG < INFO < NOTICE < WARN < ERROR < CRIT < ALERT < FATAL = EMERG

category被组织成一个树,子category创建时优先级缺省NOTSET,category缺省会继承父category的appender。而如果不希望这种appender的继承关系,log4cpp允许使用additivity 标签,为false时新的appender取代category的appender列表。

为了更好的理解上面的概念下面以手动使用方式举例。




回页首


2. 手动使用步骤

手动使用log4cpp的基本步骤如下:

  1. 实例化一个layout 对象;
  2. 初始化一个appender 对象;
  3. 把layout对象附着在appender对象上;
  4. 调用log4cpp::Category::getInstance("name"). 实例化一个category对象;
  5. 把appender对象附到category上(根据additivity的值取代其他appender或者附加在其他appender后)。
  6. 设置category的优先级;
// FileName: test_log4cpp1.cpp
// Test log4cpp by manual operation.
// Announce: use as your own risk.
// Compile : g++ -otest1 -llog4cpp test_log4cpp1.cpp
// Run     : ./test1
// Tested  : RedHat 7.2 log4cpp0.3.4b
// Author  : liqun (liqun@nsfocus.com)
// Data    : 2003-6-27
#include	"log4cpp/Category.hh"
#include	"log4cpp/FileAppender.hh"
#include	"log4cpp/BasicLayout.hh"
int main(int argc, char* argv[])
{
        // 1实例化一个layout 对象
        log4cpp::Layout* layout = 
        new log4cpp::BasicLayout();
        // 2. 初始化一个appender 对象
	log4cpp::Appender* appender = new 
              log4cpp::FileAppender("FileAppender",
              "./test_log4cpp1.log");
	// 3. 把layout对象附着在appender对象上
	appender->setLayout(layout);
	// 4. 实例化一个category对象
	log4cpp::Category& warn_log = 
        log4cpp::Category::getInstance("mywarn");
	// 5. 设置additivity为false,替换已有的appender
        warn_log.setAdditivity(false);
	// 5. 把appender对象附到category上
	warn_log.setAppender(appender);
	// 6. 设置category的优先级,低于此优先级的日志不被记录
	warn_log.setPriority(log4cpp::Priority::WARN);
	// 记录一些日志
	warn_log.info("Program info which cannot be wirten");
	warn_log.debug("This debug message will fail to write");
	warn_log.alert("Alert info");
	// 其他记录日志方式
	warn_log.log(log4cpp::Priority::WARN, "This will be a logged warning");
	log4cpp::Priority::PriorityLevel priority;
	bool this_is_critical = true;
	if(this_is_critical)
		priority = log4cpp::Priority::CRIT;
	else
		priority = log4cpp::Priority::DEBUG;
	warn_log.log(priority,"Importance depends on context");
	
	warn_log.critStream() << "This will show up << as " 
	<< 1 << " critical message" 
	<< log4cpp::CategoryStream::ENDLINE;
	// clean up and flush all appenders
	log4cpp::Category::shutdown();
	return 0;
}





回页首


3. 配置文件驱动方式使用步骤

另一个非常优秀的特征就是通过读取配置文件,确定category、appender、layout等对象。也是我们非常推荐的使用方式,可以灵活地通过配置文件定义所有地对象及其属性,不用重新编码,动态更改日志记录的策略。

Log4cpp主要提供了 log4cpp::PropertyConfigurator 和log4cpp::SimpleConfigurator两种机制(文件格式),但 log4cpp::SimpleConfigurator将来不再支持了,而且格式非常简单,这里就不多说明,自己看源码吧。

配置文件的格式和log4j的配置文件一样,是标准的java属性文件格式。下面是附带的例子配置文件:

# a simple test config
#定义了3个category sub1, sub2, sub1.sub2
log4j.rootCategory=DEBUG, rootAppender
log4j.category.sub1=,A1
log4j.category.sub2=INFO
log4j.category.sub1.sub2=ERROR, A2
# 设置sub1.sub2 的additivity属性
log4j.additivity.sub1.sub2=false
#定义rootAppender类型和layout属性
log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender
log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayout
#定义A1的属性
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.fileName=A1.log
log4j.appender.A1.layout=org.apache.log4j.SimpleLayout
#定义A2的属性
log4j.appender.A2=org.apache.log4j.ConsoleAppender
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=The message '%m' at time %d%n 

配置文件语法如下,不是很规范,结合上面的例子,应该可以看懂。

		log4j / log4cpp . [category / appender].[category or appender 's name].[category or appender 's property] 
= [Appender / Layout / property's value / Priority, appender name1 [appender name2 ...]]
	[appender]
		{ConsoleAppender}
		{FileAppender}	// 当appender的类型是FileAppender时,可以定义它下面的属性。
			[fileName]	string	foobar	// 格式是:属性名 值的类型 缺省值
			[append]	bool	true
		{RollingFileAppender}
			[fileName] 	string	foobar
			[maxFileSize]	num	10*1024*1024
			[maxBackupIndex]	num	1
			[append]	bool	true
		{SyslogAppender}
			[syslogName]	string	syslog
			[syslogHost]	string	localhost
			[facility]	num	-1	// * 8 to get LOG_KERN, etc. compatible values. 
			[portNumber]	num	-1
		{IdsaAppender}
			[idsaName]	string	foobar
		{Win32DebugAppender}
		{NTEventLogAppender}
			[source]	string	foobar
			
			[threshold]	string ""	// 全部 
			// 如果此类型appender需要layout,必须定义此appender的下面属性
			[layout]
				{BasicLayout}	
				{SimpleLayout}
				{PatternLayout}		// 当layout的值是BasicLayout时,需要定义下面的属性。
					[ConversionPattern]
					
	[rootCategory]
	[additivity]
		[category name]	bool	true
		

基本使用步骤是:

  1. 读取解析配置文件;
  2. 实例化category对象;
  3. 正常使用这些category对象进行日志处理;

下面是个简单的使用代码,使用起来是非常方便的:

// FileName: test_log4cpp2.cpp
// Test log4cpp by config file.
// Announce: use as your own risk.
// Compile : g++ -llog4cpp test_log4cpp2.cpp
// Run     : ./a.out
// Tested  : RedHat 7.2 log4cpp0.3.4b
// Author  : liqun (liqun@nsfocus.com)
// Data    : 2003-6-27
#include "log4cpp/Category.hh"
#include "log4cpp/PropertyConfigurator.hh"
int main(int argc, char* argv[])
{
	// 1 读取解析配置文件
	// 读取出错, 完全可以忽略,可以定义一个缺省策略或者使用系统缺省策略
	// BasicLayout输出所有优先级日志到ConsoleAppender
    try { 
		log4cpp::PropertyConfigurator::configure("./log4cpp.conf");
	} catch(log4cpp::ConfigureFailure& f) {
		std::cout << "Configure Problem " << f.what() << std::endl;
        return -1;
    }
	
	// 2 实例化category对象
	// 这些对象即使配置文件没有定义也可以使用,不过其属性继承其父category
	// 通常使用引用可能不太方便,可以使用指针,以后做指针使用
	// log4cpp::Category* root = &log4cpp::Category::getRoot();
    log4cpp::Category& root = log4cpp::Category::getRoot();
	
    log4cpp::Category& sub1 = 
        log4cpp::Category::getInstance(std::string("sub1"));
    log4cpp::Category& sub3 = 
        log4cpp::Category::getInstance(std::string("sub1.sub2"));
	// 3 正常使用这些category对象进行日志处理。
	// sub1 has appender A1 and rootappender.
	sub1.info("This is some info");
	sub1.alert("A warning");
	
	// sub3 only have A2 appender.
	sub3.debug("This debug message will fail to write");
	sub3.alert("All hands abandon ship");
	sub3.critStream() << "This will show up << as " << 1 << " critical message" 
	<< log4cpp::CategoryStream::ENDLINE;
	sub3 << log4cpp::Priority::ERROR 
              << "And this will be an error"  
              << log4cpp::CategoryStream::ENDLINE;
	sub3.log(log4cpp::Priority::WARN, "This will be a logged warning");
	
	return 0;
}





回页首


4. 相关考虑

性能问题,可能是很多想使用log4cpp的程序员关心的问题。在参考资料2中有一段描述。结论就是log4j以及log4cpp是以性能为首要目标的;如果关闭日志记录的话,对性能影响可以忽略;打开日志记录,主要消耗是在记录动作,而不是库的管理过程;所以你尽可放心的使用。实在要深究性能的话。可以从下面方面提高:

输出的日志消息不要使用复杂的转换或者处理,比如: sub1.debug(string("Current num is") + i + GetCurStat()); 这种情况即使不进行日志处理,括号中的语句还是会执行。变通方法是:

if(sub1.isDebugEnabled())
{
	sub1.debug(string("Current num is") + i + GetCurStat());
}

安全性问题对于商业软件开发可能也是问题。可能不希望别人通过修改配置文件获取程序的调试等程序内部运行情况的日志信息。比较稳妥的方案或者是加密配置文件,运行中解密,输出到临时文件后读取;或者在发行版本里读取配置文件后,强行把低于某个优先级的category设到比较高的优先级。

多线程安全性问题。当前log4cpp还没有宣称自己是多线程安全的,不过其代码中大多数可能冲突的地方都增加了线程互斥控制,对多线程环境应该问题不大。但为了加入这个特性,linux下编译log4cpp时,configure请加入--with-pthreads 或者--with-omnithreads选项。Win版本已经加入对MS线程的支持。

参考资料



关于作者

李群,关注于网络安全产品的开发、研究;软件开发过程等方面。您可以通过 liqun@nsfocus.com和他联系。

如何开始使用boost的跨平台thread库(Windows)

摘自http://unknown-error.spaces.live.com/blog/cns!9B12A9BDE11A3428!142.entry
boost主页:http://www.boost.org/
在主页点击download进入sourceforge页面下载,当前最新版本为boost_1_33_1,有多种文件格式可供下载(包括.exe, .tar.gz等),内容相同,都是boost_1_33_1的全部源代码。下载后解压(假设解压目录为D:\boost\boost_1_33_1)。
 
boost中的大部分内容都可以直接源代码使用,而thread则需要首先编译出对应的库。
 
Windows XP平台:
      我的机器上安装了vc2003和vc2005,分别在D:\devenv\vs2003和D:\devenv\vs2005目录下
1. 编译jam(JAM是编译其他库的基础)
启动命令行进入D:\boost\boost_1_33_1\tools\build\jam_src目录
运行build即可
运行结束后,将新出现bin.ntx86目录,我们所需要的bjam.exe就在该目录下。
 
查看一下build文件的内容,发现其自动检测vc2003(vc7.1)的安装目录,然后调用vc7.1编译出的bjam.exe。(使用了VS71COMNTOOLS宏)
 
2. 编译thread库
启动命令行进入D:\boost\boost_1_33_1目录
set VC71_ROOT=D:\devenv\vs2003\Vc7  (此时没有自动检测,所以需要人工指定)
tools\build\jam_src\bin.ntx86\bjam.exe --with-thread stage (只编译thread库)
 
编译完成后,结果在D:\boost\boost_1_33_1\bin\boost\libs\thread\build目录下(包括debug/relase, dll/lib等);另外,由于我们在编译时使用了stage选项,所以所有的结果都将被拷贝到D:\boost\boost_1_33_1\stage\lib目录下。
 
总结一下,
动态库
libboost_thread-vc71-mt-gd-1_33_1.lib  +  boost_thread-vc71-mt-gd-1_33_1.dll (debug)      42.4k + 88.0k
libboost_thread-vc71-mt-1_33_1.lib     +  boost_thread-vc71-mt-1_33_1.dll    (release)    42.0k + 44.0k
静态库
libboost_thread-vc71-mt-gd-1_33_1.lib  (debug)    2.61M
libboost_thread-vc71-mt-1_33_1.lib     (release)  782k
静态库(runtime-link-static)
libboost_thread-vc71-mt-sgd-1_33_1.lib (debug)    2.19M
libboost_thread-vc71-mt-s-1_33_1.lib   (release)  692k

s Static link to runtime.
g Debug runtime.
d Debug enabled code.
 
 
3. 准备使用thread库
    选用编译得到的thread动态库。
    将boost_thread-vc71-mt-gd-1_33_1.lib重命名为libboost_thread-vc71-mt-gd-1_33_1.lib(前面加了lib)
    将boost_thread-vc71-mt-1_33_1.lib重命名为libboost_thread-vc71-mt-1_33_1.lib(前面加了lib)
    将这两个文件拷贝到D:\devenv\vs2003\Vc7\PlatformSDK\Lib
 
    将boost_thread-vc71-mt-gd-1_33_1.dll拷贝到windows的system32目录下
    将boost_thread-vc71-mt-1_33_1.dll拷贝到windows的system32目录下
 
以上将thread的debug/release版本的共享库拷贝到系统目录下,接下去就可以使用了。
 
4. 使用thread库
    在vc2003中创建一个空的Win32 Console Project,代码:
#include <boost/thread/thread.hpp>
#include <iostream>
void hello()
{
    std::cout << "Hello world, I'm a thread!" << std::endl;
}
main()
{
    boost::thread thrd(&hello);
    thrd.join();
}
 
    设置Project属性的Code Geneartion------Runtime Library 为 /MDd 或者 /MD
    设置Project属性的General-------Additional Include Directories为 D:\boost\boost_1_33_1
 
    编译运行即可。

C++开源跨平台类库及在VC++.net中应用的配置

在如下的库支持下,开发的系统可以很方便移植到当前大部分平台上运行而无需改动,只需在对应的平台下 用你喜欢的编译器重新编译即可。

经典的C++库
  STLport-------SGI STL库的跨平台可移植版本,在以前有些编译器离符合标准比较远的情况下 那时还是有用的,当然目前vc71已经比较接近标准了,故目前不怎么用它了。

  Boost---------准标准库, 功能强大 涉及能想的到的大部分非特别领域的算法,有一个大的C++社区支持。

  WxWindows-----功能强大的跨平台GUI库 ,它的功能和结构都类似 MFC,故原则上可以通过WxWindows把现有MFC程序移植到非Win平台下。

  Blitz---------高效率的数值计算函数库 ,你可以订制补充你需要的算法。

  Log4cpp-------日志处理 ,功能类似java中的log4j。

  ACE-----------自适应通讯环境, 重量级的通讯环境库。

  Crypto++ -----加/解密算法库, 非常专业的C++ 密码学函式库。

  CppUnit --- 一个c++的单元测试框架 类似 java 的JUnit。

  Loki ------- 一个实验性质的库,尝试把类似设计模式这样思想层面的东西通过库来提供,他是C++的一个模板库,系C++"贵族", 它把C++模板的功能发挥到了极致。

学术性的C++库:

  FC++ --------The Functional C++ Library ,用库来扩充语言的一个代表作 ,模板库。

  CGAL ------- Computational Geometry Algorithms Library计算几何方面的大部分重要的解决方案和方法以C++库的形式提供给工业和学术界的用户。

其它目前我感觉还不是很爽的C++库:

  Doxygen ----注释文档生成工具 ,可恨的是我找不到 windows版本。

  QT ----------大名顶顶的一个多平台的C++图形用户界面应用程序框架(GUI库)可气的是他的 Windows版 是商业发布的要付费。

  xml4c--------IBM开发的XML Parser,系超重量级的, 适用大型应用中, 其DLL有 12M,恐怖吧。

  Xerces c++ --Apache的XML项目, 但 只支持少数的字符编码,如ASCII,UTF-8,UTF-16等,不能处理包含中文字符的XML文档。

  XMLBooster ----- 也是一种 XML的 解析工具。

  Fox -------又一种开放源代码(C++)的GUI库,功能不是很强。

C++开发环境(Win平台下除了 Visual C++ 和 Borland C++以外的):

  Cygwin --------Windows下的一个Unix仿真环境。

  MinGW --------GCC的一个Windows移植版本。

  Dev C++ -------- 一个C/C++ 的集成开发环境,在Windows上的C++编译器一直和标准有着一段距离的时候,GCC就是一个让Windows下开发者流口水的编译器。

  Eclipse-CDT ----IMB 开发的一个集成开发环境,一般用来作为Java 开发环境,但由于Eclipse 是通过插件体系来扩展功能,这里我们 安装 CDT插件后,就可以用来作为C++ 的集成开发环境。



经典的C++库在VC++.net中应用的配置

以下以 vc71环境 为例,其他环境 见各软件包的说明文档。

1. STLport (SGI STL库的跨平台可移植版本。)
-------http://www.stlport.org

vc71环境中编译安装
版本:STLport-4.6.2.tar.gz
copy vc71.mak makefile
nmake clean all

头文件在 %STLport_root%/include\stlport
库文件在 %STLport_root%/lib

头文件添加方法如:
#include 需要链接lib库

2 WxWindows (跨平台的GUI库)
--------http://www.wxwindows.org
--------http://sourceforge.net/projects/wxwindows
--------http://i18n.linux.net.cn/others/wxWindowstut/wxTutorial.html

  因为其类层次极像MFC,所以有文章介绍从MFC到WxWindows的代码移植以实现跨平台的功能。通过多年的开发也是一个日趋完善的GUI库,支持同样不弱于前面两个库。并且是完全开放源代码的。新近的C++ Builder X的GUI设计器就是基于这个库的。

vc71环境中编译安装
版本:wxMSW-2.6.0-Setup.exe
copy makefile.vc makefile
通过 配置 config.vc 的 SHARED = 0 和 BUILD = debug
确定 nmake clean all 的四种编译结果:

include头文件: include\wx
Lib库文件: lib\vc_dll 和 lib\vc_lib
DLL: lib\vc_dll

头文件在 %wxWidgets_root%/include\wx
库文件在 %wxWidgets_root%/lib\vc_dll 和 %wxWidgets_root%/lib\vc_lib

头文件添加方法如:
#include 需要链接lib库

3 boost ("准"标准库)
------http://www.boost.org/
------http://sourceforge.net/projects/boost/

  Boost库是一个经过千锤百炼、可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的发动机之一。 Boost库由C++标准委员会库工作组成员发起,在C++社区中影响甚大,其成员已近2000人。 Boost库为我们带来了最新、最酷、最实用的技术,是不折不扣的"准"标准库。

vc71环境中编译安装
版本:boost_1_32_0.exe

首先进入 tools\build\jam_src 运行 build.bat 得到一个工具: bjam.exe
将其复制到 boost_root 目录下
执行 bjam "-sTOOLS=vc-7_1" stage 开始编译 (bjam "-sTOOLS=vc-7_1" install)

头文件在 %boost_root%/boost
库文件在 %boost_root%/stage\lib

头文件添加方法如:
#include 有时要链接lib库

  Boost中比较有名气的有这么几个库:
  Regex正则表达式库
  Spirit
  LL parser framework,用C++代码直接表达EBNF
  Graph图组件和算法
  Lambda在调用的地方定义短小匿名的函数对象,很实用的functional功能
  concept check检查泛型编程中的concept
  Mpl用模板实现的元编程框架
  Thread可移植的C++多线程库
  Python把C++类和函数映射到Python之中
  Pool内存池管理
  smart_ptr5个智能指针,学习智能指针必读,一份不错的参考是来自CUJ的文章:Smart Pointers in Boost,哦,这篇文章可以查到,CUJ是提供在线浏览的。

  Boost总体来说是实用价值很高,质量很高的库。并且由于其对跨平台的强调,对标准C++的强调,是编写平台无关,现代C++的开发者必备的工具。但是Boost中也有很多是实验性质的东西,在实际的开发中实用需要谨慎。并且很多Boost中的库功能堪称对语言功能的扩展,其构造用尽精巧的手法,不要贸然的花费时间研读。Boost另外一面,比如Graph这样的库则是具有工业强度,结构良好,非常值得研读的精品代码,并且也可以放心的在产品代码中多多利用。

4 blitz (高效率的数值计算函数库)
------http://folk.uio.no/patricg/blitz/html/index.html
------ http://www.oonumerics.org/blitz/
------http://sourceforge.net/projects/blitz/

  Blitz++ 是一个高效率的数值计算函数库,它的设计目的是希望建立一套既具像C++ 一样方便,同时又比Fortran速度更快的数值计算环境。通常,用C++所写出的数值程序,比 Fortran慢20%左右,因此Blitz++正是要改掉这个缺点。方法是利用C++的template技术,程序执行甚至可以比Fortran更快。

  Blitz++目前仍在发展中,对于常见的SVD,FFTs,QMRES等常见的线性代数方法并不提供,不过使用者可以很容易地利用Blitz++所提供的函数来构建。

vc71环境中编译安装
版本:blitz-0.8.tar.gz

将 blitz-0.8/Blitz-VS.NET.zip 解压到当前目录下
打开 Blitz-Library.sln 编译即可

头文件在 %blitz_root%/blitz
%blitz_root%/random
库文件在 %blitz_root%/lib (静态库)

头文件添加方法如:
#include 有时要链接lib库
#include 不需要lib库

5 log4cpp (日志处理)
-------http://sourceforge.net/projects/log4cpp/
------- http://log4cpp.hora-obscura.de/index.php/Main_Page

  Log4cpp 是 Log4J 的 C++ 移植版本,开放源代码并且完全免费。与 Log4J 能够跨平台一样,Log4cpp也致力于写出跨平台的 C++ 程序。Log4cpp 主要是用于 C++ 程序中写 log 文件,与此同时,Log4cpp 中有很多有用的类库,对于写跨平台 C++ 程序的人来说,可以直接拿来用,或者作为自己写跨平台类的参考。

  Log4cpp 中的跨平台类库有明显的 Java 痕迹,比如 Class、Object 、Loader、Locale 等类。 Log4cpp
中的类都可以根据类名 new 出一个 instance,其实现的方式和 MFC 如出一辙:通过 C++ 强大的宏来实现。

Log4cpp 中的跨平台类库主要有:

信号类:Condition(broadcast,signal,wait),CriticalSection (lock,unlock),WaitAccess,
Event(set,reset,wait),Mutex(lock,unlock), Semaphore(wait,tryWait,post)

网络类:InetAddress,Socket,ServerSocket,DatagramSocket,SocketInputStream,
SocketOutputStream

日期类:DateFormat,DateTimeDateFormat,System(currentTimeMillis)

文件类:FileWatchdog(doOnChange)

内存操作类:基于引用计数机制的智能指针 ObjectPtrT
字符串操作类:StrictMath,StringHelper(toUpperCase,toLowerCase,trim,equalsIgnoreCase,endsWith,format),StringTokenizer

线程类:Thread(start,run,join)

  使用以上的类不用考虑 thread handle, event handle, socket handle 之类的 handle 问题,所有这些文
件已经被封装了。很好用,对不对?不足之处在于没有 GUI 类。ANSI C++ 中对于目录等文件系统的处理功能较弱,这里面也没有目录处理类。另外 Socket 的 read(void * buf, size_t len) 不能设置 timeout,并且如果读取数据个数小于 len 那么 read 函数将一直堵塞,不太好用,很可惜。实际的使用上面,可以考虑做一个 Socket 子类,重写 read() 函数。

vc71环境中编译安装
版本:log4cpp-0.3.5rc1.tar.gz

打开 msvc6 编译即可

头文件在 %log4cpp_root%/include\log4cpp
库文件在 %log4cpp_root%/lib

头文件添加方法如:
#include 需要链接lib库

6 Crypto++ 加/解密算法库
---http://sourceforge.net/projects/cryptopp/
---http://www.eskimo.com/~weidai/cryptlib.html
---http://www.cryptopp.com

  提供处理密码,消息验证,单向hash,公匙加密系统等功能的免费库。

  Crypto++ 是一个非常专业的C++ 密码学函式库,几乎在密码学里头常见的演算法都可以在Crypto++
找到实作的函式,如:block 与stream ciphers,hash functions,MACs,random number generators,public key 加密...等方法

vc71环境中编译安装
版本:cryptopp521.zip

直接通过 cryptest.dsw 相关的库

头文件在 %cryptopp_root%
库文件在 %cryptopp_root%/lib

头文件添加方法如:
#include <*.h> 需要链接lib库

7 ACE

------http://www.cs.wustl.edu/~schmidt/ACE.html

  C+ +库的代表,超重量级的网络通信开发框架。ACE自适配通信环境(Adaptive Communication Environment)是可以自由使用、开放源代码的面向对象框架,在其中实现了许多用于并发通信软件的核心模式。ACE提供了一组丰富的可复用C++ 包装外观(Wrapper Facade)和框架组件,可跨越多种平台完成通用的通信软件任务,其中包括:

  事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通信、共享内存管理、消息路由、分布式服务动态(重)配置、并发执行和同步,等等。

8. CppUnit
-------http://sourceforge.net/projects/cppuint/

  一个c++的单元测试框架,可以通过派生测试类的方式,定制具体的测试方案。xUnit家族的一员,从JUnit移植而来,JUnit是Java语言的单元测试框架。

vc71环境中编译安装
版本:cppunit-1.10.2.tar.gz

直接通过 CppUnitLibraries.dsw 编译相关的库

头文件在 %cppunit_root%/cppunit
库文件在 %cppunit_root%/lib

头文件添加方法如:
#include 需要链接lib库

9 Loki
-----http://moderncppdesign.com
-----http://sourceforge.net/projects/loki-lib/
-----http://sourceforge.net/projects/loki-exp/

  其实可和Boost一起介绍它,一个实验性质的库。作者在loki中把C++模板的功能发挥到了极致。并且尝试把类似设计模式这样思想层面的东西通过库来提供。同时还提供了智能指针这样比较实用的功能。

  该库系模板库,库本身无需编译,在你的工程文件中 引用头文件就可以使用,如果 你直接或间接使用了small object,那你需要在你的工程文件加上SmallObj.cpp;如果 你直接或间接使用了Singletons,那你需要在你的工程文件 加上 Singleton.cpp

学术性的C++库的详细介绍:

1 FC++: The Functional C++ Library
--------http://www.cc.gatech.edu/~yannis/fc++/

  这个库提供了一些函数式语言中才有的要素。属于用库来扩充语言的一个代表作。如果想要在OOP之外寻找另一分的乐趣,可以去看看函数式程序设计的世界。大师Peter Norvig在 "Teach Yourself Programming in
Ten Years"一文中就将函数式语言列为至少应当学习的6类编程语言之一。

当前版本:FC++.1.5.zip
模板库,在实际工程中 ,加上要用的头文件 就可以编译。

2 CGAL
-----http://www.cgal.org

  Computational Geometry Algorithms Library的目的是把在计算几何方面的大部分重要的解决方案和方
法以C++库的形式提供给工业和学术界的用户。

当前版本:CGAL-3.1.zip
这是一个已编译的版本,当然也包括完整的源码

头文件在 %CGAL_root%/include/CGAL
库文件在 %CGAL_root%/lib/msvc7

头文件添加方法如:
#include 需要链接lib库

其它目前我感觉还不是很爽的C++库的详细介绍:

1 Doxygen
------http://sourceforge.net/projects/doxygen/
------ http://www.stack.nl/~dimitri/doxygen/

  注释文档生成工具,较之Doc++功能更为齐全,可以生成包括HTML、PDF、RTF在内的多种格式的文档,并有GUI界面,除了支持c/c++语言外,还支持IDL、java、PHP、c#等。

2、 QT(windows版要付钱)
-------http://www.trolltech.com/
-------http://www.qiliang.net/qt.html

  Qt是Trolltech公司的一个多平台的C++图形用户界面应用程序框架。它提供给应用程序开发者建立艺术级的图形用户界面所需的所用功能。Qt是完全面向对象的很容易扩展,并且允许真正地组件编程。自从1996年早些时候,Qt进入商业领域,它已经成为全世界范围内数千种成功的应用程序的基础。Qt也是流行的Linux桌面环境KDE 的基础,同时它还支持Windows、Macintosh、Unix/X11等多种平台。

3、Fox
---------http://www.fox-toolkit.org/

  开放源代码的GUI库。作者从自己亲身的开发经验中得出了一个理想的GUI库应该是什么样子的感受
出发,从而开始了对这个库的开发。有兴趣的可以尝试一下。

4 xml4c
------http://www.alphaworks.ibm.com/tech/xml4c

  IBM的XML Parser,用c++语言写就,功能超级强大。号称支持多达100种字符编码,能够支持中文,适合于大规模的xml应用。若只是很小范围的应用,则非最佳选择,毕竟,你需要"背负"约12M左右的dll的沉重负担。

5 Xerces c++
-------http://xml.apache.org/xerces-c

  Apache的XML项目,同样是c++ 实现,来源于IBM的xml4c,因此编程接口也是和xml4c一致的。但是目前只支持少数的字符编码,如ASCII,UTF-8,UTF-16等,不能处理包含中文字符的XML文档。

  Xerces-C++ 是一个非常健壮的XML解析器,它提供了验证,以及SAX和DOM API。XML验证在文档类型定义(Document Type Definition,DTD)方面有很好的支持,并且在2001年12月增加了支持W3C XML Schema的基本完整的开放标准。

6 XMLBooster
-------http://www.xmlbooster.com/

  这个库通过产生特制的parser的办法极大的提高了XML解析的速度,并且能够产生相应的GUI程序来修改这个parser。在DOM和SAX两大主流XML解析办法之外提供了另外一个可行的解决方案。

C++开发环境(Win平台下除了 Visual C++ 和 Borland C++以外的):

1. Cygwin (Windows下的一个Unix仿真环境)

  这个Cygwin的一部分是GCC的另外一个Windows移植版本,Cygwin是Windows下的一个Unix仿真环境。严格的说是模拟GNU的环境,这也就是"Gnu's Not Unix"要表达的意思。

  至Cygwin的網站http://www.cygwin.com/下載安裝程式setup.exe,可直接點選執行或先行下載至個人電腦後再執行。目前我已经下载到本地了,直接安装即可。

2. MinGW (GCC的一个Windows移植版本)

1)http://sourceforge.net/projects/mingw 直接访问的,点击 Files,然后下载以下文件:MinGW-3.1.0-1.exe, mingw32-make-3.80.0-3.exe。
安装MinGW 到 C:/MinGW 目录下,然后安装 mingw32-make 到 C:/MinGW 下,通过浏览器
到 C:/MinGW/bin 下,将 mingw32-make.exe 改名或者另外复制为 make.exe。

(以上的设置已经足够。不过为了求新,我是同时下载了 gcc-core-3.4.2-20040916-1.tar.gz,mingw-runtime-3.5.tar.gz 和 w32api-3.1.tar.gz,将它们直接解压到 C:/MinGW 下更新旧的文件。不过这对这篇文章本身没有任何影响。新旧两种配置我都测试过。)

安装次序:
MinGW-3.1.0-1.exe
mingw32-make-3.80.0-3.exe
gcc-core-3.4.2-20040916-1.tar.gz
mingw-runtime-3.5.tar.gz
w32api-3.1.tar.gz
gdb-5.2.1-1.exe
mingw-utils-0.3.tar.gz
binutils-2.15.91-20040904-1.tar.gz

3)准备MinGW 用户开发的命令行环境(一个批处理)
如: mingw.bat
@rem --------------------------------------
@SET MINGW_ROOT=D:\Mingw

@rem
@echo Setting environment for using Mingw.
@rem

@set PATH=%MINGW_ROOT%\BIN;%PATH%
@set INCLUDE=%MINGW_ROOT%\INCLUDE;%MINGW_ROOT%\INCLUDE\c++\3.2.3;%MINGW_ROOT%\include\c++\3.2.3\mingw32;%MINGW_ROOT%\include\c++\3.2.3\backward;%INCLUDE%
@set LIB=MINGW_ROOT\LIB;%LIB%
@rem ----------------------------------------

3. Dev C++ (一个C/C++ 的集成开发环境)

  GCC是一个很好的编译器。在Windows上的C++编译器一直和标准有着一段距离的时候,GCC就是一个让Windows下开发者流口水的编译器。Dev-C++就是能够让GCC跑在Windows下的工具,作为集成开发环境,还提供了同专业IDE相媲美的语法高亮,代码提示,调试等功能。由于使用Delphi开发,占用内存少,速度很快,比较适合轻量级的学习和使用。

  可以使用 MinGW-GCC 作为它的编译器

4 Eclipse-CDT

游戏开发

Audio/Video 3D C++ Programming Library

------http://www.galacticasoftware.com/products/av/
------http://sourceforge.net/projects/av3d/

  AV3D是一个跨平台,高性能的C++库。主要的特性是提供3D图形,声效支持(SB,以及S3M),控制接口(键盘,鼠标和遥感),XMS。

KlayGE

------http://home.g365.net/enginedev/
------http://sourceforge.net/projects/klayge/

  国内游戏开发高手自己用C++开发的一个开放源代码、跨平台的游戏引擎。KlayGE是一个开放源代码、跨平台的游戏引擎,并使用Python作脚本语言。KlayGE在LGPL协议下发行。感谢龚敏敏先生为中国游戏开发事业所做出的贡献。

OGRE

------http://www.ogre3d.org
------http://www.ogre3d.org/docs/manual/
------ http://sourceforge.net/projects/ogre

  OGRE(面向对象的图形渲染引擎)是用C++开发的,使用灵活的面向对象3D引擎。它的目的是让开发者能更方便和直接地开发基于3D硬件设备的应用程序或游戏。引擎中的类库对更底层的系统库(如:Direct3D和OpenGL)的全部使用细节进行了抽象,并提供了基于现实世界对象的接口和其它类。

11 juin

Detect & post-build for Intel C++ Compiler

Posted in C/C++ , Programming by jeffhung @ March 14th, 2007

Intel C++ Compiler (ICC) 所編譯出來的執行檔,其速度總令人驚豔,且可以近乎無痛地與 Microsoft Visual C++ (MSVC) 的開發經驗整合,是升級開發工具的好選擇。然而,在 Windows 上,ICC 需要搭配隨 ICC 附上的 libmmd.dll ,才能夠執行,再加上 ICC 和 MSVC 是可以隨時切換的,因此,在 build system 的設計上,必須要有些特別的設計,以便處理這種 IDE 不會處理到的事。

我們用的 MSVC 是 VC6,需要偵測目前使用的編譯器,是 MSVC 還是 ICC,如果是 ICC 的話,還要偵測是哪一個版本,然後,依據偵測到的版本,作對應的 post-build 的動作。例如,若是使用 ICC 的話,就將正確版本的 libmmd.dll,複製到目標目錄下。

以下假設 ICC 是安裝在 ICPP_COMPILER 這個目錄下,如 %ProgramFiles%\Intel\Compiler\C++\9.1

裝了 ICC 後,compiler/linker 會被換成在 %ICPP_COMPILER%\..\..\ISELECT\bin\ 目錄下的 xicl6.exexilink6.exe,這只是一個只是一個 front-end,ICC 會依據目前的設定,啟動對應的 compiler/linker 進行編譯或連結。

依據隨附 ICC 安裝的文件裡的「Intel® C++ Compiler Documentation » Building Applications » Building Applications with Microsoft Visual Studio 6.0 » Selecting the Intel® C++ Compiler Using makefile」這一篇文章:

... The Makefile Utility provides users with the ability to switch between the Intel C++ Compiler and the Microsoft Visual C++ Compiler without requiring changes to their makefiles. This utility modifies the registry, so exported makefiles only call cl.exe (CPP = cl).

這個所謂的 Makefile Utility 指的就是 %ICPP_COMPILER%\..\..\ISELECT\bin\pickcmd.exe,會修改 registry,依據給定的參數,設定要使用的編譯器。也就是說,這個 utility 就是 MSVC IDE 裡的 Tools » Intel® C++ Compiler Selection Tool 的 command line 版本。

故 ICC 實際上是依據 registry 來決定要使用哪個 compiler/linker 的。所以觀察 registry 可以發現 HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6 下面有這幾組 key:

HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6
Path64 REG_SZ
MSVC_binary_dir REG_SZ C:\Program Files\Microsoft Visual Studio\VC98\Bin
Compiler REG_SZ 91032
Use_Intel_Cxx REG_DWORD 0x1
Languages REG_DWORD 0x1
Compiler_List REG_SZ 91032

HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6\90032
bin REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\IA32\bin
CHelpFile REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\Docs\Main_cls.chm
Compiler_Interface REG_DWORD 0x0
Include REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\IA32\include
Languages REG_DWORD 0x1
Lib REG_SZ C:\Program Files\Intel\Compiler\C++\9.0\IA32\lib
Name REG_SZ 9.0

HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6\91032
bin REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\IA32\bin
CHelpFile REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Docs\Main_cls.chm
Compiler_Interface REG_DWORD 0x0
Include REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\IA32\include
Languages REG_DWORD 0x1
Lib REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\IA32\lib
Name REG_SZ 9.1

HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6\91064
bin REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Itanium\bin
Name REG_SZ 9.1
Lib REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Itanium\lib
Languages REG_DWORD 0x1
Include REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Itanium\include
Compiler_Interface REG_DWORD 0x0
CHelpFile REG_SZ C:\Program Files\Intel\Compiler\C++\9.1\Docs\Main_cls.chm

從這幾組 registry 的結構以及實驗的結果,我們可以看出,ICC 係依據 Use_Intel_Cxx 這一個 key,來決定要不要用 ICC,以及依據 Compiler 這一個 key,決定要用哪一個版本的 ICC。故,如果我們能夠在 command line 下查詢這些 registry key,我們就可以寫出 BATCH 檔,依據設定作對應的 post-build 動作。

我們可以使用 reg.exe 這一支程式[ 1],在 command line 查詢 registry key,如下:

SHELL> REG QUERY "HKCU\Software\Intel\Intel Tools\Select Compiler\IDE\6" \
/v Use_Intel_Cxx

! REG.EXE VERSION 3.0

HKEY_CURRENT_USER\Software\Intel\Intel Tools\Select Compiler\IDE\6
Use_Intel_Cxx REG_DWORD 0x1

好,既然 registry 裡的資訊抓得出來了,那剩下的就只剩下 BATCH 檔的程式設計技巧。我們會需要下列技術:

如 UNIX shell 的 backquote 一般,取得程式執行的結果,並存入變數

BATCH 並沒有如 UNIX shell 的 backquote 的功能,也就是另外執行程式,將程式的輸出,轉變成目前的 script 部份內容,進而存入變數。為了取得 registry 裡的值,放入變數以便作後續的處理,我們必須在 BATCH 裡,模擬出 backquote 的功能。

要達到 backquote 的效果,解法很簡單,但非常 tricky。假設我們要達到 set foo `bar` 的效果,我們可以這麼寫:

bar > bar.tmp
SET /p foo=<bar.tmp

SET 使用 /p 選項時,會改向使用者詢問,將得到的字串,設給 foo,而 x 後面的字串,則當作 prompt string 使用。因此,我們先執行 bar,將結果存在暫存檔 bar.tmp 裡,然後用 redirect 將 bar.tmp 的內容,導給 SET /p foo=,如此就可以將 bar 的執行結果,存在 foo 變數裡。

如 UNIX 的 grep 工具一般,在一堆內容裡,找到符合某 pattern 的那些行

因為使用 reg.exe 查詢 registry key 的時候,會額外印出許多不相干的內容,所以我們第一步就是要把顯示值的那一行抓出來。

Windows 的 cmd.exe 有個指令叫 FIND,可以做到如 grep 的效果,只是沒有 grep 那麼強大。假設我們要抓 Use_Intel_Cxx 的值,我們可以這麼下指令:

SHELL> SET ISELECT6=HKCU\Software\Intel\Intel Tools\Select Compiler\IDE\6

SHELL> REG QUERY "%ISELECT6%" /v Use_Intel_Cxx 2>>NUL | FIND "REG_DWORD"
Use_Intel_Cxx REG_DWORD 0x1

其中,裡面的 2>>NUL 負責把 stderr 的東西丟掉。

如 UNIX 的 cut 工具一般,依據位置,取出某行文字的部份內容

這個要用到 cmd.exe 變數展開的延伸功能[ 2]。在 cmd.exe 裡打 HELP SET 我們可以得到下面的說明[ 3]

您也可以為擴充功能指定子字串。

%PATH:~10,5%

這將會擴充 PATH 環境變數,然後只使用擴充結果的第 11 個(位移 10)字元
後的 5 個字元如果長度未指定,將會預設為上次使用的變數值。如果數字(位
移或長度)是負數,使用的數字將會是環境變數的長度加上位移或指定長度。

假設含有 Use_Intel_Cxx 的值的那一整行,已經在 ICC_USE_INTEL_CXX 變數裡了,我們就可以用下面的指令,擷取出 0x1 的部份:

SET ICC_USE_INTEL_CXX=%ICC_USE_INTEL_CXX:~28%

很可惜的,Windows 下預設沒有如 sedcut awk 等強大的文字處理工具,而 cmd.exe 的變數延展功能又太過陽春,不能依靠如 regular pattern 的方式處理,因此這裡的數字 28 必須自行計算並寫死。還好,以目前的應用來說,使用固定的數字不會有任何的問題。

有了以上的技術,我們就可以全部組裝起來,達到我們想要的功能。最後完整的程式如下:

@ECHO OFF

SET PREFIX=..\FooProj

IF "%1"=="" GOTO USAGE
IF "%1"=="/?" GOTO USAGE
IF "%1"=="/h" GOTO USAGE
IF "%1"=="/help" GOTO USAGE
IF "%1"=="-h" GOTO USAGE
IF "%1"=="--help" GOTO USAGE

SET CONFIGURATION=%1
IF "%CONFIGURATION%"=="debug" GOTO CONFIGURATION_CHECK_OK
IF "%CONFIGURATION%"=="release" GOTO CONFIGURATION_CHECK_OK
:CONFIGURATION_CHECK_FAILED
ECHO ERROR: Bad [configuration].
GOTO USAGE
:CONFIGURATION_CHECK_OK

SET ISELECT6=HKCU\Software\Intel\Intel Tools\Select Compiler\IDE\6
SET TMP_FILE=tmp-backquote.txt

REM Determine whether ICC is used
REG QUERY "%ISELECT6%" /v Use_Intel_Cxx 2>>NUL | FIND "REG_DWORD" > %TMP_FILE%
SET /P ICC_USE_INTEL_CXX=<%TMP_FILE%
SET ICC_USE_INTEL_CXX=%ICC_USE_INTEL_CXX:~28%
IF %ICC_USE_INTEL_CXX%==0x1 GOTO POST_BUILD_ICC_BEGIN
IF %ICC_USE_INTEL_CXX%==0x0 GOTO POST_BUILD_MSVC_BEGIN

:POST_BUILD_ICC_BEGIN
ECHO Perform ICC post-build steps:

REM Get ICC version code, which is part of next reg key to query
REG QUERY "%ISELECT6%" /v Compiler 2>>NUL | FIND "REG_SZ" > %TMP_FILE%
SET /P ICC_VER_CODE=<%TMP_FILE%
SET ICC_VER_CODE=%ICC_VER_CODE:~20%

REG QUERY "%ISELECT6%\%ICC_VER_CODE%" /v bin 2>>NUL | FIND "REG_SZ" > %TMP_FILE%
SET /P ICC_BIN_PATH=<%TMP_FILE%
SET ICC_BIN_PATH=%ICC_BIN_PATH:~15%

IF "%CONFIGURATION%"=="debug" XCOPY "%ICC_BIN_PATH%\libmmdd.dll" %PREFIX%\bin /Y /F /D
IF "%CONFIGURATION%"=="release" XCOPY "%ICC_BIN_PATH%\libmmd.dll" %PREFIX%\bin /Y /F /D

:POST_BUILD_ICC_END
GOTO POST_BUILD_COMMON_BEGIN

:POST_BUILD_MSVC_BEGIN
ECHO Perform MSVC post-build steps:

:POST_BUILD_MSVC_END
GOTO POST_BUILD_COMMON_BEGIN

:POST_BUILD_COMMON_BEGIN
ECHO Perform common post-build steps:
XCOPY lib\libFoo\include\foo_core.h %PREFIX%\include /Y /F /D
:POST_BUILD_COMMON_END
GOTO QUIT

:USAGE
ECHO Usage: %0 [configuration]
ECHO ----
ECHO [configuration] could be Debug or Release.
GOTO QUIT

:QUIT
IF EXIST %TMP_FILE% DEL /Q %TMP_FILE%
EXIT /B

由於在 Release mode 我們需要的是 libmmd.dll,而在 Debug mode 裡需要的是 libmmdd.dll,所以這個 script 吃一個參數,可以是 debugrelease (大小寫不拘),以決定要 XCOPY 哪一個 DLL。

參考資料:


  1. 內含於 Windows Support Tools 裡。
  2. Windows 的 cmd.exe 有兩種模式:基本模式與擴充模式。擴充模式的內建指令功能較強,可以在 cmd 啟動時加上 /E 選項開啟,或是直接在 registry 裡設定是否開啟的預設值。這裡用的「延伸功能」的字眼,指的就是擴充模式裡才有的變數展開功能。
  3. 說明裡的「擴充」兩字,應是翻譯自英文的「expand」,我覺得這個翻譯不太好,故本文裡都是用「變數展開」的方法描述,和「巨集展開」的用法一樣。Microsoft 的中文翻譯,很多地方都不太傳神。

8 juin

VC++ 6.0 中如何使用 CRT 调试功能来检测内存泄漏

作者:JerryZ下载例子源代码

  最近看了周星星 Blog 中的一篇文章:" VC++6.0中内存泄漏检测",受益匪浅,便运行其例子代码想看看 Output 窗口中的输出结果,可惜怎么弄其输出都不是预期的东西,郁闷了半天,便到水坛里找到周星星,请求他指点一、二,然而未果。没有办法,最后我一头栽进 MSDN 库狂搜了一把,功夫不负有心人,我搜出很多有关这方面的资料,没过多久我便基本上就找到了答案......
  首先,检测内存泄漏的基本工具是调试器和 CRT 调试堆函数。为了使用调试堆函数,必须在要检测内存泄漏和调试的程序中添加下面的语句:

#define _CRTDBG_MAP_ALLOC 
#include<stdlib.h> 
#include<crtdbg.h> 

#include "debug_new.h" 

  MSDN 如是说:"必须保证上面声明的顺序,如果改变了顺序,可能不能正常工作。"至于这是为什么,我们不得而知。MS 的老大们经常这样故弄玄虚。
  针对非 MFC 程序,再加上周星星的头文件:debug_new.h,当然如果不加这一句,也能检测出内存泄漏,但是你无法确定在哪个源程序文件中发生泄漏。Output 输出只告诉你在 crtsdb.h 中的某个地方有内存泄漏。我测试时 REG_DEBUG_NEW 没有起作用。加不加这个宏都可以检测出发生内存分配泄漏的文件。
  其次,一旦添加了上面的声明,你就可以通过在程序中加入下面的代码来报告内存泄漏信息了:

      _CrtDumpMemoryLeaks(); 
  这就这么简单。我在周星星的例子代码中加入这些机关后,在 VC++ 调试会话(按 F5 调试运行) Output 窗口的 Debug 页便看到了预期的内存泄漏 dump。该 dump 形式如下:
Detected memory leaks! 
Dumping objects -> 
c:\Program Files\...\include\crtdbg.h(552) : {45} normal block at 0x00441BA0, 2 bytes long. 
Data: <AB> 41 42 
c:\Program Files\...\include\crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long. 
Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD 
c:\Program Files\...\include\crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long. 
Data: < C > E8 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete. 

更具体的细节请参考本文附带的源代码文件。

  下面是我看过 MSDN 资料后,针对"如何使用 CRT 调试功能来检测内存泄漏?"的问题进行了一番编译和整理,希望对大家有用。如果你的英文很棒,那就不用往下看了,建议直接去读 MSDN 库中的技术原文。
  C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:"最大的长处也可能成为最大的弱点",那么 C/C++ 应用程序正好印证了这句话。在 C/C++ 应用程序开发过程中,动态分配的内存处理不当是最常见的问题。其中,最难捉摸也最难检测的错误之一就是内存泄漏,即未能正确释放以前分配的内存的错误。偶尔发生的少量内存泄漏可能不会引起我们的注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种 各样的征兆:从性能不良(并且逐渐降低)到内存完全耗尽。更糟的是,泄漏的程序可能会用掉太多内存,导致另外一个程序垮掉,而使用户无从查找问题的真正根源。此外,即使无害的内存泄漏也可能殃及池鱼。
  幸运的是,Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法。下面请和我一起分享收获——如何使用 CRT 调试功能来检测内存泄漏?

  1. 如何启用内存泄漏检测机制?
  2. 解释内存块类型
  3. 如何在内存分配序号处设置断点?
  4. 如何比较内存状态?
  5. 结论

如何启用内存泄漏检测机制

  VC++ IDE 的默认状态是没有启用内存泄漏检测机制的,也就是说即使某段代码有内存泄漏,调试会话的 Output 窗口的 Debug 页不会输出有关内存泄漏信息。你必须设定两个最基本的机关来启用内存泄漏检测机制。

一是使用调试堆函数:

#define _CRTDBG_MAP_ALLOC 
#include<stdlib.h> 
#include<crtdbg.h> 

注意:#include 语句的顺序。如果更改此顺序,所使用的函数可能无法正确工作。

  通过包含 crtdbg.h 头文件,可以将 malloc 和 free 函数映射到其"调试"版本 _malloc_dbg 和 _free_dbg,这些函数会跟踪内存分配和释放。此映射只在调试(Debug)版本(也就是要定义 _DEBUG)中有效。发行版本(Release)使用普通的 malloc 和 free 函数。
  #define 语句将 CRT 堆函数的基础版本映射到对应的"调试"版本。该语句不是必须的,但如果没有该语句,那么有关内存泄漏的信息会不全。

二是在需要检测内存泄漏的地方添加下面这条语句来输出内存泄漏信息:

_CrtDumpMemoryLeaks();
  当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在 Output 窗口的 Debug 页中显示内存泄漏信息。比如:
Detected memory leaks!
Dumping objects ->
C:\Temp\memleak\memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long.
Data: <AB> 41 42 
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long.
Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD 
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long.
Data: < C > 08 02 43 00 16 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete.

如果不使用 #define _CRTDBG_MAP_ALLOC 语句,内存泄漏的输出是这样的:

Detected memory leaks!
Dumping objects ->
{45} normal block at 0x00441BA0, 2 bytes long.
Data: <AB> 41 42 
{44} normal block at 0x00441BD0, 33 bytes long.
Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD 
{43} normal block at 0x00441C20, 40 bytes long.
Data: < C > C0 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete.
  根据这段输出信息,你无法知道在哪个源程序文件里发生了内存泄漏。下面我们来研究一下输出信息的格式。第一行和第二行没有什么可说的,从第三行开始:

xx}:花括弧内的数字是内存分配序号,本文例子中是 {45},{44},{43};
block:内存块的类型,常用的有三种:normal(普通)、client(客户端)或 CRT(运行时);本文例子中是:normal block; 
用十六进制格式表示的内存位置,如:at 0x00441BA0 等;
以字节为单位表示的内存块的大小,如:32 bytes long; 
前 16 字节的内容(也是用十六进制格式表示),如:Data: <AB> 41 42 等;

  仔细观察不难发现,如果定义了 _CRTDBG_MAP_ALLOC ,那么在内存分配序号前面还会显示在其中分配泄漏内存的文件名,以及文件名后括号中的数字表示发生泄漏的代码行号,比如:

C:\Temp\memleak\memleak.cpp(15) 
  双击 Output 窗口中此文件名所在的输出行,便可跳到源程序文件分配该内存的代码行(也可以选中该行,然后按 F4,效果一样) ,这样一来我们就很容易定位内存泄漏是在哪里发生的了,因此,_CRTDBG_MAP_ALLOC 的作用显而易见。

使用 _CrtSetDbgFlag

  如果程序只有一个出口,那么调用 _CrtDumpMemoryLeaks 的位置是很容易选择的。但是,如果程序可能会在多个地方退出该怎么办呢?在每一个可能的出口处调用 _CrtDumpMemoryLeaks 肯定是不可取的,那么这时可以在程序开始处包含下面的调用:
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

  这条语句无论程序在什么地方退出都会自动调用 _CrtDumpMemoryLeaks。注意:这里必须同时设置两个位域标志:_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF。

设置 CRT 报告模式

  默认情况下,_CrtDumpMemoryLeaks 将内存泄漏信息 dump 到 Output 窗口的 Debug 页, 如果你想将这个输出定向到别的地方,可以使用 _CrtSetReportMode 进行重置。如果你使用某个库,它可能将输出定向到另一位置。此时,只要使用以下语句将输出位置设回 Output 窗口即可:

_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

有关使用 _CrtSetReportMode 的详细信息,请参考 MSDN 库关于 _CrtSetReportMode 的描述。

解释内存块类型

  前面已经说过,内存泄漏报告中把每一块泄漏的内存分为 normal(普通块)、client(客户端块)和 CRT 块。事实上,需要留心和注意的也就是 normal 和 client,即普通块和客户端块。

  • normal block(普通块):这是由你的程序分配的内存。
  • client block(客户块):这是一种特殊类型的内存块,专门用于 MFC 程序中需要析构函数的对象。MFC new 操作符视具体情况既可以为所创建的对象建立普通块,也可以为之建立客户块。
  • CRT block(CRT 块):是由 C RunTime Library 供自己使用而分配的内存块。由 CRT 库自己来管理这些内存的分配与释放,我们一般不会在内存泄漏报告中发现 CRT 内存泄漏,除非程序发生了严重的错误(例如 CRT 库崩溃)。

除了上述的类型外,还有下面这两种类型的内存块,它们不会出现在内存泄漏报告中:

  • free block(空闲块):已经被释放(free)的内存块。
  • Ignore block(忽略块):这是程序员显式声明过不要在内存泄漏报告中出现的内存块。

如何在内存分配序号处设置断点?

  在内存泄漏报告中,的文件名和行号可告诉分配泄漏的内存的代码位置,但仅仅依赖这些信息来了解完整的泄漏原因是不够的。因为一个程序在运行时,一段分配内存的代码可能会被调用很多次,只要有一次调用后没有释放内存就会导致内存泄漏。为了确定是哪些内存没有被释放,不仅要知道泄漏的内存是在哪里分配的,还要知道泄漏产生的条件。这时内存分配序号就显得特别有用——这个序号就是文件名和行号之后的花括弧里的那个数字。
  例如,在本文例子代码的输出信息中,"45"是内存分配序号,意思是泄漏的内存是你程序中分配的第四十五个内存块:

Detected memory leaks!
Dumping objects ->
C:\Temp\memleak\memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long.
Data: <AB> 41 42 
......
Object dump complete. 

  CRT 库对程序运行期间分配的所有内存块进行计数,包括由 CRT 库自己分配的内存和其它库(如 MFC)分配的内存。因此,分配序号为 N 的对象即为程序中分配的第 N 个对象,但不一定是代码分配的第 N 个对象。(大多数情况下并非如此。)
  这样的话,你便可以利用分配序号在分配内存的位置设置一个断点。方法是在程序起始附近设置一个位置断点。当程序在该点中断时,可以从 QuickWatch(快速监视)对话框或 Watch(监视)窗口设置一个内存分配断点:

  例如,在 Watch 窗口中,在 Name 栏键入下面的表达式:

_crtBreakAlloc

如果要使用 CRT 库的多线程 DLL 版本(/MD 选项),那么必须包含上下文操作符,像这样:

{,,msvcrtd.dll}_crtBreakAlloc

  现在按下回车键,调试器将计算该值并把结果放入 Value 栏。如果没有在内存分配点设置任何断点,该值将为 –1。
  用你想要在其位置中断的内存分配的分配序号替换 Value 栏中的值。例如输入 45。这样就会在分配序号为 45 的地方中断。
  在所感兴趣的内存分配处设置断点后,可以继续调试。这时,运行程序时一定要小心,要保证内存块分配的顺序不会改变。当程序在指定的内存分配处中断时,可以查看 Call Stack(调用堆栈)窗口和其它调试器信息以确定分配内存时的情况。如果必要,可以从该点继续执行程序,以查看对象发生了什么情况,或许可以确定未正确释放对象的原因。
  尽管通常在调试器中设置内存分配断点更方便,但如果愿意,也可在代码中设置这些断点。为了在代码中设置一个内存分配断点,可以增加这样一行(对于第四十五个内存分配):

_crtBreakAlloc = 45;

你还可以使用有相同效果的 _CrtSetBreakAlloc 函数:

_CrtSetBreakAlloc(45);

如何比较内存状态?

  定位内存泄漏的另一个方法就是在关键点获取应用程序内存状态的快照。CRT 库提供了一个结构类型 _CrtMemState。你可以用它来存储内存状态的快照:

_CrtMemState s1, s2, s3;

  若要获取给定点的内存状态快照,可以向 _CrtMemCheckpoint 函数传递一个 _CrtMemState 结构。该函数用当前内存状态的快照填充此结构:

_CrtMemCheckpoint( &s1 );

  通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构,可以在任意地方 dump 该结构的内容:

_CrtMemDumpStatistics( &s1 );

该函数输出如下格式的 dump 内存分配信息:

0 bytes in 0 Free Blocks.
75 bytes in 3 Normal Blocks.
5037 bytes in 41 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 5308 bytes.
Total allocations: 7559 bytes.

  若要确定某段代码中是否发生了内存泄漏,可以通过获取该段代码之前和之后的内存状态快照,然后使用 _CrtMemDifference 比较这两个状态:

_CrtMemCheckpoint( &s1 );// 获取第一个内存状态快照

// 在这里进行内存分配

_CrtMemCheckpoint( &s2 );// 获取第二个内存状态快照

// 比较两个内存快照的差异
if ( _CrtMemDifference( &s3, &s1, &s2) )
     _CrtMemDumpStatistics( &s3 );// dump 差异结果

  顾名思义,_CrtMemDifference 比较两个内存状态(前两个参数),生成这两个状态之间差异的结果(第三个参数)。在程序的开始和结尾放置 _CrtMemCheckpoint 调用,并使用 _CrtMemDifference 比较结果,是检查内存泄漏的另一种方法。如果检测到泄漏,则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来分割程序和定位泄漏。

结论

  尽管 VC ++ 具有一套专门调试 MFC 应用程序的机制,但本文上述讨论的内存分配很简单,没有涉及到 MFC 对象,所以这些内容同样也适用于 MFC 程序。在 MSDN 库中可以找到很多有关 VC++ 调试方面的资料,如果你能善用 MSDN 库,相信用不了多少时间你就有可能成为调试高手。

VC 常用插件和界面库

 



VC 常用插件 
1.Visual Assist(强烈推荐)
http://www.wholetomato.com/
VA从5.0一直到现在的VAX,功能越来越强大,除了以前版本中的自动识别各种关键字,系统函数,成员变量,自动给出输入提示,自动更正大小写错误,自动标示错误等等以外,最新的版本中还在
WorkSpace窗口中加入一个VA View,可以更方便的查找工程中的文件、类和变量。

2.WndTabs(强烈推荐)
http://www.wndtabs.com/
WndTabs主要是在编辑窗口中显示了所有已经打开的文件,在VC中能够更方便的操作这些文件,比如修改文件属性,copy文件路径、文件名等,并且还开放源代码,你要是愿意的话,可以添加自己很兴趣的功能。

3.LineCounter
http://www.wndtabs.com/

用来统计整个工程的代码行数,包括总行数、代码行数、注释行数、空行数等,并且对多个工程一起统计时,不会把相同的文件计算多次.

4.Spelly
http://www.wndtabs.com/ 
一个拼写检查的插件,可以对整个文件或所选部分进行拼写检查,支持C/C++/C#, VB, Fortran 和HTML。

5.SourceStyler C++
http://www.sourcestyler.com/
此插件是针对C++的一个格式化工具,可以针对自己的编码习惯,选择一种编码风格,也可以自己定义,而且定义非常详细,有表达式、指针、模板、类、枚举等十几种,肯定能满足你的需要

6.Numega BoundsChecker(强烈推荐)
是针对Visual C++6.0应用程序的最为全面的错误检测工具。BoundsChecker 能自动指出静态,堆栈内存错误和资源泄漏问题。BoundsChecker 能够校验最新的 Windows APIs,包括 ActiveX, DirectX, OLE/COM, ODBC等等。能够发现与 Windows 平台兼容性。

7.BCGControlBar Library
非常好的一套应用于vc6的界面扩展类库,轻松的作出 vc2003 的界面。并且给了各种界面例子,如vc.net、outlook、更换皮肤等等。

8.Comment Wizard
Visual C++插件,提供了Visual C++源代码注解标准化与自动化功能。在它的帮助下,您可快速创建标头文件信息注解,文件中模块注解, C++处理方式,以及C语言功能与历史校正功能注解,等等。


VC 界面库 收集

GuiToolkit(开源,类似Visual Studio 2003风格)
http://www.beyondata.com/default.htm

GardenUI(免费,界面效果挺好的,XML,代码 界面 分离)
http://www.gardenui.com/

CJLib(开源,免费,UNICODE编码,是xtreme toolkit的前生,但xtreme toolkit收费了)
http://www.codejock.com/

LibUIDK(部分免费,不开源,效果好,适合贴图)
http://www.iuishop.com/download.htm

BCGControlBar(收费,界面感觉和Office类似)
http://www.bcgsoft.com

SKin++(收费,界面很好看)
http://www.uipower.com/

SkinMagic(与SKin++类似)
http://appspeed.com/html/download.html

ActiveSkin(未知)
http://www.softshape.com/software/develop/

SYGUI(收费,类似Office)
http://www.sygui.com/
 

AfxBeginThread 难道有memory leak?

新弄了个小程序,用到AfxBeginThread,可是一F5就发现了memory leak,找出以前写的用到AfxBeginThread的代码,比来比去都没发现异常,难道?忘了说,我刚把STLport更新最新的5.13。
 
用 BoundsChecker 查了半天,发现内存泄漏发生在 stlport 中,郁闷!!!
 
上网查了半天,原来 stlport 对小块内存采用内存池的方式进行内存管理,不会回收收集的小块内存,而借助操作系统的自动回收能力的做法,以减少内存印记、获得较好的再分配能力和性能。

VC6 + ICC8.1+...VC6与VS2005媲美

VC6 + ICC8.1+...VC6与VS2005媲美--摘自http://www.chinadforce.com/redirect.php?fid=60&tid=524855&goto=nextoldset

因为VC6自带的编译器对C++支持不好,好像还不支持C98标准吧,反正是很久以前的标准了,Intel自家的编译器应该是比较优秀的。听说VC的编译器就是MS和Intel一起搞的,现在Intel想独立门户了吧。
所以产生了用ICC替换VC6编译器的念头,而且在VC6.0IDE中还可以选择使用ICC编译器或者VC6编译器。
下面咱们就一起去体验吧。

VC6 + ICC8.1+...VC
VC6 +sp6: vc6.0加sp6补丁:http://download.microsoft.com/do ... c22eed74/Vs6sp6.exe
ICC8.1:Intel C++编译器 下载:ftp://download.intel.com/software/products/compilers/downloads/ 授权: http://blog.vckbase.com/Files/bruceteen/intel_license.zip


具体设置参照下列大侠的Blog网址:
http://blog.vckbase.com/bruceteen/archive/2004/10/15/987.html
不过话说回来,我自己机器上怎么就有问题呢?
换回VC6编译时,链接出错,用ICC8时,编译出错。
所以我先用VC6编译,用ICC8链接。
很烦的说。

还望成功人士来分享一下经验。
另外推荐两个VC6的插件:
visual assist x v10.* 必备
http://www.wholetomato.com
WndTabs:多页面浏览选单(同Maxthon多页面浏览一样)
http://www.wndtabs.com
或者Visual Studio Booster 同上
  BoundsChecker 内存泄漏检测插件

搞定这些后,不必装庞大的VS2005就可以和其媲美了,而且还可以自由切换编译器:VC6或者ICC编译器。

原来myplayer.com是个病毒

看到STLport出了5.13版,于是用SVN从sourceforge.net档了个最新版本--SVN3039版,在VC6的环境编译,谁知cl第一个文件就报错:
'd:\myplayer.com' is not recognized as an internal or external command,operable program or batch file.
NMAKE : fatal error U1077: 'if' : return code '0x1'
 
心想难道上次装播放器把vc的环境搞坏了,检查了vc的环境变量, 系统变量,都没问题啊。vc编译文件怎么会用'd:\myplayer.com' 呢,google了一下,原来这个有病毒的嫌疑。可是D盘并没有一个 myplayer.com 可执行文件啊。 这个绝对有问题啦,果然。

注册表搜一下,找myplayer键和键值,果然在autorun键值下。OK。删了它

现在一切都清静了。。。。

享受STL最新版的性能吧。
6 juin

STLport Configuration Manual (by Boris Fomitchev)

Parameters

There are two major categories of STLport configuration parameters:

  • User-definable preferences
  • Parameters describing compiler bugs and misfeatures

The former class lets you set some specific preferences. Switches controlling these preferences are located in <stl_use_config.h> header, which is self-documented. This manual provide some additional info on them.

For the latter, STLport  comes  preconfigured for a wide variety of compilers.
See list for a complete list of compilers that STLport automatically recognizes.
If your compiler is not being recognized properly, this document will help you to fix the problem.

 

User-defined preferences

You may specify some user-defined preferences for STLport. There are two ways to set the options up :

  1. Setting corresponding #define in <stl_user_config.h> . This is the preferred method. You can also use different <stl_user_config.h> headers for different projects.
  2. Specifying corresponding flag on compiler command-line. Some options also may be suppressed. For example, you may configure the distribution to exploit exception handling with setting _STLP_USE_EXCEPTIONS macro to 1, then turning exception handling off with -D_STLP_NO_EXCEPTIONS command-line option for particular project

Below is a stable set of of user-defined options with description. If not indicated otherwise, default for these options is that they are undefined.

User-defined options for STLport

Controlling Macro Description
_STLP_NO_OWN_IOSTREAMS This switch is used to select one from two major STLport iostream modes. Please visit "Getting Started" section for details.
_STLP_NO_NEW_IOSTREAMS Suppress using new-style iostreams even if they are available.
_STLP_NO_IOSTREAMS This switch is experimental and is intended to be used in embedded environments. It makes STLport assume that no iostreams are available at all.
_STLP_NO_CUSTOM_IO This switch is experimental. Define this if you do not instantiate basic_xxx iostream classes with custom types (which is most likely the case). "Custom" means types other than char, wchar and char_traits<>, like basic_ostream<my_char_type, my_traits<my_char_type> >. When this option is on, most non-inline template functions definitions for iostreams are not seen by the client. Default is off, just not to break compilation for those who do use those types. When on, it saves a lot of compile time for most compilers, also object and executable size for some. That also guarantees that you still use optimized standard i/o when you compile your program without optimization and link with optimized library. Option does not affect STLport library build; you may use the same binary library with and without this option, on per-project basis.
_STLP_USE_DEBUG_LIB (Windows compilers only) Normally, optimized STLport library is being automatically linked in even when you compile your project with _DEBUG set (Debug build). If you wish to use debug build of STLport library for your debug builds, define this option (you will also have to build STLport library with debug flags, via additional "make debug_static debug_dynamic" command, as "make all" only builds "release" and "stldebug" versions of the libraries).
_STLP_USE_STATIC_LIB, _STLP_USE_STATICX_LIB, _STLP_USE_DYNAMIC_LIB (Windows compilers only) Normally, a version of STLport library iwhich corresponds to your RTL library setting is being automatically linked in (if you use dynamic RTL DLL, dynamic STLport DLL is being used, and vice versa). If you wish to force use of particular (static, dynamic, or "staticx" which is static STLport lib built with dynamic RTL) STLport lib, please make use one of those options.
_STLP_NO_OWN_NAMESPACE Suppresses STLport use of namespace _STL:: even if it is recommended, forces STLport to use std::. Normally, STLport uses _STL:: namespace instead of std ::, to prevent clashes with iostreams/string stuff that comes with the compiler.
_STLP_USE_OWN_NAMESPACE Forces STLport to use _STL:: namespace instead of std:: STLport sets this flag automatically if _STLP_USE_NAMESPACES is set.
_STLP_DONT_RENAME_STD Precludes STLport from redefining std:: to _STL::. Define it only if renaming scheme does not work for you for some reason.
_STLP_DEBUG Turns the Debug Mode on. That gets you checked iterators and ranges. Thread-safe.
_STLP_USE_SYSTEM_ASSERT use the system-defined assert instead of fprintf to stderr
_STLP_DEBUG_MESSAGE Uncomment this to force all failed assertions to be directed through a user-defined global function: void __stl_debug_message(const char * format_str, ...). This allows you to take control of assertions for debugging purposes. Note : If you set this macro, you must supply __stl_debug_message function definition somewhere.
_STLP_DEBUG_TERMINATE Uncomment this to force all failed assertions to be executed through user-defined global function: void __stl_debug_terminate(void). This allows you to take control of assertion behaviour for debugging purposes. Default routine throws unique exception if _STLP_NO_DEBUG_EXCEPTIONS is not set, calls abort() otherwise.
Note : If you set this macro, you must supply __stl_debug_terminate function definition somewhere.
_STLP_NO_DEBUG_EXCEPTIONS Comment this out to enable throwing unique exceptions from default __stl_debug_terminate() instead of calling abort().
_STLP_NO_EXCEPTIONS Disables exception handling code.
_STLP_NO_NAMESPACES Puts STLport into global namespace, even if the compiler supports namespaces.
_STLP_NO_RELOPS_NAMESPACE if defined, don't put the relational operators in namespace std::rel_ops.
_REENTRANT Define this if your project is multithreaded. STLport uses MT-safe allocator support then.
_NOTHREADS If defined, STLport don't use any multithreading support.
_STLP_NO_NEW_C_HEADERS If defined, STLport don't use native new-style C headers even if they are available.
_STLP_USE_RAW_SGI_ALLOCATORS Force STLport to use older SGI-style allocators as default ones for containers, instead of standard-conforming allocator<>.
_STLP_USE_MALLOC This makes allocator<> to do plain malloc() calls instead of using SGI optimized node allocator engine.
_STLP_USE_NEWALLOC This makes allocator<> to do plain new() calls instead of using SGI optimized node allocator engine.
_STLP_DEBUG_ALLOC This makes allocator<> to perform memory debugging, such as padding/checking for memory consistency.
_STLP_DEBUG_UNINITIALIZED Use this option to catch uninitialized members in your classes. When it is set, construct() and destroy() fill the class storage with _STLP_SHRED_BYTE (see below). Note : _STLP_DEBUG and _STLP_DEBUG_ALLOC don't set this option automatically
_STLP_SHRED_BYTE Provides a definition for the byte with which raw memory will be filled if _STLP_DEBUG_ALLOC or _STLP_DEBUG_UNINITIALIZED is defined. Choose a value which is likely to cause a noticeable problem if dereferenced or otherwise abused. A good value may already be defined for your platform; see stldebug.h
_STLP_DONT_THROW_RANGE_ERRORS This macro prevents instantiation of at() member function for containers (vector and deque). We do not instantiate at() that does not throw range errors - if this macro is defined, at() method is not defined.
_STLP_MSVC50_COMPATIBILITY This definition makes SGI reverse_iterator to be compatible with other parts of MSVC library. (With partial specialization, it just has no effect). Its use is strongly discouraged - for MSVC5.0 configuration, it is being set automatically.
_STLP_USE_MFC You should define this macro if compiling with MFC - STLport <stl/_config.h> then include <afx.h> instead of <windows.h> to get synchronisation primitives
_STLP_MINIMUM_DEFAULT_TEMPLATE_PARAMS Use minimum set of default arguments on template classes that have more than one - for example map<>, set<>. This has effect only if _STLP_LIMITED_DEFAULT_TEMPLATES is on.
_STLP_NO_PROXY_ARROW_OPERATOR By default, STLport uses proxy technique to enable operator -> for iterators even for those compilers that check the return type of unused instantiations. If this causes problems for your project, turn this switch on to disable proxy ->() operators.
_STLP_USE_ABBREVS Use abbreviated class names internally for linker benefit (don't affect interface). This option is obsolete, but should work in this release.
_STLP_USE_DECLSPEC (Obsolete, Windows only) This switch makes STLport symbols exported from DLL. To use export feature, you should define this macro for all compilation units and also define _STLP_DESIGNATED_DLL macro for one of your DLL's which is supposed to export all STLport symbols. You must use this feature if you use default node allocator and pass objects across DLL boundaries, and do not use STLport iostreams. Defined automatically if you use STLport iostreams and compile with /MD flag.
_STLP_DESIGNATED_DLL (Obsolete, Windows only) Designed to be used together with _STLP_USE_DECLSPEC switch to make one of your DLL's to export STLport symbols for other modules. The library which is designated to export DLL symbols, must have both of those switches defined for compilation and it should include at least <string> header from at least one source file. Normally you should not be using this, it is used by STLport internally to build STLport DLL.

Notes:

  • When using tools like Purify (c), Codeguard (c) or BoundsChecker (c), it is advised to #define _STLP_USE_MALLOC or _STLP_USE_NEWALLOC, otherwise pointer checking will generally not be available on most STL internal structures, thus defeating the purpose of those tools.

 

Compiler-specific switches

Compiler-specific switches are STLport macros describing compiler (mis)features/bugs.Below is the table describing them. They are designed in such a way that the compiler that implements complete set of ANSI C++ features and has no bugs ;) would have empty configuration file. STLport also provides a script that is designed to help you setting those numerous switches. Please read Running configure chapter to learn more about this tool.
Note : do not expect STLport to be configured just by running "configure". It is provided only as a tool to help in the initial configuration phase.

STLport macros describing compiler features

Controlling macro name Description Default
_STLP_NATIVE_INCLUDE_PATH The path where native compiler headers are located. STLport uses this information to import std:: names into _STL:: namespace. Default is ../include. Hint : never install STLport headers in the directory that ends with include.
_STLP_UINT32_T Unsigned 32-bit integral type unsigned long
_STLP_LONG_LONG Defined if compiler supports non-standard long long type Off
_STLP_NO_WCHAR_T Defined if compiler does not support wchar_t type Off
_STLP_WCHAR_T_IS_USHORT wchar_t is not a unique type, and is actually a typedef to unsigned short Off
_STLP_NO_LONG_DOUBLE Defined if compiler does not support long double type Off
_STLP_NO_TYPENAME Defined if your compiler does not support typename keyword Off
_STLP_NO_EXPLICIT Defined if your compiler does not support explicit keyword Off
_STLP_NO_MUTABLE Defined if your compiler does not support mutable keyword Off
_STLP_NO_NEW_STYLE_CASTS Defined if compiler does not support new-style const_cast<> Off
_STLP_NO_BOOL Defined if the compiler does not support bool Off
_STLP_DONT_USE_BOOL_TYPEDEF Bool not supported, but keyword is reserved for future use Off
_STLP_YVALS_H true/false defined in <yvals.h> header ( Visual C++ 4.2) Off
_STLP_DEFAULT_TEMPLATE_PARAM Defined if compiler does not support default template parameters Off
_STLP_DEFAULT_TYPE_PARAM Defined if compiler support only complete types as default parameters Off
_STLP_LIMITED_DEFAULT_TEMPLATES Defined if the compiler cannot handle default non-type template parameters Off
_STLP_NON_TYPE_TMPL_PARAM_BUG Defined if compiler has trouble with functions getting non-type-parameterized classes as parameters Off
_STLP_NO_STATIC_TEMPLATE_DATA Defined if compiler does not support static class data template definition Off
_STLP_STATIC_CONST_INIT_BUG Defined if compiler does not support initialization of const static class data template members within class. Off
_STLP_WEAK_ATTRIBUTE Defined if your compiler provides __attribute((weak))__ construct as extension. Only needed if the compiler can't handle static template data members (gcc 2.7.2) Off
_STLP_HAS_NO_NAMESPACES Defined if your compiler does not support namespaces Off
_STLP_BROKEN_USING_DIRECTIVE "using" keyword does not work with template types Off
_STLP_HAS_NO_EXCEPTIONS Defined if your compiler does not support exception-handling Off
_STLP_NO_EXCEPTION_SPEC Defined if your compiler does not support exception specifications Off
_STLP_THROW_RETURN_BUG Compiler requires return statement after throw() Off
_STLP_NO_BAD_ALLOC Header <new> that comes with the compiler does not define bad_alloc exception Off
_STLP_NO_MEMBER_TEMPLATES Defined if compiler does not support member templates Off
_STLP_NO_MEMBER_TEMPLATE_CLASSES Defined if compiler does not support member template classes Off
_STLP_NO_FRIEND_TEMPLATES Defined if compiler does not support friend templates Off
_STLP_NO_QUALIFIED_FRIENDS Compiler does not accept friend declaration qualified with namespace name. Off
_STLP_NO_CLASS_PARTIAL_SPECIALIZATION Defined if compiler does not support partial template class specialization Off
_STLP_PARTIAL_SPEC_NEEDS_TEMPLATE_ARGS Class being partially specialized require full name (template parameters) of itself for method declarations Off
_STLP_PARTIAL_SPECIALIZATION_BUG Partial specialization has bugs that prevent you from using new-style reverse_iterator Off
_STLP_MEMBER_SPECIALIZATION_BUG Compiler has problems specializing members of partially specialized class Off
_STLP_NO_METHOD_SPECIALIZATION Defined if the compiler does not support specializations of single template method. Off
_STLP_NO_FUNC_PARTIAL_ORDERING Defined if compiler does not support partial template functions ordering Off
_STLP_NO_PARTIAL_SPECIALIZATION_SYNTAX Defined if compiler does not support full template specialization syntax Off
_STLP_NO_EXPLICIT_FUNCTION_TMPL_ARGS Compiler does not support explicit template arguments for functions Off
_STLP_AUTOMATIC_TYPE_TRAITS Defined if the compiler specializes predefined class type_traits<T> for every type. This is an extension Off
_STLP_LOOP_INLINE_PROBLEMS Defined if your compiler can't inline while(), for() Off
_STLP_BASE_MATCH_BUG Defined if the compiler fails to match a template function argument of base type Off
_STLP_NONTEMPL_BASE_MATCH_BUG Defined if the compiler fails to match a template function argument of base (non-template) type Off
_STLP_NESTED_TYPE_PARAM_BUG Defined if the compiler rejects outline method definition explicitly taking nested types/typedefs Off
_STLP_TYPENAME_ON_RETURN_TYPE Compiler requires typename keyword on outline method definition explicitly taking nested types/typedefs Off
_STLP_BASE_TYPEDEF_BUG Defined if your compiler have serious problems with typedefs Off
_STLP_BASE_TYPEDEF_OUTSIDE_BUG Defined if the baseclass typedefs not visible from outside Off
_STLP_MEMBER_POINTER_PARAM_BUG Defined if the compiler can't handle pointer-to-member type as function parameter Off
_STLP_UNINITIALIZABLE_PRIVATE Defined if the compiler has problems with static private data members initialization Off
_STLP_STATIC_ARRAY_BUG Defined if the compiler has trouble instantiating static array members with dimension defined as enum. Off
_STLP_DEFAULT_CONSTRUCTOR_BUG Defined if default constructor for builtin integer type fails to initialize it to 0 Off
_STLP_CONST_CONSTRUCTOR_BUG Defined if constructor required to explicitly call member's default constructors for const objects Off
_STLP_TRIVIAL_CONSTRUCTOR_BUG Defined if the compiler has trouble calling POD-types constructors Off
_STLP_TRIVIAL_DESTRUCTOR_BUG Defined if the compiler has trouble calling POD-types destructors Off
_STLP_MULTI_CONST_TEMPLATE_ARG_BUG problems specializing maps/sets with key type being const Off
__SGI_STL_NO_ARROW_OPERATOR Compiler has deficiencie compiling -> operators for iterators. STLport provides proxy workaround for those. Off
_STLP_NO_AT_MEMBER_FUNCTION Disables at() member functions for containers Off
_STLP_HAS_NO_NEW_IOSTREAMS Native C++ library does not provide new-style templatized iostreams Off
_STLP_NO_EXCEPTION_HEADER compiler lacks <exception> header Off
_STLP_HAS_NO_NEW_C_HEADERS Native library does not provide new-style headers like <cstddef>, only old-style like: <stddef.h> Off
_STLP_NO_NEW_NEW_HEADER Native library does not provide new-style like <new> header. Off
_STLP_VENDOR_GLOBAL_STD compiler-supplied standard library resides in global namespace, not std:: std::
_STLP_VENDOR_GLOBAL_CSTD compiler-supplied <cstdio> and the like put stuff in global namespace, not std:: Off
_STLP_RAND48 Defined if C library has lrand48() function Off
_STLP_NATIVE_INCLUDE_PATH Path to get native compiler headers included. May be relative or absolute. ../include
_STLP_NATIVE_C_INCLUDE_PATH Path to get native compiler's old-style C headers (like stdio.h) included. May be relative or absolute. ../include
_STLP_NATIVE_CPP_C_INCLUDE_PATH Path to get native compiler new-style C headers (like cstdio) included. May be relative or absolute. ../include
_STLP_MAKE_HEADER This macro constructs header path from directory and name. You may change it if your compiler does not understand "/". <path/header>
_STLP_NATIVE_HEADER(header) This macro constructs native include header path from include path and name. You may have do define it if experiencing problems with preprocessor See stl_config.h
_STLP_NATIVE_C_HEADER(header) Same for old-style C headers See stl_config.h
_STLP_NATIVE_CPP_C_HEADER(header) Same for new-style C headers See stl_config.h
_STLP_LINK_TIME_INSTANTIATION This switch should be set on if the compiler supports separate template compilation model, with non-inlined functions and methods being defined in implementation .c file. Off

 

Compilers supported by STLport

Portability is a key feature of STLport. It allows for having single, thoroughly tested code base on all platforms used in the project.Microsoft Visual C++ 4.x - 6.x, for Win32  

Compiling

  • If your program is multi-threaded, make sure you defined macro _REENTRANT to enable proper synchronization (if you are using SGI node allocator. If you are using new_alloc or malloc_alloc, you won't need that) .
  • You may encounter problems using  <iostream> in mix with SGI STL <string>, <stdexcept> without defining _STLP_USE_OWN_NAMESPACE.  Using  _STLP_USE_OWN_NAMESPACE  is the most safe way to cooperate with new I/O
    library and is definitely recommended unless you recompile the whole C++ std library with STLport installed.
  • _STLP_USE_EXCEPTIONS is being defined automatically if the compiler defined _CPPUNWIND macro ("/GX" option). So you should not worry about it.
  • There were reports that Visual C++ 4.0 have problems with namespaces in certain cases. Due to that, _STLP_NO_NAMESPACES  defined for it in <stlcomp.h>.
  • You may encounter problems with too long symbols when compiling with debug information. To handle this, try defining _STLP_USE_ABBREVS in stlcomp.h.
  • You may experience problems with default SGI node allocator. I had no such problems, though. Default node allocator is quite fast, so I wouldn't recommend disabling it without serious reason. However, if it causes problems, define _STLP_USE_MALLOC or _STLP_USE_NEWALLOC to get bare malloc()-based or new()-based default allocator.
  • If your program is multi-threaded, make sure you defined macro _REENTRANT to enable proper synchronization.
  • [ VC++ 4.2 and higher] This version allows use of new-style <iostream>, <string> with STLport , as well as the old-style <iostream.h> . The default is new <iostream> usage.
    If you want to use old-style <iostream.h> in your project:
    • define macro _STLP_NO_NEW_IOSTREAMS in <stl_user_config.h> or at the command line.
    If you are going to use new-style <iostream> without defining _STLP_USE_OWN_NAMESPACE:
    • You may have to edit relative (or set full) path to VC++  native headers in <stl_user_config.h>. Using native versions is necessary to avoid clashes with compiled code in C++ runtime library.
    • In the whole project, use coherent set of headers : all new-style.
       
  • You may also try SGI STL adapted for MSVC++ 5.0 only, by Wayne Ouchida. (Note : looks like it's out of date and  discontinued. It doesn't provide debug support and other extensions of this adaptation).

Migration notes

You should experience no other problems migrating from VC++ STL to SGI STL. Default allocator<T> in this adaptation is fully functional.