1、C/C++函数修饰名:

对于我们的C/C++源程序而言,函数名只是函数的一小部分,函数还有调用方式(参数入栈方式)、返回值类型、参数个数和各参数类型等信息,对于C++类成员函数,还有更多信息。这些信息按照约定格式有序排列,程序员阅读起来非常方便。但对编译器和链接器等工具而言,把这些信息集中到一个字符串中更符合它们的“阅读”习惯,所以函数修饰名(Decorated Name)应运而生了。函数修饰名是在编译函数定义或原型期间生成的,用于编译工具内部的函数识别、定位。程序员一般情况下不需要接触函数修饰名,但在一些特殊形况下,我们还是要直接接触到函数修饰名,譬如链接过程中的排错;在汇编程序中嵌入C/C++函数。

2、C/C++函数修饰名规则:

2.1. C编译器的函数名修饰规则

c语言不支持重载,所以不会出现函数名相同,而返回值类型、参数个数和参数类型不同的情况。也就是说函数修饰名只需添加调用方式(参数入栈方式)即可。对于常见的三种调用方式:

  • __stdcall调用约定:编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数,例如_functionname@number。
  • __cdecl调用约定:仅在输出函数名前加上一个下划线前缀,例如_functionname。
  • __fastcall调用约定:在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,例如@functionname@number

2.2. C++编译器的函数名修饰规则

因为C++支持函数重载,所以它的函数名修饰规则有些复杂。经过修饰后的C++函数名,不仅携带函数调用方式信息,还携带有返回值类型、参数个数和参数类型信息,可能还有命名空间和类信息。不管__cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”,对于__fastcall方式则是“@@YI”。参数表的拼写代号如下所示:  X--void     D--char     
E--unsigned char     F--short     H--int     
I--unsigned int     J--long     
K--unsigned long(DWORD)  M--float     N--double     _N--bool  U--struct  ....  
指针的方式有些特别,用PA表示指针,用PB表示const类型的指针。后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复。U表示结构

类型,通常后跟结构体的类型名,用“@@”表示结构类型名的结束。函数的返回值不作特殊处理,它的描述方式和函数参数一样,紧跟着参数表的开始标志,也就是说,函数参数表的第一项实际上是表示函数的返回值类型。参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。下面举两个例子,假如有以下函数声明:   
int Function1 (char *var1,unsigned long);  
其函数修饰名为“?Function1@@YG H PA D K@Z”,而对于函数声明:  void Function2();  
其函数修饰名则为“?Function2@@YGXXZ” 。   
    对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和参数表之间插入以“@”字符引导的类名;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。下面就以类CTest为例说明C++成员函数的名字修饰规则:  class CTest  {  ......  private:  
    void Function(int);  protected:  
    void CopyInfo(const CTest &src);  public:  
    long DrawText(HDC hdc, long pos, const TCHAR* text, RGBQUAD color, BYTE bUnder, bool bSet);  
    long InsightClass(DWORD dwClass) const;  ......  };   
对于成员函数Function,其函数修饰名为“?Function@CTest@@AAEXH@Z”,字符串“@@AAE”表示这是一个私有函数。成员函数CopyInfo只有一个参数,是对类CTest的const引用参数,其函数修饰名为“?CopyInfo@CTest@@IAEXABV1@@Z”。DrawText是一个比较复杂的函数声明,不仅有字符串参数,还有结构体参数和HDC句柄参数,需要指出的是HDC实际上是一个HDC__结构类型的指针,这个参数的表示就是“PAUHDC__@@”, 其完整的函数修饰名为 
 “?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z”。InsightClass是一个共有的const函数,它的成员函数标识是“@@QBE”,完整的修饰名就是“?InsightClass@CTest@@QBEJK@Z”。   
无论是C函数名修饰方式还是C++函数名修饰方式均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。

3.查看函数的名字修饰   
    有两种方式可以检查你的程序中的函数的名字修饰:使用编译输出列表或使用Dumpbin工具。使用/FAc,/FAs或/FAcs命令行参数可以让编译器输出函数或变量名字列表。使用dumpbin.exe /SYMBOLS命令也可以获得obj文件或lib文件中的函数或变量名字列表。此外,还可以使用 undname.exe 将修饰名转换为未修饰形式。     
函数调用约定和名字修饰规则不匹配引起的常见问题      函数调用时如果出现堆栈异常,十有八九是由于函数调用约定不匹配引起的。比如动态链接库a有以下导出函数:long MakeFun(long lFun);  
动态库生成的时候采用的函数调用约定是__stdcall,所以编译生成的a.dll中函数MakeFun的调用约定是_stdcall,也就是函数调用时参数从右向左入栈,函数返回时自己还原堆栈。现在某个程序模块b要引用a中的MakeFun,b和a一样使用C++方式编译,只是b模块的函数调用方式是__cdecl,由于b包含了a提供的头文件中MakeFun函数声明,所以MakeFun在b模块中被其它调用MakeFun的函数认为是__cdecl调用方式,b模块中的这些函数在调用完MakeFun当然要帮着恢复堆栈啦,可是MakeFun已经在结束时自己恢复了堆栈,b模块中的函数这样多此一举就引起了栈指针错误,从而引发堆栈异常。宏观上的现象就是函数调用没有问题(因为参数传递顺序是一样的),MakeFun也完成了自己的功能,只是函数返回后引发错误。解决的方法也很简单,只要保证两个模块的在编译时设置相同的函数调用约定就行了。  
   在了解了函数调用约定和函数的名修饰规则之后,再来看在C++程序中使用C语言编译的库时经常出现的LNK 2001错误就很简单了。还以上面例子的两个模块为例,这一次两个模块在编译的时候都采用__stdcall调用约定,但是a.dll使用C语言的语法编译的(C语言方式),所以a.dll的载入库a.lib中MakeFun函数的名字修饰就是“_MakeFun@4”。b包含了a提供的头文件中MakeFun函数声明,但是由于b采用的是C++语言编译,所以MakeFun在b模块中被按照C++的名字修饰规则命名为“?MakeFun@@YGJJ@Z”,编译过程相安无事,链接程序时c++的链接器就到a.lib中去找“?MakeFun@@YGJJ@Z”,但是a.lib中只有“_MakeFun@4”,没有“?MakeFun@@YGJJ@Z”,于是链接器就报告:   
error LNK2001: unresolved external symbol ?MakeFun@@YGJJ@Z   
解决的方法和简单,就是要让b模块知道这个函数是C语言编译的,extern "C"可以做到这一点。一个采用C语言编译的库应该考虑到使用这个库的程序可能是C++程序(使用C++编译器),所以在设计头文件时应该注意这一点。通常应该这样声明头文件:    
#ifdef _cplusplus  extern "C" {  #endif   
long MakeFun(long lFun);

#ifdef _cplusplus  }  
#endif     
这样C++的编译器就知道MakeFun的修饰名是“_MakeFun@4”,就不会有链接错误了。   
    许多人不明白,为什么我使用的编译器都是VC的编译器还会产生“error LNK2001”错误?其实,VC的编译器会根据源文件的扩展名选择编译方式,如果文件的扩展名是“.C”,编译器会采用C的语法编译,如果扩展名是“.cpp”,编译器会使用C++的语法编译程序,所以,最好的方法就是使用extern "C"。   
1.__stdcall  
以“?”标识函数名的开始,后跟函数名; 函数名后面以“@@YG”标识参数表的开始,后跟参数表;  
参数表以代号表示: X--void , D--char, E--unsigned char, F--short, H--int, I--unsigned int, J--long, K--unsigned long, M--float, N--double, _N--bool, .... PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;  
参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;  
参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。 其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”, 例如 int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z” void Test2() -----“?Test2@@YGXXZ”  2 __cdecl调用约定: 规则同上面的 _stdcall 调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。   
3 __fastcall调用约定: 规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。   
VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用。   
CB在输出函数声明时使用4种修饰符号 :   
__cdecl cb 的默认值,它会在输出函数名前加 "_",并保留此函数名不变,参数按照从右到左的顺序依次传递给栈,也可以写成_cdecl和cdecl形式。  
__fastcall 修饰的函数的参数将尽可能的使用寄存器来处理,其函数名前加@,参数按照从左到右的顺序压栈;  
__pascal 它说明的函数名使用 Pascal 格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈;  
__stdcall 使用标准约定的函数名。函数名不会改变。使用 __stdcall 修饰时。参数按照由右到左的顺序压栈,也可以是_stdcall;

语言函数调用约定   
在C语言中,假设我们有这样的一个函数:   
int function(int a,int b)   
调用时只要用 result = function(1,2) 这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就出现来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。   
栈是一种先后进先出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈 (Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈 (pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。   
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。   
在参数传递中,有两个很重要的问题必须得到明确说明:   
当参数个数多于一个时,按照什么顺序把参数压入堆栈  函数调用后,由谁来把堆栈恢复原状  
在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:   
stdcall  cdecl  fastcall  thiscall  naked call   
stdcall调用约定  
stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。   
stdcall调用约定声明的语法为(以前文的那个函数为例):   
int __stdcall function(int a,int b)   
stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自

动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸   
以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:   
push 2      // 第二个参数入栈  push 1      // 第一个参数入栈  
call function // 调用参数,注意此时自动把cs:eip入栈   
而对于函数自身,则可以翻译为:   
push ebp     // 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复  
mov ebp,esp // 保存堆栈指针  
mov eax,[ebp + 8H] // 堆栈中ebp指向位置之前依次保存有 ebp,cs:eip,a,b,ebp + 8指向 a  add eax,[ebp + 0CH] // 堆栈中ebp + 1 2处保存了b  mov esp,ebp        // 恢复esp  pop ebp  ret 8  
而在编译时,这个函数的名字被翻译成_function@8   
注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。  其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。   
从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。  
函数结束后,ret 8 表示清理8个字节的堆栈,函数自己恢复了堆栈。   
cdecl调用约定  
cdecl 调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:   
int function (int a ,int b) //不加修饰就是C调用约定  int __cdecl function(int a,int b) //明确指出C调用约定   
cdecl调用约定的参数压栈顺序是和 stdcall是一样的,参数首先由有向左压入堆栈。  所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。  
由于这种变化,C 调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。  对于前面的function函数,使用cdecl后的汇编码变成:   
调用处  push 1  push 2  
call functionadd  
esp,8   // 注意:这里调用者在恢复堆栈

被调用函数_function处  
push ebp      // 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复  
mov ebp,esp // 保存堆栈指针  
mov eax,[ebp + 8H] // 堆栈中ebp指向位置之前依次保存有 ebp, cs:eip,a,b,ebp +8指向a  add eax,[ebp + 0CH] // 堆栈中ebp + 12处保存了b  mov esp,ebp         // 恢复esp  pop ebp  
   ret         // 注意,这里没有修改堆栈   
MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function,但是我在编译时似乎没有看到这种变化。  由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:   
int sprintf(char* buffer,const char* format,...)   
由于所有的不定参数都可以通过 format 确定,因此使用不定个数的参数是没有问题的。   
fastcall调用约定  
fastcall调用约定和stdcall类似,它意味着:   
函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈  被调用函数清理堆栈  函数名修改规则同stdcall  
其声明语法为:int fastcall function(int a,int b)   
为了说明这个调用约定,定义如下类和使用代码:  class A  {  
public:       
int function1(int a,int b);       int function2(int a,...);  };   
int A::function1 (int a,int b)  {    
return a+b;  }

int A::function2(int a,...)  {     
va_list ap;   
va_start(ap,a);   
int i;    
int result = 0;   
for(i = 0  i < a  i ++)     {        
   result += va_arg(ap,int);     }   
return result;  }   
void callee()  {     A a;     
a.function1 (1,2);    a.function2(3,1,2,3);  }   
// 下面这段汇编代码是原文章的,我觉得有问题,还是自己反汇编看看   
//函数function1调用0401C1D    push        200401C1F    push        100401C21    
lea         ecx,[ebp-8]00401C24    call function1            
// 注意,这里this没有被入栈  //函数function2调用00401C29    push        300401C2B    push        200401C2D    push        100401C2F    push        300401C31    lea         eax,[ebp-8]    
这里引入this指针00401C34    push        eax00401C35    call   function200401C3A    add         esp,14h

以下代码是我修改分析的:   
上面的C++代码,必须包含 stdarg.h ,提供动态参数头文件   
int A::function1 (int a,int b)     //  {    
004113A0 push        ebp  004113A1 mov         ebp,esp  004113A3 sub         esp,0CCh  004113A9 push        ebx  004113AA push        esi  004113AB push        edi  004113AC push        ecx  
004113AD lea         edi,[ebp-0CCh]  004113B3 mov         ecx,33h  
004113B8 mov         eax,0CCCCCCCCh  004113BD rep stos    dword ptr es:[edi]  004113BF pop         ecx  
004113C0 mov         dword ptr [ebp-8],ecx  return a+b;  
004113C3 mov         eax,dword ptr [a]  004113C6 add         eax,dword ptr [b]  }   
004113C9 pop         edi  004113CA pop         esi  004113CB pop         ebx  004113CC mov         esp,ebp  004113CE pop         ebp  004113CF ret         8   
void callee()  {     
00411460 push        ebp  00411461 mov         ebp,esp  00411463 sub         esp,0CCh  00411469 push        ebx  0041146A push        esi  0041146B push        edi  
0041146C lea         edi,[ebp-0CCh]  00411472 mov         ecx,33h  
00411477 mov         eax,0CCCCCCCCh  0041147C rep stos    dword ptr es:[edi]  A a;

a.function1 (1,2);    
0041147E push        2                // 参数 2 入栈  00411480 push        1                 // 参数 1 入栈  00411482 lea         ecx,[a]           // this 指针 ----> ECX  00411485 call        A::function1 (411050h)  a.function2(3,1,2,3);  
0041148A push        3     0041148C push        2     0041148E push        1     00411490 push        3     
00411492 lea         eax,[a]          // 这里 this 指针入栈了,对照 callee 对 function1 的调用,   
00411495 push        eax             // 对 this 的处理是不同的    
00411496 call        A::function2 (411122h)     // 此处调用者自己没有恢复堆栈   
// 由于上面的入栈顺序可知,在 function 2中 当保存ebp 后(打开stack frame后),堆栈的状态如下.  
ebp               // 保存的 EBP 的值, 且 此时ebp指向该处  RetAddr       // 返回地址  
this指针       // 入栈的 this 指针  
参数 3          // 下面是入栈的参数, 从右向左入栈  参数 1  参数 2  参数 3    
0041149B add         esp,14h                            // 此处调用者自己恢复堆栈   
//.............下面的汇编代码是 检查堆栈和恢复 callee 堆栈的操作,不再写了  }   
可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl   
naked call 调用约定  
这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返   
回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:   
__declspec(naked) int add(int a,int b)  {    
   __asm mov eax,a

__asm add eax,b       __asm ret  }   
注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。   
上面代码被翻译成汇编以后变成:   
mov eax,[ebp+8]  add eax,[ebp+12]  ret 8   
注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对于和stdcall结合的代码,则变成:   
__declspec(naked) int __stdcall function(int a,int b)  {     
__asm mov eax,a     __asm add eax,b     
__asm ret 8        //注意后面的8  }  
至于这种函数被调用,则和普通的cdecl及stdcall调用函数一致。    
函数调用约定导致的常见问题  
如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:  
函数原型声明和函数体定义不一致  
DLL导入函数时声明了不同的函数约定  
以后者为例,假设我们在dll种声明了一种函数为:   
__declspec(dllexport) int func(int a,int b);//注意,这里没有stdcall,使用的是cdecl  使用时代码为:   
typedef int (*WINAPI DLLFUNC)func(int a,int b);       hLib = LoadLibrary(...);       
DLLFUNC func = (DLLFUNC)GetProcAddress(...) //这里修改了调用约定       result = func(1,2);       //导致错误  
由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破坏,  
MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏了

[转][C/C++]函数名字修饰(Decorated Name)方式的更多相关文章

  1. C/C++函数调用的几种方式及函数名修饰规则以及c++为什么不允许重载仅返回类型不同的函数

    我们知道,调用函数时,计算机常用栈来存放函数执行需要的参数,由于栈的空间大小是有限的,在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域.这句话的意思是栈顶的地址和栈的最大容量是系统预 ...

  2. C++编译器的函数名修饰规则

    我们知道在C++中有函数重载这样一个东西,当我们定义了几个功能类似且函数名是一样的函数的时候,只要它的参数列表不同,编译是可以通过的,但是在C中是不可以的. double add(double a, ...

  3. C++编译时函数名修饰约定规则(很具体),MFC提供的宏,extern "C"的作用

    调用约定: __cdecl __fastcall与 __stdcall,三者都是调用约定(Calling convention),它决定以下内容:1)函数参数的压栈顺序,2)由调用者还是被调用者把参数 ...

  4. 名字修饰约定extern "C"与extern "C++"浅析

    所谓名字修饰约定,就是指变量名.函数名等经过编译后重新输出名称的规则. 比如源代码中函数名称为int Func(int a,int b),经过编译后名称可能为?Func@@YAHHH@Z.?Func@ ...

  5. JS调用函数时候加括号与只写函数名字的区别 fn与fn()的区别

    经常见插件里面函数调用的时候只写个函数名字,不写函数参数,甚至连括号也不写,比如说: <!DOCTYPE html> <html> <head> <meta ...

  6. const关键字对C++成员函数的修饰

    const对C++成员函数的修饰分为三种:1. 修饰参数:2. 修饰返回值:3. 修饰this指针.简述一下知识点如下,以后找功夫再完善. 1. 对函数参数的修饰. 1)const只能用来修饰输入参数 ...

  7. 精读JavaScript模式(四),数组,对象与函数的几种创建方式

    一.前言 放了个元旦,休息了三天,加上春运抢票一系列事情的冲击,我感觉我的心已经飞了.确实应该收收心,之前计划的学习任务也严重脱节了:我恨不得打死我自己. 在上篇博客中,笔记记录到了关于构造函数方面的 ...

  8. Python函数定义及传参方式

    主要内容:     1.函数初识     2.函数传参方式        (1)位置参数        (2)默认参数        (3)关键参数        (4)非固定传参 一.函数初识 1. ...

  9. JavaScript 函数的两种声明方式

    1.函数声明的方式 JavaScript声明函数有两种选择:函数声明法,表达式定义法. 函数声明法 function sum (num1 ,num2){ return num1+num2 } 表达式定 ...

随机推荐

  1. python语法笔记(二)

    1. 循环对象 循环对象是一类特殊的对象,它包含一个next()方法(在python3中是 __next__()方法),该方法的目的是进行到下一个结果,而在结束一系列结果之后,举出 StopItera ...

  2. VIm vi 使用 汇总

    x 删除当前光标下的字符dw 删除光标之后的单词剩余部分.d$ 删除光标之后的该行剩余部分.dd 删除当前行. c 功能和d相同,区别在于完成删除操作后进入INSERT MODEcc 也是删除当前行, ...

  3. PHP与Ajax的交互更新页面

    PHP与Ajax的交互更新页面 本次主要学习ajax的概念以及怎么与PHP之间进行交互操作 1.什么是Ajax?    国内翻译常为“阿贾克斯”和阿贾克斯足球队同音,AJAX 是一种用于创建快速动态网 ...

  4. Canu Quick Start(快速使用Canu)

    Canu Quick Start Canu Quick Start PBcR (老版的canu) CA Canu specializes in(专门从事) assembling PacBio or O ...

  5. u盘安装ubuntu server 14.04 以及No CD-ROM drive was detected 错误

    u盘安装ubuntu server 14.04 1:下载ubuntu server14的 iso镜像文件 2:下载 UltraISO U盘镜像制作工具 : 3:使用Ultra iOS 将下载好的 is ...

  6. 为什么MB51本位币金额和采购订单历史本位币金额不一样?

    翻译 为什么物料凭证的本位币金额有时和采购订单历史记录物料的本位币金额不一致? 回答: 有2个字段对应FI存放位置.物料凭证的字段(MSEG-DMBER)是存放库存账目金额.采购订单历史(EKBE-D ...

  7. spring boot学习

    window10的环境下 使用apache-maven-3.3.9 到https://github.com/spring-projects/spring-boot 下载源码包,解压到c:\spring ...

  8. const 不再迷茫

    博客地址:http://blog.csdn.net/jiangxinnju github:https://github.com/jiangxincode 首先说明一下const在C和C++中的主要用法 ...

  9. css3属性 transition transform

    1.transition 译:过渡,转变 可以设置过渡属性 transition: property duration timing-function delay; transition-proper ...

  10. 11 个用来创建图形和图表的 JavaScript 工具包

    11个用来创建图形和图表的JavaScript工具包,方便开发者使用,喜欢的各位收藏一下吧! Aristochart DEMO|| Download Aristochart 是一个用来创建图形和图表的 ...