风声竹影's profile听风竹轩的书架BlogListsNetwork Tools Help

风声竹影

听风竹轩的书架

July 13

编程语言排行杂谈

今天看到csdn的《09年2月编程语言排行榜:商业编程语言的王者之争》,明白自己掌握的开发语言简直就是凤毛麟角。不过很明白的看到,java依然是用的最多编程语言。C依然很强大。
另外,有blog把开发语言比作信仰,很有意思:如下:
原文来自If programming languages were religions,很有意思,可以从宗教的角度来看看各种常见语言的特点。(这里丝毫没有要找出不同语言优劣的意思,每个人都有信仰自由)
C是犹太教——很古老而且戒律很多,但大多数人都熟悉并尊重其戒律。问题是很难皈依它,你要么开始就信仰它,要么会认为它简直太疯狂了。而且,一旦事情出了差错,人们就会怪罪于它。
Java是正统基督教——理论上来说它基于C,但它去掉了很多老的戒律,以至于跟原教旨已经大相径庭。另外,它新加了一套严格的教义,追随者们相信这些比原来的教义更为重要。他们认为这是世界上最好的语言,而且会将所有的异端烧死在刑柱上。
PHP是Cafeteria基督教——与Java在Web开发领域进行竞争。它引入了C和Java的一些概念,但只限于它所喜欢的。也许它不像其它语言那样条理清楚,但至少给你了更多自由,看起来还算一个整体。而且不会有下地狱(goto hell)这样的说法。
C++是伊斯兰教——来源于C,不仅保持了后者的戒律,还变本加厉加入了一套新的复杂戒律。它是如此地多才多艺,可用于构建任何事物,从弥天大罪到美丽的艺术品,追随者们相信它才是终极语言,并会被持不同意见的人激怒。更可怕的是,如果你冒犯了它或者是它的创始人,你的生命都会受到威胁。
C#是摩门教——乍看之下,它跟Java一样,不过仔细一看就会发现它被单一组织所控制(很多Java的追随者将其视为恶魔),而且很多概念都不相同。你在暗忖,它也许不错吧,呃。。。希望你不会被Java的追随者们歧视。
Lisp是佛教——没有语法,没有核心教义,也没有让人膜拜的神。就看你的造化了——需要你有足够的悟性。有人说它压根儿就不是一门语言,有的人却认为它是唯一有意义的语言。
Haskell是道教——与其它语言差别太大,以至于很多人怀疑它到底能做什么。其追随者相信它才是通往智慧的正道,只是这些智慧为大多数世俗之人力所不及。
Erlang是印度教——又一种奇怪的语言,看起来没什么用处,但不像大多数其它的现代语言,它建立在“同步之神”的基础上。
Perl是巫毒教——一套神秘的、难以理解的咒语,会让你的灵魂永久地堕落。老板在周五晚上九点吩咐你一项紧急任务,这时就得靠它了。(想起兽族部落的巫毒商店,而且它与萨满教有些类似)
Lua是巫术——一种泛神论的语言,容易与不同文化和地区相融合。它的代码非常自由,其中的一些技术在传统语言的用户看来是不可思议的。它与月亮有紧密的联系。
Ruby是新异教主义——不同语言和理念的混合体,可以被看作是一种语言。其支持者群体成长迅速,尽管很多人面带疑色地看着他们,但基本上他们还是善良的,无意于伤害他人。
Python是人文主义——简单、自由,它只需要你具备常识。很多追随者声称,Python帮助他们卸下了其它语言带来的担子,从而再次找到编程的乐趣。有些人还说它就是一种伪代码。
COBOL是古异教主义——很久以前,它曾经统治着大片地区,现在却差不多死掉了。尽管很多人都在其神的仪式中留下了伤疤,直到今天仍然有人追随着它。
APL是山达基教——尽管有很多人声称要追随它,但你总会怀疑它是一个大大的恶作剧。
LOLCODE是阿里乌斯教——它很神秘,产生于互联网,尽管有人在努力地开发和散布,但没人会对它当真。
Visual Basic是撒旦教——可惜你并不是真的需要将灵魂出卖。。。
最后,说说我的信仰吧:开始是撒旦教,发现它不太强势,于是找到摩门教,感觉很好,但发现它被单一组织控制,很不爽!后进了正统基督教,从此忠实地追随,不过偶尔会偷偷地跑到摩门教和Cafeteria基督教那边去看看。
 
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wdhSoft/archive/2009/02/21/3918892.aspx
April 04

Java运行时多态性

  运行时多态性是面向对象程序设计代码重用的一个最强大机制,动态性的概念也可以被说成“一个接口,多个方法”。Java实现运行时多态性的基础是动态 方法调度,它是一种在运行时而不是在编译期调用重载方法的机制,下面就继承和接口实现两方面谈谈java运行时多态性的实现。

  一、通过继承中超类对象引用变量引用子类对象来实现

  举例说明:

//定义超类superA
class superA
{
 int i = 100;
 void fun()
 {
  System.out.println(“This is superA”);
 }
}
//定义superA的子类subB
class subB extends superA
{
 int m = 1;
 void fun()
 {
  System.out.println(“This is subB”);
 }
}
//定义superA的子类subC
class subC extends superA
{
 int n = 1;
 void fun()
 {
  System.out.println(“This is subC”);
 }
}

class Test
{
 public static void main(String[] args)
 {
  superA a;
  subB b = new subB();
  subC c = new subC();
  a=b;
  a.fun(); (1)
  a=c;
  a.fun(); (2)
 }
}

  运行结果为:

This is subB
This is subC

   上述代码中subB和subC是超类superA的子类,我们在类Test中声明了3个引用变量a, b, c,通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。也许有人会问:“为什么(1)和(2)不输出:This is superA”。java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是 在超类中定义过的,也就是说被子类覆盖的方法。

  所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于 (1)中的a被b赋值,指向了子类subB的一个实例,因而(1)所调用的fun()实际上是子类subB的成员方法fun(),它覆盖了超类 superA的成员方法fun();同样(2)调用的是子类subC的成员方法fun()。

  另外,如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化,但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。具体的实现方法同上例。

  不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,否则子类必须被abstract修饰符修饰,当然也就不能被实例化了。
  二、通过接口类型变量引用实现接口的类的对象来实现

  接口的灵活性就在于“规定一个类必须做什么,而不管你如何做”。我们可以定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上述的超类对象引用访问子类对象的机制相似。

  举例说明:

//定义接口InterA
interface InterA
{
 void fun();
}
//实现接口InterA的类B
class B implements InterA
{
 public void fun()
 {
  System.out.println(“This is B”);
 }
}

//实现接口InterA的类C
class C implements InterA
{
 public void fun()
 {
  System.out.println(“This is C”);
 }
}

class Test
{
 public static void main(String[] args)
 {
  InterA a;
  a= new B();
  a.fun();
  a = new C();
  a.fun();
 }
}

  输出结果为:

This is B
This is C

  上例中类B和类C是实现接口InterA的两个类,分别实现了接口的方法fun(),通过将类B和类C的实例赋给接口引用a而实现了方法在运行时的动态绑定,充分利用了“一个接口,多个方法”展示了Java的动态多态性。

  需要注意的一点是:Java在利用接口变量调用其实现类的对象的方法时,该方法必须已经在接口中被声明,而且在接口的实现类中该实现方法的类型和参数必须与接口中所定义的精确匹配。

  结束语

  以上就是java运行时多态性的实现方法,大家在编程过程中可以灵活运用,但是在性能要求较高的代码中不提倡运用运行时多态,毕竟Java的运行时动态方法调用较之普通的方法调用的系统开销是比较大的。
March 23

arm汇编语言调用C函数之参数传递

arm汇编语言调用C函数之参数传递


对于ARM体系来说,不同语言撰写的函数之间相互调用(mix calls)遵循的是 ATPCS(ARM-Thumb Procedure Call Standard),ATPCS主要是定义了函数呼叫时参数的传递规则以及如何从函数返回,关于ATPCS的详细内容可以查看ADS1.2 Online Books ——Developer Guide的2.1节。这篇文档要讲的是汇编代码中对C函数调用时如何进行参数的传递以及如何从C函数正确返回。
   不同于x86的参数传递规则,ATPCS建议函数的形参不超过4个,如果形参个数少于或等于4,则形参由R0,R1,R2,R3四个寄存器进行传递;若形参个数大于4,大于4的部分必须通过堆栈进行传递。
   我们先讨论一下形参个数为4的情况.

实例1:
test_asm_args.asm
//--------------------------------------------------------------------------------
        IMPORT test_c_args ;声明test_c_args函数
        AREA TEST_ASM, CODE, READONLY
        EXPORT test_asm_args
test_asm_args
        STR lr, [sp, #-4]!
;保存当前lr
        ldr r0,=0x10      
;参数 1
        ldr r1,=0x20      
;参数 2
        ldr r2,=0x30      
;参数 3
        ldr r3,=0x40       ;参数 4
        bl test_c_args    
;调用C函数
        LDR pc, [sp], #4  
;将lr装进pc(返回main函数)
        END
test_c_args.c
//--------------------------------------------------------------------------------
void test_c_args(int a,int b,int c,int d)
{
        printk("test_c_args:\n");
        printk("%0x %0x %0x %0x\n",a,b,c,d);
}
main.c
//--------------------------------------------------------------------------------
int main()
{
     test_asm_args();
     for(;;);
}

   程序从main函数开始执行,main调用了test_asm_args,test_asm_args调用了test_c_args,最后从 test_asm_args返回main。代码分别使用了汇编和C定义了两个函数,test_asm_args 和 test_c_args,test_asm_args调用了test_c_args,其参数的传递方式就是向R0~R3分别写入参数值,之后使用bl语句 对test_c_args进行调用。其中值得注意的地方是用红色标记的语句,test_asm_args在调用test_c_args之前必须把当前的 lr入栈,调用完test_c_args之后再把刚才保存在栈中的lr写回pc,这样才能返回到main函数中。
   如果test_c_args的参数是8个呢?这种情况test_asm_args应该怎样传递参数呢?
实例2:
test_asm_args.asm
//--------------------------------------------------------------------------------
        IMPORT test_c_args ;声明test_c_args函数
        AREA TEST_ASM, CODE, READONLY
        EXPORT test_asm_args
test_asm_args
       STR lr, [sp, #-4]!
;保存当前lr
       ldr r0,=0x1 ;参数 1
       ldr r1,=0x2 ;参数 2
       ldr r2,=0x3 ;参数 3
       ldr r3,=0x4 ;参数 4
      
ldr r4,=0x8
       str r4,[sp,#-4]! ;参数 8 入栈
       ldr r4,=0x7
       str r4,[sp,#-4]! ;参数 7 入栈
       ldr r4,=0x6
       str r4,[sp,#-4]! ;参数 6 入栈
       ldr r4,=0x5
       str r4,[sp,#-4]! ;参数 5 入栈
       bl test_c_args_lots
       ADD sp, sp, #4     ;清除栈中参数 5,本语句执行完后sp指向 参数6
       ADD sp, sp, #4     ;清除栈中参数 6,本语句执行完后sp指向 参数7
       ADD sp, sp, #4     ;清除栈中参数 7,本语句执行完后sp指向 参数8
       ADD sp, sp, #4     ;清除栈中参数 8,本语句执行完后sp指向 lr
       LDR pc, [sp],#4   
;将lr装进pc(返回main函数)
       END
test_c_args.c
//--------------------------------------------------------------------------------
void test_c_args(int a,int b,int c,int d,int e,int f,int g,int h)
{
       printk("test_c_args_lots:\n");
       printk("%0x %0x %0x %0x %0x %0x %0x %0x\n",
              a,b,c,d,e,f,g,h);
}
main.c
//--------------------------------------------------------------------------------
int main()
{
     test_asm_args();
     for(;;);
}

这部分的代码和实例1的代码大部分是相同的,不同的地方是test_c_args的参数个数和test_asm_args的参数传递方式。
在test_asm_args中,参数1~参数4还是通过R0~R3进行传递,而参数5~参数8则是通过把其压入堆栈的方式进行传递,不过要注意这四个入栈参数的入栈顺序,是以参数8->参数7->参数6->参数5的顺序入栈的。
直到调用test_c_args之前,堆栈内容如下:
sp->+----------+
        |  参数5  |
       +----------+
        |  参数6  |
       +----------+
        |  参数7  |
       +----------+
        |  参数8  |
       +----------+
        |   lr   |
       +----------+
test_c_args执行返回后,则设置sp,对之前入栈的参数进行清除,最后将lr装入pc返回main函数,在执行 LDR pc, [sp],#4 指令之前堆栈内容如下:
       +----------+
        |  参数5  |
       +----------+
        |  参数6  |
       +----------+
        |  参数7  |
       +----------+
        |  参数8  |
sp->+----------+
        |   lr   |
       +----------+

 原文地址 http://blog.chinaunix.net/u2/70832/showart_1019133.html
February 12

void指针类型

1.概述
许多初学者对C/C 语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误。本文将对void关键字的深刻含义进行解说,并

详述void及void指针类型的使用方法与技巧。

2.void的含义
void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。

void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变量,让我们试着来定义:


void a;

这行语句编译时会出错,提示“illegal use of type 'void'”。不过,即使void a的编译不会出错,它也没有任何实际意义。

void真正发挥的作用在于:
(1) 对函数返回的限定;
(2) 对函数参数的限定。

我们将在第三节对以上二点进行具体说明。

众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型

转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。

例如:
float *p1;
int *p2;
p1 = p2;

其中p1 = p2语句会编译出错,提示“'=' : cannot convert from 'int *' to 'float *'”,必须改为:
p1 = (float *)p2;
而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换:
void *p1;
int *p2;
p1 = p2;

但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针。因为“无类型”可以包容“有类型”,而“有类型”则不能包

容“无类型”。道理很简单,我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”。下面的语句编译出错:
void *p1;
int *p2;
p2 = p1;

提示“'=' : cannot convert from 'void *' to 'int *'”。

3.void的使用

下面给出void关键字的使用规则:
规则一 如果函数没有返回值,那么应声明为void类型

在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void类型。例如:
add ( int a, int b )
{
return a b;
}
int main(int argc, char* argv[])
{
printf ( "2 3 = %d", add ( 2, 3) );
}

程序运行的结果为输出:
2 3 = 5
这说明不加返回值说明的函数的确为int函数。

林锐博士《高质量C/C 编程》中提到:“C 语言有很严格的类型安全检查,不允许上述情况(指函数不加类型声明)发生”。可是编译

器并不一定这么认定,譬如在Visual C 6.0中上述add函数的编译无错也无警告且运行正确,所以不能寄希望于编译器会做严格的类型检查。

因此,为了避免混乱,我们在编写C/C 程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为void类

型。这既是程序良好可读性的需要,也是编程规范性的要求。另外,加上void类型声明后,也可以发挥代码的“自注释”作用。代码的“自注

释”即代码能自己注释自己。

规则二如果函数无参数,那么应声明其参数为void

在C 语言中声明一个这样的函数:
int function(void)
{
return 1;
}

则进行下面的调用是不合法的:
function(2);

因为在C 中,函数参数为void的意思是这个函数不接受任何参数。

我们在Turbo C 2.0中编译:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}

编译正确且输出1,这说明,在C语言中,可以给无参数的函数传送任意类型的参数,但是在C 编译器中编译同样的代码则会出错。在C

中,不能向无参数的函数传送任何参数,出错提示“'fun' : function does not take 1 parameters”。

所以,无论在C还是C 中,若函数不接受任何参数,一定要指明参数为void。

规则三 小心使用void指针类型

按照ANSI(American National Standards Institute)标准,不能对void指针进行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid ; //ANSI:错误
pvoid = 1; //ANSI:错误
//ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。
//例如:
int *pint;
pint ; //ANSI:正确

pint 的结果是使其增大sizeof(int)。

但是大名鼎鼎的GNU(GNU's Not Unix的缩写)则不这么认定,它指定void *的算法操作与char *一致。

因此下列语句在GNU编译器中皆正确:
pvoid ; //GNU:正确
pvoid = 1; //GNU:正确

pvoid 的执行结果是其增大了1。

在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void * pvoid;
(char *)pvoid ; //ANSI:正确;GNU:正确
(char *)pvoid = 1; //ANSI:错误;GNU:正确

GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合

ANSI标准。

规则四如果函数的参数可以是任意类型指针,那么应声明其参数为void *

典型的如内存操作函数memcpy和memset的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );

这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不

论这片内存是什么类型。如果memcpy和memset的参数类型不是void *,而是char *,那才叫真的奇怪了!这样的memcpy和memset明显不是一个

“纯粹的,脱离低级趣味的”函数!

下面的代码执行正确:
//示例:memset接受任意类型指针
int intarray[100];
memset ( intarray, 0, 100*sizeof(int) ); //将intarray清0

//示例:memcpy接受任意类型指针
int intarray1[100], intarray2[100];
memcpy ( intarray1, intarray2, 100*sizeof(int) ); //将intarray2拷贝给intarray1

有趣的是,memcpy和memset函数返回的也是void *类型,标准库函数的编写者是多么地富有学问啊!

规则五 void不能代表一个真实的变量

下面代码都企图让void代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误

void体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(还有人妖?)。

void的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解void数据类型。正如不能给抽

象基类定义一个实例,我们也不能定义一个void(让我们类比的称void为“抽象数据类型”)变量。
February 05

C语言参考手册阅读笔记:预处理指令之#,##,可变参数收藏

---------------------------------------------------------------
 3.3.8 将记号转换为字符串 [C语言参考手册,p38(r54)]
---------------------------------------------------------------
    标准C语言中有一种机制可以将宏参数(扩展之后)转换为字符串型常量。在此之前,编程人员要利用许多C语言预处理器中的漏洞以不同方式达到相同结果。
    在标准C语言中,宏定义中出现的#记号被当作一元“字符串化”运算符,后面为宏正式参数名。宏扩展期间,#和正式参数名换成相应的包含在字符串引号当中的 实际参数。生成字符串时,记号参数表中的每个空白序列换成一个空格符,任何嵌人引号和反斜杠前面加上一个反斜杠以保留其在字符串中的含义。参数开头和末尾 的空白符忽略,因此空参数扩展为空字符串""(即使逗号之间有空白符)。

例:考虑TEST宏的标准c语言定义

#define TEST(a,b) printf(#a "<" #b "=%d\n", (a)<(b))
语句TEST(0,0xFFFF);TEST('\n', 10);展开如下:
printf("0" "<" "0xFFFF" "=%d\n", (0)<(0xFFFF));
printf("'\\n'" "<" "10 "=%d\n", ('\n')<(10));

---------------------------------------------------------------
 3.3.9 宏扩展中的记号合并 [C语言参考手册,p38(r54)]
---------------------------------------------------------------
    标准C语言中合并记号形成新记号时,由宏定义中的合并运算符##控制。重新扫描更多宏之前,宏替换表中任何运算符##中间的两个记号合并成一个记号。因此必须有这种记号:##不能放在替换表开头或末尾。如果合并之后得不到有效记号,则结果是未定义的。

例子:
#define TEMP(i) temp ## i
TEMP(1) = TEMP(2 + k) + x;
预处理之后变成:
temp1 = temp2 + k + x;

    上例中,扩展TEMP()+x时可能出现一个奇妙的情形。宏定义是有效的,但##右边没有要组合的记号(除非遇到+,但这不是我们所要的)。要解决这个问 题,应把正式参数i看成是专为##扩展的特殊的“空”记号。这样,TEMP()+x扩展的结果将如我们预料的那样为temp+x。
    不能用记号拼接产生通用字符名。
    和宏参数转换成字符串时一样(见3.3.8节),编程人员可以利用许多非标准C语言实现中的漏洞获得这种合并功能。尽管C语言的原始定义明确地把宏体描述 成记号序列,而不是字符序列,但许多语言编译器把宏体当作字符序列一样扩展和重新扫描。这在编译器处理注释语句时更加明显,编译器将注释语句完全删除而不 是换成空格,因此一些巧妙编写的程序就可以利用这个特性。
例:考虑下列例子:
#define INC        ++
#define TAB        internal_table
#define INCTAB    table_of_increments
#define CONC(x,y)    x/**/y
CONC(INC,TAB)

    标准C语言将CONC体解释为两个记号x和y,用空格分开(注释语句变成空格)。调用CONC(INC,TAB)扩展为两个记号INC TAB。但有些非标准C语言实现直接删除注释语句,然后重新扫描宏体中的记号,这样就把CONC(INC,TAB)扩展为一个记号INCTAB;

 -----------------------------------------------
 步骤    标准C语言扩展        可能的非标准扩展
 -----------------------------------------------
 1    CONC(INC,TAB)        CONC(INC,TAB)
 2    INC/**/TAB                  INC/**/TAB
 3    INC TAB                      INCTAB
 4    ++ internal_table      table_of_increments


测试代码:

#define XVARNAME(i)        x##i
#define PXVARNAME(n)    printf("x"#n" = %d\n",x##n)

int k = 0;
int b = 0;
int XVARNAME(k)=12;
    PXVARNAME(k + 1);                         //xk + 1 = 13
    PXVARNAME(k);                                //xk = 12
    b = XVARNAME(k) + 0;
    printf("b = %d, k =  %d\r\n", b, k);    //b = 12, k = 0
    b = XVARNAME(k + 10) + 0;
    printf("b = %d, k =  %d\r\n", b, k);    //b = 22, k = 0


---------------------------------------------------------------
 3.3.10  宏中的可变参数表 [C语言参考手册,p38(r54)]
 notice: Visual C++ 直至8.0版才开始支持C99的可变宏定义
---------------------------------------------------------------
#ifdef DEBUG
#define myprintf(...) fprintf(stderr, __VA_ARGS__)
#else
#define myprintf(...) fprintf(__VA_ARGS__)
#endif

特别地:
#define make_em_str(...) #__VA_ARGS__
下列调用
make_em_str(a,b,c,d)
展开成字符串
"a,b,c,d"
February 01

memcpy和memmove的区别与实现

区别:
从DESCRIPTION看来,两者的功能基本相同,唯一不同的是,当 dest 和 src 有重叠的时候选用不同的函数可能会造成不同的结果。不妨写个小程序来测一下:
0 #i nclude <string.h>
1 #i nclude <stdio.h>
2
3 int main()
4 {
5    int i = 0;
6    int a[10];
7
8    for(i; i < 10; i++)
9    {
10        a[i] = i;
11   }
12
13   memcpy(&a[4], a, sizeof(int)*6);
14
15   for(i = 0; i < 10; i++)
16   {
17       printf("%d ",a[i]);
18   }
20
21    printf("\n");
22    return 0;
23 }
很简单的小程序!不过已经足以达到我的目的了:)将上面代码gcc之后再运行,结果为:0 1 2 3 0 1 2 3 0 1 。
再把第13行改成:memmove(&a[4], a, sizeof(int)*6),重新gcc再运行,结果为:0 1 2 3 0 1 2 3 4 5 !
呵呵,两者的区别出现了。不过其实这样还不够,继续修改13行: memmove(a, &a[4], sizeof(int)*6) //也就是将源、目的置换一下而已
重新gcc编译再运行,结果为:4 5 6 7 8 9 6 7 8 9 。
还不够,继续修改13行为: memcpy(a, &a[4], sizeof(int)*6); gcc并运行,结果仍为: 4 5 6 7 8 9 6 7 8 9 !
至此真相已经大白了。对比上面四个结果,不难得出以下结论:
1. 当 src 和 dest 所指内存区有重叠时,memmove 相对 memcpy 能提供保证:保证能将 src 所指内存区的前 n 个字节正确的拷贝到 dest 所指内存中;
2. 当 src 地址比 dest 地址低时,两者结果一样。换句话说,memmove 与 memcpy 的区别仅仅体现在 dest 的头部和 src 的尾部有重叠的情况下;
memcpy


代码:
;***
;memcpy.asm - contains memcpy and memmove routines
;
;       Copyright (c) 1986-1997, Microsoft Corporation. All right reserved.
;
;Purpose:
;       memcpy() copies a source memory buffer to a destination buffer.
;       Overlapping buffers are not treated specially, so propogation may occur.
;       memmove() copies a source memory buffer to a destination buffer.
;       Overlapping buffers are treated specially, to avoid propogation.
;
;*******************************************************************************
;***
;memcpy - Copy source buffer to destination buffer
;
;Purpose:
;       memcpy() copies a source memory buffer to a destination memory buffer.
;       This routine does NOT recognize overlapping buffers, and thus can lead
;       to propogation.
;       For cases where propogation must be avoided, memmove() must be used.
;
;       Algorithm:

       void* memcpy(void* dest, void* source, size_t count)

      {

           void* ret = dest;

          //copy from lower address to higher address

          while (count--)

                  *dest++ = *source;


           return ret;

      }

memmove

memmove - Copy source buffer to destination buffer
;
;Purpose:
;       memmove() copies a source memory buffer to a destination memory buffer.
;       This routine recognize overlapping buffers to avoid propogation.
;       For cases where propogation is not a problem, memcpy() can be used.
;
;   Algorithm:

    void* memmove(void* dest, void* source, size_t count)

   {

       void* ret = dest;


      if (dest <= source || dest >= (source + count))

       {

          //Non-Overlapping Buffers
         //copy from lower addresses to higher addresses
    

         while (count --)

               *dest++ = *source++;

     }

     else

     {

        //Overlapping Buffers
       //copy from higher addresses to lower addresses


       dest += count - 1;

       source += count - 1;

       while (count--)

                *dest-- = *source--;l

     }

      return ret;

   }


另一种实现:

void* mymemcpy( void* dest, const void* src, size_t count )
{
     char* d = (char*)dest;
     const char* s = (const char*)src;
   //   int n = (count + 7) / 8; // count > 0 assumed
     int n = count >> 3;
     switch( count & 7 )
     {
               do {   *d++ = *s++;
     case 7:         *d++ = *s++;
     case 6:         *d++ = *s++;
     case 5:         *d++ = *s++;
     case 4:         *d++ = *s++;
     case 3:         *d++ = *s++;
     case 2:         *d++ = *s++;
     case 1:         *d++ = *s++;
     case 0           } //while (--n > 0);
                  while (n-- > 0)
     }

     return dest;
}

宽字符处理函数函数与普通函数对照表

字符分类:
iswalnum()  isalnum
()  测试字符是否为数字或字母
iswalpha
()  isalpha()  测试字符是否是字母
iswcntrl
()  iscntrl()  测试字符是否是控制符
iswdigit
()  isdigit()  测试字符是否为数字
iswgraph
()  isgraph()  测试字符是否是可见字符
iswlower
()  islower()  测试字符是否是小写字符
iswprint
()  isprint()  测试字符是否是可打印字符
iswpunct
()  ispunct()  测试字符是否是标点符号
iswspace
()  isspace()  测试字符是否是空白符号
iswupper
()  isupper()  测试字符是否是大写字符
iswxdigit
()  isxdigit()  测试字符是否是十六进制的数字


大小写转换:
towlower
()  tolower()  把字符转换为小写
towupper
()  toupper()  把字符转换为大写


字符比较:
wcscoll
()  strcoll()  比较字符串


日期和时间转换:
strftime
()  根据指定的字符串格式和locale设置格式化日期和时间
wcsftime
()  根据指定的字符串格式和locale设置格式化日期和时间, 并返回宽字符串
strptime
()  根据指定格式把字符串转换为时间值, 是strftime的反过程


打印和扫描字符串:
fprintf
()  /fwprintf()  使用vararg参量的格式化输出
fscanf
()  /fwscanf()  格式化读入
printf
()  使用vararg参量的格式化输出到标准输出
scanf
()  从标准输入的格式化读入
sprintf
()  /swprintf()  根据vararg参量表格式化成字符串
sscanf
()  以字符串作格式化读入
vfprintf
()  /vfwprintf()  使用stdarg参量表格式化输出到文件
vprintf
()  使用stdarg参量表格式化输出到标准输出
vsprintf
()  /vswprintf()  格式化stdarg参量表并写到字符串


数字转换:
wcstod
()  strtod()  把宽字符的初始部分转换为双精度浮点数
wcstol
()  strtol()  把宽字符的初始部分转换为长整数
wcstoul
()  strtoul()  把宽字符的初始部分转换为无符号长整数


多字节字符和宽字符转换及操作:
mblen
()  根据locale的设置确定字符的字节数
mbstowcs
()  把多字节字符串转换为宽字符串
mbtowc
()  /btowc()  把多字节字符转换为宽字符
wcstombs
()  把宽字符串转换为多字节字符串
wctomb
()  /wctob()  把宽字符转换为多字节字符


输入和输出:
fgetwc
()  fgetc()  从流中读入一个字符并转换为宽字符
fgetws
()  fgets()  从流中读入一个字符串并转换为宽字符串
fputwc
()  fputc()  把宽字符转换为多字节字符并且输出到标准输出
fputws
()  fputs()  把宽字符串转换为多字节字符并且输出到标准输出串
getwc
()  getc()  从标准输入中读取字符, 并且转换为宽字符
getwchar
()  getchar()  从标准输入中读取字符, 并且转换为宽字符
None gets
()  使用fgetws() 
putwc
()  putc()  把宽字符转换成多字节字符并且写到标准输出
putwchar
()  putchar()  把宽字符转换成多字节字符并且写到标准输出
None puts
()  使用fputws() 
ungetwc
()  ungetc()  把一个宽字符放回到输入流中


字符串操作:
wcscat
()  strcat()  把一个字符串接到另一个字符串的尾部
wcsncat
()  strncat()  类似于wcscat()  , 而且指定粘接字符串的粘接长度.
wcschr
()  strchr()  查找子字符串的第一个位置
wcsrchr
()  strrchr()  从尾部开始查找子字符串出现的第一个位置
wcspbrk
()  strpbrk()  从一字符字符串中查找另一字符串中任何一个字符第一次出现的位置
wcswcs
()  /wcsstr()  strchr()  在一字符串中查找另一字符串第一次出现的位置
wcscspn
()  strcspn()  返回不包含第二个字符串的的初始数目
wcsspn
()  strspn()  返回包含第二个字符串的初始数目
wcscpy
()  strcpy()  拷贝字符串
wcsncpy
()  strncpy()  类似于wcscpy()  , 同时指定拷贝的数目
wcscmp
()  strcmp()  比较两个宽字符串
wcsncmp
()  strncmp()  类似于wcscmp()  , 还要指定比较字符字符串的数目
wcslen
()  strlen()  获得宽字符串的数目
wcstok
()  strtok()  根据标示符把宽字符串分解成一系列字符串
wcswidth
()  None 获得宽字符串的宽度
wcwidth
()  None 获得宽字符的宽度


另外还有对应于memory操作的 wmemcpy
()  , wmemchr()  , wmemcmp()  ,wmemmove()  , wmemset()