Port of the chmlib-0.31 that works on mac os x
Oct 16, I ported chmlib-0.31 to mac os x 10.2 using gcc 3.3.
Here is my steps:
1.File chm_lib.c, line 145, append "|| __ppc__ " to the end.
2.File Makefile, line 11, delete -DCHM_USE_IO64.
3.File Makefile, line 15, modify "LIBTOOL=libtool" to "LIBTOOL=./libtool"
4.In directory chmlib-0.31, provide a script names libtool(wraps libtool command), which typically generated by autoconf, my libtool script is here.
Now, after the four steps, chmlib-0.31 can work well under os x.
2003年10月15日
一种VC++程序本地化的方法
Visual C++中资源本地化最通用的技术就是使用DLL来存储资源。
使用这种技术有以下问题
1.代码和资源无法分开,为了支持不同的语言,必须建立相应的DLL工程。
2.开发者必须参与到资源本地化的过程。
本质上,使用DLL无法将资源和代码进行分离。
我们可以使用脚本文件来存储所有与本地化相关的资源。这样就解决的使用DLL来存储资源的问题。步骤如下:
1.菜单的资源本地化,我们可以使用菜单函数,一次取得菜单ID,然后从资源中取得相应的本地化字符串。
2.对话框的资源本地化,我们从一个给定的对话框ID中依次取得他所有的子控件ID,然后从资源中取得相应的本地化字符串。
3.状态条的资源本地化,我们可以重载CFrameWnd中GetMessageString(),根据传过来的ID从资源中取得相应的本地化字符串。
4.Tooltip的资源本地化,我们可以重载CFrameWnd中OnToolTipText(),从相应的资源中取得本地化字符串。
5.对定制的控件和View,我们可以对使用到的字符串提供一个ID,显示的时候根据这个唯一ID,从相应的字符串中取得本地化资源。
实际上,很多软件都是那么做的,比如flashget等等。
微软Html Help格式研究
http://www.speakeasy.org/~russotto/chm/
http://bonedaddy.net/pabs3/hhm/index.html
http://66.93.236.84/~jedwin/projects/chmlib/
Windows动态库与Linux共享对象技术分析比较
原始出处: http://www.ahcit.com/200306/3a.doc
摘要: 动态链接库技术实现和设计程序常用的技术,在Windows和Linux系统中都有动态库的概念,采用动态库可以有效的减少程序大小,节省空间,提高效率,增加程序的可扩展性,便于模块化管理。但不同操作系统的动态库由于格式不同,在需要不同操作系统调用时需要进行动态库程序移植。本文分析和比较了两种操作系统动态库技术,并给出了将Visual C++编制的动态库移植到Linux上的方法和经验。
关键词:动态链接库 Linux编程 程序移植
1 引言
动态库(Dynamic Link Library abbr,DLL)技术是程序设计中经常采用的技术。其目的减少程序的大小,节省空间,提高效率,具有很高的灵活性。采用动态库技术对于升级软件版本更加容易。与静态库(Static Link Library)不同,动态库里面的函数不是执行程序本身的一部分,而是根据执行需要按需载入,其执行代码可以同时在多个程序中共享。
在Windows和Linux操作系统中,都可采用这种方式进行软件设计,但他们的调用方式以及程序编制方式不尽相同。本文首先分析了在这两种操作系统中通常采用的动态库调用方法以及程序编制方式,然后分析比较了这两种方式的不同之处,最后根据实际移植程序经验,介绍了将VC++编制的Windows动态库移植到Linux下的方法。
2 动态库技术
2.1 Windows动态库技术
动态链接库是实现Windows应用程序共享资源、节省内存空间、提高使用效率的一个重要技术手段。常见的动态库包含外部函数和资源,也有一些动态库只包含资源,如Windows字体资源文件,称之为资源动态链接库。通常动态库以.dll,.drv、.fon等作为后缀。相应的windows静态库通常以.lib结尾,Windows自己就将一些主要的系统功能以动态库模块的形式实现。
Windows动态库在运行时被系统加载到进程的虚拟空间中,使用从调用进程的虚拟地址空间分配的内存,成为调用进程的一部分。DLL也只能被该进程的线程所访问。DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。DLL模块中包含各种导出函数,用于向外界提供服务。DLL可以有自己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL在内存中只有一个实例;DLL实现了代码封装性;DLL的编制与具体的编程语言及编译器无关,可以通过DLL来实现混合语言编程。DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。
根据调用方式的不同,对动态库的调用可分为静态调用方式和动态调用方式。
(1)静态调用,也称为隐式调用,由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(Windows系统负责对DLL调用次数的计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须在源文件中声明一下。 LIB文件包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文件名,不含有实际的代码。Lib文件包含的信息进入到生成的应用程序中,被调用的DLL文件会在应用程序加载时同时加载在到内存中。
(2)动态调用,即显式调用方式,是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,比较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。在Windows系统中,与动态库调用有关的函数包括:
①LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。
②GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL内部地址。
③FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。
在windows中创建动态库也非常方便和简单。在Visual C++中,可以创建不用MFC而直接用C语言写的DLL程序,也可以创建基于MFC类库的DLL程序。每一个DLL必须有一个入口点,在VC++中,DllMain是一个缺省的入口函数。DllMain负责初始化(Initialization)和结束(Termination)工作。动态库输出函数也有两种约定,分别是基于调用约定和名字修饰约定。DLL程序定义的函数分为内部函数和导出函数,动态库导出的函数供其它程序模块调用。通常可以有下面几种方法导出函数:
①采用模块定义文件的EXPORT部分指定要输入的函数或者变量。
②使用MFC提供的修饰符号_declspec(dllexport)。
③以命令行方式,采用/EXPORT命令行输出有关函数。
在windows动态库中,有时需要编写模块定义文件(.DEF),它是用于描述DLL属性的模块语句组成的文本文件。
2.2 Linux共享对象技术
在Linux操作系统中,采用了很多共享对象技术(Shared Object),虽然它和Windows里的动态库相对应,但它并不称为动态库。相应的共享对象文件以.so作为后缀,为了方便,在本文中,对该概念不进行专门区分。Linux系统的/lib以及标准图形界面的/usr/X11R6/lib等目录里面,就有许多以so结尾的共享对象。同样,在Linux下,也有静态函数库这种调用方式,相应的后缀以.a结束。Linux采用该共享对象技术以方便程序间共享,节省程序占有空间,增加程序的可扩展性和灵活性。Linux还可以通过LD-PRELOAD变量让开发人员可以使用自己的程序库中的模块来替换系统模块。
同Windows系统一样,在Linux中创建和使用动态库是比较容易的事情,在编译函数库源程序时加上-shared选项即可,这样所生成的执行程序就是动态链接库。通常这样的程序以so为后缀,在Linux动态库程序设计过程中,通常流程是编写用户的接口文件,通常是.h文件,编写实际的函数文件,以.c或.cpp为后缀,再编写makefile文件。对于较小的动态库程序可以不用如此,但这样设计使程序更加合理。
编译生成动态连接库后,进而可以在程序中进行调用。在Linux中,可以采用多种调用方式,同Windows的系统目录(../system32等)一样,可以将动态库文件拷贝到/lib目录或者在/lib目录里面建立符号连接,以便所有用户使用。下面介绍Linux调用动态库经常使用的函数,但在使用动态库时,源程序必须包含dlfcn.h头文件,该文件定义调用动态链接库的函数的原型。
(1)_打开动态链接库:dlopen,函数原型void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。
(2)取函数执行地址:dlsym,函数原型为: void *dlsym(void *handle, char *symbol);
dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。
(3)关闭动态链接库:dlclose ,函数原型为: int dlclose (void *handle);
dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。 (4)动态库错误函数:dlerror,函数原型为: const char *dlerror(void); 当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
在取到函数执行地址后,就可以在动态库的使用程序里面根据动态库提供的函数接口声明调用动态库里面的函数。在编写调用动态库的程序的makefile文件时,需要加入编译选项-rdynamic和-ldl。
除了采用这种方式编写和调用动态库之外,Linux操作系统也提供了一种更为方便的动态库调用方式,也方便了其它程序调用,这种方式与Windows系统的隐式链接类似。其动态库命名方式为“lib*.so.*”。在这个命名方式中,第一个*表示动态链接库的库名,第二个*通常表示该动态库的版本号,也可以没有版本号。在这种调用方式中,需要维护动态链接库的配置文件/etc/ld.so.conf来让动态链接库为系统所使用,通常将动态链接库所在目录名追加到动态链接库配置文件中。如具有X window窗口系统发行版该文件中都具有/usr/X11R6/lib,它指向X window窗口系统的动态链接库所在目录。为了使动态链接库能为系统所共享,还需运行动态链接库的管理命令./sbin/ldconfig。在编译所引用的动态库时,可以在gcc采用 –l或-L选项或直接引用所需的动态链接库方式进行编译。在Linux里面,可以采用ldd命令来检查程序依赖共享库。
3 两种系统动态库比较分析
Windows和Linux采用动态链接库技术目的是基本一致的,但由于操作系统的不同,他们在许多方面还是不尽相同,下面从以下几个方面进行阐述。
(1)动态库程序编写,在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数作为初始化的人口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。Linux下的gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要到函数做特别声明,编写比较方便。
(2)动态库编译,在windows系统下面,有方便的调试编译环境,通常不用自己去编写makefile文件,但在linux下面,需要自己动手去编写makefile文件,因此,必须掌握一定的makefile编写技巧,另外,通常Linux编译规则相对严格。
(3)动态库调用方面,Windows和Linux对其下编制的动态库都可以采用显式调用或隐式调用,但具体的调用方式也不尽相同。
(4)动态库输出函数查看,在Windows中,有许多工具和软件可以进行查看DLL中所输出的函数,例如命令行方式的dumpbin以及VC++工具中的DEPENDS程序。在Linux系统中通常采用nm来查看输出函数,也可以使用ldd查看程序隐式链接的共享对象文件。
(5)对操作系统的依赖,这两种动态库运行依赖于各自的操作系统,不能跨平台使用。因此,对于实现相同功能的动态库,必须为两种不同的操作系统提供不同的动态库版本。
4 动态库移植方法
如果要编制在两个系统中都能使用的动态链接库,通常会先选择在Windows的VC++提供的调试环境中完成初始的开发,毕竟VC++提供的图形化编辑和调试界面比vi和gcc方便许多。完成测试之后,再进行动态库的程序移植。通常gcc默认的编译规则比VC++默认的编译规则严格,即使在VC++下面没有任何警告错误的程序在gcc调试中也会出现许多警告错误,可以在gcc中采用-w选项关闭警告错误。
下面给出程序移植需要遵循的规则以及经验。
(1)尽量不要改变原有动态库头文件的顺序。通常在C/C++语言中,头文件的顺序有相当的关系。另外虽然C/C++语言区分大小写,但在包含头文件时,Linux必须与头文件的大小写相同,因为ext2文件系统对文件名是大小写敏感,否则不能正确编译,而在Windows下面,头文件大小写可以正确编译。
(2)不同系统独有的头文件。在Windows系统中,通常会包括windows.h头文件,如果调用底层的通信函数,则会包含winsock..h头文件。因此在移植到Linux系统时,要注释掉这些Windows系统独有的头文件以及一些windows系统的常量定义说明,增加Linux都底层通信的支持的头文件等。
(3)数据类型。VC++具有许多独有的数据类型,如__int16,__int32,TRUE,SOCKET等,gcc编译器不支持它们。通常做法是需要将windows.h和basetypes.h中对这些数据进行定义的语句复制到一个头文件中,再在Linux中包含这个头文件。例如将套接字的类型为SOCKET改为int。
(4)关键字。VC++中具有许多标准C中所没有采用的关键字,如BOOL,BYTE,DWORD,__asm等,通常在为了移植方便,尽量不使用它们,如果实在无法避免可以采用#ifdef 和#endif为LINUX和WINDOWS编写两个版本。
(5)函数原型的修改。通常如果采用标准的C/C++语言编写的动态库,基本上不用再重新编写函数,但对于系统调用函数,由于两种系统的区别,需要改变函数的调用方式等,如在Linux编制的网络通信动态库中,用close()函数代替windows操作系统下的closesocket()函数来关闭套接字。另外在Linux下没有文件句柄,要打开文件可用open和fopen函数,具体这两个函数的用法可参考文献[2]。
(6)makefile的编写。在windows下面通常由VC++编译器来负责调试,但gcc需要自己动手编写makefile文件,也可以参照VC++生成的makefile文件。对于动态库移植,编译动态库时需要加入-shared选项。对于采用数学函数,如幂级数的程序,在调用动态库是,需要加入-lm。
(7)其它一些需要注意的地方
①程序设计结构分析,对于移植它人编写的动态库程序,程序结构分析是必不可少的步骤,通常在动态库程序中,不会包含界面等操作,所以相对容易一些。
②在Linux中,对文件或目录的权限分为拥有者、群组、其它。所以在存取文件时,要注意对文件是读还是写操作,如果是对文件进行写操作,要注意修改文件或目录的权限,否则无法对文件进行写。
③指针的使用,定义一个指针只给它分配四个字节的内存,如果要对指针所指向的变量赋值,必须用malloc函数为它分配内存或不把它定义为指针而定义为变量即可,这点在linux下面比windows编译严格。同样结构不能在函数中传值,如果要在函数中进行结构传值,必须把函数中的结构定义为结构指针。
④路径标识符,在Linux下是“/”,在Windows下是“/”,注意windows和Linux的对动态库搜索路径的不同。
⑤编程和调试技巧方面。对不同的调试环境有不同的调试技巧,在这里不多叙述。
5 结束语
本文系统分析了windows和Linux动态库实现和使用方式,从程序编写、编译、调用以及对操作系统依赖等方面综合分析比较了这两种调用方式的不同之处,根据实际程序移植经验,给出了将VC++编制的Windows动态库移植到Linux下的方法以及需要注意的问题,同时并给出了程序示例片断,实际在程序移植过程中,由于系统的设计等方面,可能移植起来需要注意的方面远比上面复杂,本文通过总结归纳进而为不同操作系统程序移植提供了有意的经验和技巧。
参 考 文 献
[1] David J.Kruglinski,Visual C++6.0技术内幕(第五版),希望图书创作室译。北京:北京希望电子出版社,1999.5.
[2] 中科红旗软件技术有限公司 Linux/UNIX高级编程 清华大学出版社 2001.
[3] Sandra Loosemore et.al. The GNU C Library Reference Manual,机械工业出版社,2000.8.
2003年10月12日
移植Mozilla字符集检测代码
字符集检测非常有用,在无法获得字符信息的情况下,我们需要有一种机制,方法来检测字符的编码,从而对字符进行正确的操作。在Windows的环境中,我们可以调用Mlang接口来进行字符集检测。通过调用IMultiLanguage2中的DetectCodepageInIStream、DetectInputCodepage这些方法,可以完成代码页的自动检测。
但是,在其它的环境中,比如Unix、Linux或者是MAC系统中,就需要我们自己提供字符集检测的代码。Mozilla开源项目中就提供了这样一套代码(可以查看我在10月2日翻译的“一种语言/编码检测的复合方法”),为了让这套代码从Mozilla项目中剥离出来,我们需要做一些小的工作,可以参考Mozilla网站上的“How to build standalone universal charset detector from Mozilla source”这篇文章。
下面我将简要介绍一下我所进行的移植工作,步骤如下:
1.首先获取Mozilla的代码,可以从www.mozilla.org网站上下载mozilla的代码,或者从cvs中提取代码,所有字符集检测的代码存放在mozilla/extensions/universalchardet/src这个目录中。
2.创建两个文件nsCore.h和config.h,内容如下
#ifndef nsCore_h__
#define nsCore_h__
#include
typedef int PRInt32;
typedef unsigned int PRUint32;
typedef bool PRBool;
typedef short PRInt16;
#define PR_FALSE false
#define PR_TRUE true
#define nsnull NULL
#define PR_MALLOC malloc
#define PR_FREEIF(x) do { if(x) free(x); } while(0)
#endif
以上代码为nsCore.h,用来替换原先代码中使用的mozilla中定义过的类型。
#ifndef CONFIG_H
#define CONFIG_H
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
# define BUILD_WIN32
#endif
#if defined(BUILD_WIN32) && !defined(BUILD_NO_DLL)
# ifdef CLASS_IMPLEMENTATION
# define CLASS_EXPORT __declspec(dllexport)
# else
# define CLASS_EXPORT __declspec(dllimport)
# endif
#else
# define CLASS_EXPORT
#endif
#endif
以上代码为config.h,用来设置编译选项。
3.修改nsUniversalDetector.h,在头部添加#include "config.h",修改nsUniversalDetector.h和nsUniversalDetector.cpp,将所有XPCOM的类注释掉。
4.提供一个nsUniversalDetector类的子类,提供一个Report方法。
5.编写Makefile,在这里,我写了两个Makefile,一个使用VC6编译,一个使用GCC编译。在VC6得编译中,可以编译成DLL和静态库。
通过以上步骤,我们就可以将字符集检测代码从Mozilla项目中移植出来,供我们的自己的项目使用了。
我在这里提供了压缩后的移植代码,detect.zip,解压后,可以使用nmake编译,或者在任何提供GCC的平台下通过make编译。如nmake -f makefile.msvc或者make -f makefile.gcc。在VC中使用静态连接库时,需要#define BUILD_NO_DLL。同时,提供了一个UniversalStringDetector类,在进行字符集检测的时候,可以调用这个类的DoIt方法,返回的结果存放在oCharset中。
2003年10月11日
2003年10月10日
anjuta的编译
linux下的开发环境不多,最常见的有kdeveloper和anjuta,一般的linux系统会缺省的替你装上kdeveloper,anjuta却不会装上。
个人认为在开发上,anjuta比kdeveloper好使。从http://anjuta.sourceforge.net上下载anjuta的源代码,anjuta当前最新版本为anjuta-1.1.97。
tar zxvf anjuta-1.1.97.tar.gz
cd anjuta-1.1.97
./configure
在配置中,由于个人的环境不一样,所以很大的可能会配置不成功,不过没事,./configure最后会提示你缺少哪些包(package)。你所需要的就是从网上将缺少的包拿回来,编译后再install即可。
在我的redhat 9的环境中缺少libgomeprint-2.0,libgnomeprintui-2.0,vte,libxvt-2.0这些包,我丛gnome镜像网站http://fto,acc.umu.se/pub/GNOME/sources中找到了这些库,分别安装上即可。有一点,需要注意的是,缺省的这些库会安装在/usr/local/lib中,所以需要在.bash_profile中培植PKG_CONFIG_PATH为
PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
接下来事情就很简单了
./configure
make
mak install
anjuta
漂亮的anjuta就出现了!
2003年10月02日
一种语言/编码检测的复合方法
翻译自Mozilla的网站。
这篇论文讨论了组合三种不同的检测方法来实现自动字符集检测。
A composite approach to language/encoding detection)
Shanjian Li (shanjian@netscape.com)
Katsuhiko Momoi (momoi@netscape.com)
Netscape Communications Corp.
[注:这篇论文最初发表在19届国际Unicode会议(19th International Unicode Conference)(San Jose)。那以后,我们的实现经受住了时间和实际应用的检验,并且,我们还作了许多改进。一个主要的变化是我们现在使用正序列来检测单字节字符集,参见4.7和4.7.1节。这篇论文写于通用字符集检测代码集成到Moailla主代码前(参见第8节)。自此以后,字符集检测代码被合并到了代码树中。如想查看最新的实现,请在Mozilla的代码树中查看相应代码。作者 - 2002年11月25日。]
1.概要:
这篇论文提供三种自动检测的方法来判定无明显字符集声明的文档的编码。我们将分别讨论每种方法的优点和缺点,并且提供了一种复合后的,更有效的方法来检测编码,这样,三种检测方法就可以互为补充。我们认为自动检测在使浏览器用户避免经常使用编码菜单手动选择编码上很有用,同时在编码菜单很少出现的情况下,提供了更合理的处理方式。我们假设,文档转化到Unicode对用户是透明的。无论字符编码采用的是某种Unicode编码还是本地编码,用户仅需知道字符最终显示是正确的就行。好的自动编码检测能有效的帮助用户处理大部分的编码事项,而无需用户手动参与。
2.背景:
自从进入计算机时代以来,人们创造了许多使用计算机数据表示的编码方案来表达不同的文字/字符集。随着全球化和Internet的发展,跨语言和区域的信息交换越来越重要。但是现存的多种编码方案对此是一个屏障。Unicode提供了通用的编码解决方案,但是,迄今为止,各种各样的因素使它并没有代替现存的区域编码方案。除了W3C和IETF建议使用UTF-8作为缺省编码,比如XML,XHTML,RDF。因此,现今的国际化软件不仅要处理Unicode编码,还要处理其它多种不同的编码方式。
我们当前的工作是在开发Internet浏览器的环境中开展的。为了处理如今Web上使用不同编码的各种语言,我们做了许多努力。为了获取正确的显示结果,浏览器需要利用HTTP服务器返回的编码信息,网页或者是最终用户通过选择编码菜单而得到的编码方式。此外,大部分用户没有能力手动的通过编码菜单来进行操作。如果没有编码信息的话,网页有时就会显示为“垃圾”字符,用户就无法得到他们想要的信息。这最终会导致用户认为他们的浏览器有故障或有Bug。
由于越来越多的Internet标准协议指定Unicode作为缺省的编码方式,网页将会不容置疑的转向使用Unicode来编码。好的通用自动检测方法可以对这种转向提供重要的贡献,因为它们工作很自然,无需用户使用编码菜单。在这种情况下,渐进的转向将会以用户不易察觉的方式进行,这是由于,对用户来说,网页总会显示正确,他们无需考虑使用编码菜单。这种平滑的转变将使编码对用户来说越来越不需要关注。自动检测在这种场景中将很关键。
3.问题范围(Problem Scope):
3.1. 通用模式
让我们从通用的模式开始。对大多数的应用,下面的示例将代表一个自动检测使用的通用框架:
输入数据 -> 自动检测器 -> 返回结果
应用/程序接受自动检测器返回的结果,并且将信息使用在不同用途上,比如,设置数据的编码,显示原始创建者的数据,将它传给其他的程序,等等。
这篇论文中所讨论的自动检测方法使用Internet浏览器作为应用环境,其他的应用也可以很容易的将其移植。
3.2. 浏览器和自动检测
浏览器可以使用某种检测算法来自动检测网页的编码方式。一个程序可以潜在的在假定不同编码的前提下,对一段文本做出随意的解释,但是,除了一些极端罕见的情况外,只有一种解释才是网页作者想要得。这就是为什么通常对用户来说,只有指定的语言才能正确合理的显示网页。
为了列出设计自动检测算法中的主要因素,我门对输入文本和步骤作如下假定,以网页数据作例,
1.对某种语言来说,输入文本由对读者来说具有可读性的单词/句子组成。(=数据具有非垃圾性)
2.输入文本是从Internet上的典型网页中取得的。(=数据不是从一些消失的或者是古代的语言中取得的)
3.输入文本中有可能包含和编码无关的,额外的噪音数据。比如HTML的标记,无关的单词(如,出现在中文文档中的英文单词),空格和其它的格式/控制字符。
对自动检测来说,要包含所有已知语言和编码方式几乎是一项无法完成的任务。在现今的方法中,我们试图包含所有东亚语言中常用的编码,同时,对单字符检测也提供了一种通用模式。俄语编码被选择作为后一种检测方法实现的例子,同时也作为单字符检测测试的基础。
4.目标中的多字节编码方式包括 UTF8,Shift-JIS,EUC-JP,GB2312,Big5,EUC-TW,EUC-KR,ISO2022-XX,和HZ。
5.提供一种通用模式来处理单字节编码 - 俄语编码(KOI8-R, ISO8859-5, window1251, Mac-cyrillic, ibm866, ibm855)将作为实现的例子,同时也作为测试基础。
4.自动检测的三种方法
4.1介绍
在这一节中,我们讨论三种不同的检测文本数据编码的方法,它们是1)编码模式方法(Coding scheme method,2)字符分布(Character Distribute),和3)双字符序列分布(2-Char Sequence Distribute)。每种方法单独使用的时候,都有它的长处和不足,如果我们以互补的方式使用所有3种方法的话,结果将是非常理想的。
4.2编码模式方法
这种方法在检测多字节编码的时候也许是最显而易见的方法,也是通常最容易使用的方法。在任何多字节编码模式中,并不是所有可能的代码点都被使用的。如果在验证特定编码的时候,碰到一个非法字节或非法字节序列(如,无用的代码点),我们可以立即判断出这种编码猜测不是正确的。一小部分的代码点同样也能代表特定的编码方式,这样,我们也能利用这种事实立即做出正确的判断。Frank Tang(Netscape Communications)开发了一个非常有效的,基于编码模式的,通过使用并行状态机(parallel state machine)来检测字符集的算法。他的基本思想是:
对每一个编码模式,都有一个相应的状态机被用来验证这种特定编码的字节序列。对检测器收到的每一个字节,它将会被输入到每一个可用的,活动的状态机中,每次一个字节。状态机基于前一个状态和它所收到的字节来改变它的状态。自动检测器对状态机的三种状态感兴趣:
- START 状态:这种状态代表两种情形,初始化,或是代表字符集的一个合法字节序列已被验证。
- ME 状态:这种状态代表状态机验证到了字符集特有的一个字节序列,并且其它可能的字符集不包含这个字节序列。这会导致检测器立即返回一个确定的回答。
- ERROR 状态:这种状态代表状态机验证了字符集的一个非法字节序列。这会立即导致对这种字符集的否定回答。检测器从此将会排除这种编码方式,不作考虑。
在一个典型的例子中,只有一个状态机会作出确定的回答,而其它的状态机会做出的否定的回答。
我们当前工作中使用的PSM(Parallel State Machine)版本是Frank Tang原先工作改造后的版本。只要一个状态机达到START状态,就意味着它成功检测了一个合法的字符序列,我们向状态机查询这种字符序列中总共有多少个字节。这个信息会用在2个方面:
- 首先,对UTF-8编码方式来说,如果有许多多字节的序列都通过验证的话,输入的数据很少有可能不是采用UTF-8编码的。因此,我们统计UTF-8状态机验证过的多字节数。当达到一个特定的数量时(=阀值),结论就可以做出了。
- 其次,对其它的多字节编码来说,这个信息可以输入到字符分布分析器(可以查看下面的介绍),这样,分析器就可以在字符数据而不是原始数据的基础上进行工作了。
4.3字符分布方法:
无论哪种语言,总有一些字符比其它字符更常用。利用这个事实,我们可以对每种语言建立起相应的数据模式。对那些字符数较多的语言,比如汉语,日语和韩语,尤其有用。我们经常听到与此有关的分布统计,但是我们没有找到太多的公布结果。因此,为了继续讨论,我们依赖自己搜集的数据。
4.3.1.简体中文:
我们对采用GB2312编码的6763个字符研究的结果,显示出如下的分布结果:
Number of Most Frequent Characters | Accumulated Percentage |
10 | 0.11723 |
64 | 0.31983 |
128 | 0.45298 |
256 | 0.61872 |
512 | 0.79135 |
1024 | 0.92260 |
2048 | 0.98505 |
4096 | 0.99929 |
6763 | 1.00000 |
Table 1. Simplified Chinese Character Distribution Table
4.3.2.繁体中文:
对于采用Big5编码的繁体中文,台湾普通话推广委员会(Taiwan's Mandarin Promotion Council)年度研究显示了类似的结果。
Number of Most Frequent Characters | Accumulated Percentage |
10 | 0.11713 |
64 | 0.29612 |
128 | 0.42261 |
256 | 0.57851 |
512 | 0.74851 |
1024 | 0.89384 |
2048 | 0.97583 |
4096 | 0.99910 |
Table 2. Traditional Chinese Character Distribution Table
4.3.3.日语
对日语,我们采用自己搜集的数据,并写了一个实用程序来分析它们。下面的表格显示了结果:
Number of Most Frequent Characters | Accumulated Percentage |
10 | 0.27098 |
64 | 0.66722 |
128 | 0.77094 |
256 | 0.85710 |
512 | 0.92635 |
1024 | 0.97130 |
2048 | 0.99431 |
4096 | 0.99981 |
1.00000 |
Table 3. Japanese Character Distribution Table
4.3.4.韩语
同样,对于韩语,我们采用自己从Internet上搜集的数据,并用我们自己的实用程序分析它们,结果如下:
Number of Most Frequent Characters | Accumulated Percentage |
10 | 0.25620 |
64 | 0.64293 |
128 | 0.79290 |
256 | 0.92329 |
512 | 0.98653 |
1024 | 0.99944 |
2048 | 0.99999 |
4096 | 0.99999 |
Table 4. Korean Character Distribution Table
4.4.分布结果的通用特性:
对所有4种语言,我们发现在我们定义的应用范围内,很少一部分的编码点占据了较大的百分比。此外,对这些代码点的更进一步的考察显示它们散布在一个很广的编码范围中。这给了我们克服在编码模式分析中遇到的通用问题的一种途径,如,不同国家的编码有可能共享一些相互重叠的代码点。由于这些语言中最常出现的字符集具有我们上面描述的特性,因此,在编码模式方法中不同编码有相互重叠的问题,将在分布方法中变得无关紧要了。
4.5.分析算法
为了验证某个语言基于字符出现频率/分布的统计特性,我们需要一个从文本输入流中计算出一个数值的算法。这个数值将表明文本是某种字符集编码的可能性。一个很直观的做法是用每个字符的出现频率权重来计算这个值。不过,从我们对不同字符编码的经验上,我们发现这种做法并不是必须的,而且它占用了太多CPU处理能力和过多的内存。一个简单的版本同样提供了很令人满意的结果,并且使用非常少的资源,同时运行得很快。
在我们当前的方法中,一个给定编码中的所有字符会被分到两个类别中,“经常使用的”和“非经常使用的”。如果一个字符是出现在出现频率分布表中的前512个字符中,它会被分类到“经常使用”。之所以选择512,是由于它在所有4种语言的输入文本中都覆盖了很大一部分的百分比,同时仅占据一小部分的代码点。在输入文本中,我们以批处理的方式统计每个类别中字符的数量,然后计算一个我们称之为分布率(Distribuion Ratio)的浮点值。
分布率的定义如下
分布率 = 出现在512个最常用的的字符中的字符数量 / 出现在剩下的字符中的字符数量
每一种检测过的多字节编码方法都显示了独特的分布率。从分布率起,我们就可以对原始输入的文本对一个给定的编码方法来计算可信度。下面对每一种编码方法的讨论将会对这个问题说得更清楚一些。
4.6分布率和可信度(Confidence Level):
让我们分别通过4种语言的数据来查看分布率的不同。注意,术语分布率代表两个意思。“理想”的分布率是对语言/字符集的定义而不是对编码的定义。如果某种语言/字符集表现为多种编码方式,那么,对每一种编码,我们通过将输入数据分类为“经常使用的”或“非经常使用的”来计算“实际上的”分布率。然后将这个值和理想的语言/字符集分布率进行比较。基于实际上获得的分布率,我们可以计算输入数据对每一种字符集的可信度,描述如下。
4.6.1.简体中文(GB2312)
GB2312包含两级中文字符。1级包含3755个字符,2级包含3008个字符。1级字符比2级字符常用,同时,不难得出512个最常用字符都包含在GB2312中的1级字符中。由于1级字符使用发音来排序,因此,512个最常用字符几乎完全分散在3755个代码点中。尽管这些字符占据1级字符所有代码点中的13.64%,但是在典型的中文文本中,它却有79.135%的出现机会。在理想的状况下,一段包含足够多字符的中文文本会返回给我们如下的结果:
分布率 = 0.79135/(1-0.79135) = 3.79
并且,对使用同样编码方案随机生成的文本来说,不考虑2级字符的话,比率大致为512/(3755-512) = 0.157。
如果我们将2级字符考虑进去的话,我们假定1级字符中每个字符出现的概率为p1,2级的为p2,计算公式将是:
512*p1/(3755*p1 + 3008*p2 – 512*p1) = 512/(3755 + 3008*p2/p1-512)
很明显,这个值将更小。在后面的分析中,我们仅使用最坏的情况作为比较。
4.6.2.Big 5:
Big5和EUC-TW(如,CNS字符集)编码具有类似的情况。Big5同样将中文字符编码成2级。最常用的512个字符均匀的分布在5401个1级字符中。从Big5编码文本中得到的理想分布率是
分布率 = 0.74851/(1-0.74851) = 2.98
并且,对随即生成的文本,分布率为
512/(5401-512) = 0.105
由于Big5的1级字符基本上等同于CNS字符集的1级字符,所以对EUC-TW来说,可以适用同样的分析。
4.6.3.日语Shift_JIS和EUC-JP:
对日语来说,平假名和片假名比日文汉字常用。由于Shift-JIS和EUC-JP将平假名和片假名编码在不同的编码区域,我们仍然可以使用这种方法将这两种编码区分开。
那些在512个最常用的的字符中出现的的日文汉字字符仍然分布在2965个1级JIS日文汉字集中。同样的分析得出如下的分布率:
分布率 = 0.92635/(1-0.92635) = 12.58
对随即生成的日文文本数据,分布率最少为
512/(2965+63+83+86-512) = 0.191
上面的计算中包含 Hankaku 片假名(63个),平假名(83个),和片假名(86个)。
4.6.4.韩语 EUC-KR:
在EUC-KR编码中,实际使用在典型韩语文本中的中文字符可以忽略不计。使用这种编码的2350个韩语字符按照发音排列。在我们分析了很大数量的韩语文本数据后得出的频率表中,最常用的字符均匀的分布在2350个代码点中。用同样的分析,在理想的情况下,我们得到
分布率 = 0.98653/(1-0.98653) = 73.24
对随机生成的韩语文本,分布率为
512/(2350-512) = 0.279
4.6.5.计算可信度
从前面对每一种语言的讨论,我们可以以如下的形式定义出每一种数据集的可信度
Confidence Detecting(InputText)
{
for each multi-byte character in InputText
{
TotalCharacterCount++;
if the character is among 512 most frequent ones
FrequentCharacterCount++;
}
Ratio = FrequentCharacterCount
/ (TotalCharacterCount-FreqentCharacterCount);
Confidence = Ratio / CHARSET_RATIO;
Return Confidence;
}
对每一种数据集的可信度定义为输入数据的分布率除以从上面分析得出的理想分布率。
4.7.双字符序列分布方法
对仅使用很少一部分字符的语言来说,我们需要比统计单字符出现率更进一步。字符组合揭示了更多的语言-字符特性。我们定义双字符序列为在输入文本中接连出现的2个字符,在这种情况中,顺序是非常重要的。由于在一种语言中,并不是所有字符都具有相同的出现率,双字符序列分布非常倾向于与语言/编码相关。这种特性可以用在语言检测上。在检测字符编码的时候,这会导致更好的可信度,在检测单字节编码上很有用处。
用俄语举例来说。我们下载了20MB的俄语纯文本,并且编写了一个程序来分析文本。这个程序发现了总共为21199528个双字符序列。在我们发现的序列中,有一些是和我们的考察无关的,比如空格-空格组合。这些序列被认为是噪音序列,并且,它们的出现没有被包含在分析中。在我们用来检测俄语的数据中,去处这些噪音数据,剩下20134122个双字符序列。总共占据我们从数据中所发现序列的95%。用来建立我们语言模式的序列可以被分为4096个不同的序列,并且,其中的1961个序列在我们的20134122个样本中出现次数少于3次。我们称这些序列为这种语言的负序列集。
4.7.1.可信度检测算法
对单字节语言,我定义如下的可信度:
Confidence Detecting(InputText)
{
for each character in InputText
{
If character is not a symbol or punctuation character
TotalCharacters++;
Find its frequency order in frequency table;
If (Frequency order < SampleSize)
{
FrequentCharCount++;
If we do not have lastChar
{
lastChar = thisChar;
continue;
}
if both lastChar and thisChar are within our sample range
{
TotalSequence++;
If Sequence(lastChar, thisChar) belongs to NegativeSequenceSet
NetgativeSequenceCount++;
}
}
}
Confidence = (TotalSequence – NegativeSequenceCount)/TotalSequence
* FrequentCharCount / TotalCharacters;
return Confidence;
}
在这个算法中,需要对如下事情解释一下。
首先,序列分析没有对所有的字符都进行。我们当然可以通过建立一个256 x 256的矩阵来包含所有的那些字符序列,但是,其中的许多对语言/编码分析来说是无关的。由于绝大多数的单字节语言仅使用数量不多于64个的字母,而最常使用的64个字符几乎包含了所有语言中都有的字符。因此,矩阵可以缩减成为更小的64 x 64。所以我们使用64作为我们工作中的采样大小。我们选择用来建立我们模式
的64个字符,是基于频率统计并作了相应调整后的。一些字符,比如0x0d和0x0a,在我们的观点中,和空格(0x20)很相似,所以,从我们的样本中移除了。
其次,对所有被64 x 64矩阵模式包含的序列,许多序列同样对用来检测语言/编码来说是无关的。几乎所有的单字节语言编码都包含ASCII子集,在其它的语言数据中,英语单词也会比较常见,尤其在网页中。空格-空格序列对任何的语言编码来说,明显是无关的。所有这些,在我们的检测中都被认为是噪音数据,并且通过过滤被移除了。
最后,在计算可信度上,我们同样需要统计出现在和不出现在我们采样范围内的字符数。如果一个小采样数据中的大部分字符都出现在我们的采样范围内的话,由于在这种情况下,负序列很少出现,因此序列分布本身将会返回一个较高的值。通过过滤,如果文本使用的是希望的编码的话,大部分提供给检测器的字符将会落在采样范围内。因此,通过统计负序列获得的可信度需要用这个数值调整一下。
对前面叙述的总结如下:
- 字符集验证的时候,只用到了所有字符中的一少部分子集。这保证了我们的模式不会过大。我们同样也通过减少噪音序列来保证我们的检测准确率更高。
- 每种语言模式使用脚本/工具生成的
- 在处理Latin字母表字符时:
- 如果语言中没有使用Latin字母的话,字母-字母序列被认为是噪音序列而从检测中被移除了。(比如,网页中,出现在其他语言中的英语单词)
- 如果语言中使用Latin字母的话,这些序列被保留下来用以分析。
- 落入采样范围的字符和没有落入采样范围的字符都被计数,这样,它们就可以用来计算可信度了。
5.三种方法比较:
5.1.编码模式:
对许多单字节编码来说,所有使用到的代码点都是均匀分布的。甚至对那些包含一些无用代码点的编码方式来说,这些无用的代码点在其它的编码方式中很少被使用,因此,不适合用于编码检测。
对其它多字节编码方式,本方法能得到一个很好的结果,并且很有效。事实上,由于一些编码方法,比如EUC-CN和EUC-KR几乎有完全类似的代码点,使用本方法很难区分将它们区分出来。考虑到浏览器通常不会包含大量文本的事实,我们必须使用其它的方法来检测编码。
对7bit的编码方式,如ISO-2022-xx和HZ来说,由于它们使用了容易识别的转义序列或变换序列,本方法可以得出一个满意的结果。对编码模式方法概括如下,
- 很适合处理7bit的编码方式,如ISO-2022-xx和HZ。
- 适合处理某些多字节编码,如Shift_JIS和EUC-JP,但不适合处理其它的编码,如EUC-CN和EUC-KR。
- 对单字节编码不是很有用。
- 能应用于任何类型的文本。
快速而有效。
5.2.字符分布:
对多字节编码,尤其是那些编码模式方法不能有效处理的多字节编码来说,字符分布方法提供了很大的帮助,同时避免了对复杂上下文进行深入分析的麻烦。对单字节编码,由于输入的数据量通常很少,而且,由于有太多可能的编码方式,除非在特定的情况下,本模式不大可能取得理想的结果。由于双字符序列分布方法对这种情况能获得很好的检测结果,因此,我们没有在单字节编码检测中过多考虑使用本方法。对字符分布方法总结如下
- 很适合处理多字节编码方式。
- 只适用于特定文本。
- 快速而有效。
5.3.双字符序列分布:
在双字符序列分布中,我们可以使用更多的数据信息来检测语言/编码。甚至在只有很少数据样本的情况下,也能得到好的结果。但是由于使用序列代替了单词(通过空格分隔),在处理多字节语言的时候,矩阵将会变得很大。因此,本方法:
- 很适合处理单字节编码。
- 对多字节编码方式来说,不是太适合。
- 在样本大小很小的情况下,也能获得好的结果。
- 只适用于特定文本。
6.复合方法:
6.1.组合三种方法:
在我们的字符集自动检测器所处理的语言/编码中,既有单字节编码,又有多字节编码。基于上面三种方法的定义,单独使用其中的任一种都无法产生满意的结果。因此我们建议使用复合的方法来处理所有这些编码。
双字符序列分布方法可用来检测所有的单字节编码。
编码模式方法可以用在UTF-8,ISO-2022-xx和HZ检测上。在UTF-8检测中,对现有的状态机作了一点修改。UTF-8检测器的成功检测是在对多个多字节序列验证之后作出的。(详见 Martin Duerst's(1977))。编码模式方法和字符分布方法一起作用在了对主要东亚字符编码进行检测上,例如GB2312,Big5,EUC-TW,EUC-KR,Shift_JIS和EUC-JP。
对日语编码,如Shift_JIS和EUC-JP,双字符序列分布方法同样也能用于检测,这是由于它们包含许多具有明显特征的平假名字符,这些平假名字符和单字节语言中的字母很相似。双字符序列分布方法在很少文本的情况下也能得到精确的结果。
我们试验了两种方法,一种带有双字符分布方法,另一种不使用。它们都取得了满意的结果。有一些网站包含许多汉字和片假名字符,但是仅有一些平假名字符。为了尽可能获得最好的结果,我们在日语编码检测上既使用了字符分布方法,又使用了双字符分布方法。
这里有一个这三种检测方法结合在一起使用的例子。最上层的控制模块(对自动检测来说)算法如下:
Charset AutoDetection (InputText)
{
if (all characters in InputText are ASCII)
{
if InputText contains ESC or “~{“
{
call ISO-2022 and HZ detector with InputText;
if one of them succeed, return that charset, otherwise return ASCII;
}
else
return ASCII;
}
else if (InputText start with BOM)
{
return UCS2;
}
else
{
Call all multi-byte detectors and single-byte detectors;
Return the one with best confidence;
}
}
对以上代码片断序列的概括如下,
很大一部分网站仍使用ASCII编码。上层的控制算法从ASCII验证开始。如果所有字符都是ASCII的,除了ISO-2022-xx和HZ编码外,其它检测器就无需使用了。
ISO-2022-xx和HZ检测器在遇到ESC或"~{"时会被载入,并且,在遇到8bit字节的时候,它们会被立即抛弃。
在验证UCS2编码的时候,会搜索BOM是否出现。我们发现有些网站在http流中发送0x00,但是,使用这个字节来验证UCS2编码被证明是不可信的。
如果任一处于激活状态的检测器接收到足够的数据并且达到了很高的可信度,整个自动检测过程将被终止,同时字符编码会作为结果返回。我们称之为快捷模式。
6.2.测试结果:
作为本文中极力推荐方法的测试,我们将我们的检测器应用在了100个流行的,但是没有基于文档或服务器发送HTTP字符集信息的国际网站。对包含在我们检测器中的全部编码方式来说,我们可以或得100%的准确率。
例如,当我们访问一个没有提供字符集信息的网站(例如,在http://www.yahoo.co.jp上的网站的服务器没有发送字符信息前),我们的的字符检测器生成如下的输出:
[UTF8] is inactive
[SJIS] is inactive
[EUCJP] detector has confidence 0.950000
[GB2312] detector has confidence 0.150852
[EUCKR] is inactive
[Big5] detector has confidence 0.129412
[EUCTW] is inactive
[Windows-1251 ] detector has confidence 0.010000
[KOI8-R] detector has confidence 0.010000
[ISO-8859-5] detector has confidence 0.010000
[x-mac-cyrillic] detector has confidence 0.010000
[IBM866] detector has confidence 0.010000
[IBM855] detector has confidence 0.010000
因此,EUC-JP编码对这个站点来说是最可能的编码方式。
7.结论:
在我们的环境中,利用编码模式,字符分布和双字符序列分布的复合方法来检测语言/编码被证明是非常有效的。我们覆盖了Unicode编码,多字节和单字节编码方式。在我们当前的Internet数字文本中,这些编码方式是很有代表性的。我们有理由相信,通过扩展,这种方法可以覆盖没有包括在这篇论文中的,余下的编码方式。
尽管在当前,在我们的检测中,只有编码信息才是我们所需要的,在大多数的情况下,语言信息同样也能被识别出。事实上,字符分布和双字符分布方法都依赖于不同语言字符集的分布模式。只有在UTF16和UTF8的情况下,编码方式能被识别出,而语言信息仍是未知的。即使是在这种情况下,我们的工作也可通过将来的被扩展以覆盖语言信息的检测。
这里列出的三种检测方法在Netscape 6.1 PR1中已被实现了,并且作为后继版本中的"Detect All"选项。我们希望我们在自动检测上的工作可以使用户从麻烦的对字符编码菜单操作中解脱出来。字符编码菜单(或者其他形式的编码菜单)与其它的Internet客户端的界面元素不同,它们对一般的用户暴露了部分国际化的信息。它的存在折射出了当前的网页加入语言/编码方式后是多么的凌乱。
我们希望通过提供缺省的好的编码和通用自动检测能帮助用户在处理网络事务中可以避免大部分的问题。Web标准正在向使用Unicode作为缺省编码转移,特别的,是在向UTF-8转移。我们期望其在Web中逐渐地被使用。由于使用了自动检测,这种转移可以悄然进行,越来越多的用户在浏览,或是在阅读/发送消息的时候将从面对编码事宜中解放出来。这也是为什么我们提倡互联网客户端要使用好的自动检测方法和好的缺省的编码设置。
8.将来的工作:
我们的自动检是设计用来识别语言的。编码判断是这个识别的副产品。在当前工作中,在单字节实现中,我们用俄语举例。由于它识别语言和这个语言下的编码方式,因此语言数据越多,编码检测的质量越高。
要想加入其它的单字节语言/编码,我们需要每一种语言的,大量的文本采样数据,同时需要对语言的认知/分析有一定的深度。我们当前使用脚本来对一种语言的所有编码生成一种语言模式。
当前,我们的工作还没有在Mozilla源代码中体现出来,但我们希望在不远的将来我们的工作会变得公开。我们希望有人在这个领域内做出贡献。因为我们还没有对许多单字节编码做过测试,我们希望在处理其它语言/编码的时候,我们在这里提出的模式可以得到更好的调整,修改甚至是重新设计。
9.引用
Duerst, Martin. 1977. The Properties and Promizes of UTF-8. 11th Unicode Conference.
http://www.ifi.unizh.ch/groups/mml/people/mduerst/papers/IUC11-UTF-8.pdf
Mandarin Promotion Council, Taiwan. Annual survey results of Traditional Chinese character usage.
http://www.edu.tw/mandr/result/87news/index1.htm
Mozilla Internationalization Projects. http://www.mozilla.org/projects/intl
Mozilla.org. http://www.mozilla.org/
Mozilla source viewing. http://lxr.mozilla.org/
版权所有 1998-2003 The Mozilla Organization
最后修改 November 26, 2002
Document History
2003年10月01日
C语言问题一则
首先,来看如下一段程序
char* xyzstring = "hello,world!";
int main(void)
{
xyzstring[0] = 'a';
printf("%s/n",xyzstring);
return 0;
}
这段程序有问题吗?有,xyzstring的数据应该是静态数据,不能被修改。对,C语言的标准上也是这样说的。
果然是这样吗,我们做一个测试,使用四种编译器来看看实际运行的情况。
Codewarrior 8.0、Visual C++ 6.0、Borland C++ 5.6编译后运行都没有问题,都打出了"aello,world!",GCC 3.2编译后运行程序死掉。
为什么会出现这种情况,答案很简单,GCC将"hello,world!"放到了.text段中,而其它三种编译器则是将"hello,world!"到了.data段中。程序中,.text段是用来存放代码的,因此不能被修改,所以,当我们用xyzstring[0]来修改其中内容的时候,程序当然会当掉了。而.data段因为是数据段,是可以被修改的,因此程序不会当掉。
下面为了验证以上说法,我们使用GCC将源代码汇编成test.s
运行如下命令gcc -S test.c
.file "test.c"
.globl _xyzstring
.text
LC0:
.ascii "hello,world!/0"
.data
.align 4
_xyzstring:
.long LC0
.def ___main; .scl 2; .type 32; .endef
.text
LC1:
.ascii "%s/12/0"
.align 2
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
call __alloca
call ___main
movl _xyzstring, %eax
movb $97, (%eax)
subl $8, %esp
pushl _xyzstring
pushl $LC1
call _printf
addl $16, %esp
movl $0, %eax
leave
ret
.def _printf; .scl 2; .type 32; .endef
以上是输出结果,我们看到test.s的头两行是
.globl _xyzstring
.text
说明_xyzstring是定义在了.text段中,现在,我们将其改称如下
.globl _xyzstring
.data
然后编译gcc -o test.exe test.s,接着执行test.exe,出现了什么,哈哈"aello,world!",执行没有问题了。
以上就是不同编译器对这种类型代码的解决方法,但是为了兼容,我们绝对不应该写出上面的代码。