71.关于调用约定
调用约定(Calling convention)决定了以下内容:
1)参数的压栈顺序(自右向左还是自左向右)
2)函数返回时,由调用函数还是被调用函数清理入栈的参数
3)编译时函数名的转换
一共有五种调用约定.
1.__stdcall
参数自右向左压栈
被调用函数在返回前清理入栈参数
C编译时函数名的转换:_function@number
其中function为函数名,number为参数的字节数
例:int MyFunc(int a, int b)
_MyFucn@8
C++编译时函数名的转换:?function@@YG****@Z或者?function@@YG*XZ
若函数有参数,以@Z结束;若函数无参数,则以Z结束
其中function为函数名,*代表参数表,为下列值:
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"代表一次重复
参数表第一项为返回类型,其后跟参数的类型,指针标识在其所指数据类型前
例:int MyFunc1(unsigned char *arg1, unsigned long arg2)
?MyFunc1@@YGHPAEK@Z
void MyFunc2(char *arg1, char *arg2, char *arg3)
?MyFunc2@@YGXPAD00@Z
void MyFunc3()
?MyFunc3@@YGXXZ
C++编译器转换函数名时更多的考虑了参数,主要是为了方便函数重载,而C语言则不存在函数重载问题
2.__cdecl
参数自右向左压栈
调用函数在函数返回后清理入栈参数
C编译时函数名的转换:_function
其中function为函数名
例:int MyFunc(int a, int b)
_MyFucn
C++编译时函数名的转换:同__stdcall,把YG改为YA
注意:对于可变参数的成员函数,始终使用__cdecl的转换方式
3.__fastcall
使用ECX传递第一个参数,EDX传递第二个参数,其余参数自右向左压栈
被调用函数在返回前清理入栈参数
C编译时函数名的转换:@function@number
其中function为函数名,number为参数的字节数
例:int MyFunc(int a, int b)
@MyFucn@8
C++编译时函数名的转换:同__stdcall,把YG改为YI
调用约定可以在Project->Setting...->C/C++->Code Generation中的Calling convention中进行设置,缺省状态为__cdecl
4.thiscall
thiscall不是一个关键字,因此不能在程序中明确指定,它是C++类成员函数缺省的调用约定。由于成员函数调用涉及到一个this指针,因此必须进行特殊处理。
参数自右向左压栈
如果参数个数确定,this指针通过ECX传递给被调用者;如果参数个数不定,this指针在所有参数压栈后被压入堆栈
如果参数个数确定,被调用函数自己清理堆栈;如果参数个数不定,由调用函数清理堆栈
可见,对于参数个数固定情况下,它类似于__stdcall,不定时则类似__cdecl
5.naked call
使用前四种调用约定时,在进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。更特殊的是,不能用return返回返回值,只能用插入汇编返回结果。
naked call必须和__declspec连用,即__declspec(naked),naked call还可以和其他调用约定联用,如:
__declspec(naked) int __stdcall function(int a, int b)
使用场合:
1、_beginthread需要__cdecl的线程函数地址,_beginthreadex和CreateThread需要__stdcall的线程函数地址。
2、main函数必须是__cdecl,一般的WIN32函数和回调函数都是__stdcall,并且专门定义了宏来标识:
#define CALLBACK __stdcall
#define WINAPI __stdcall
3.如果某函数在C语言编译器中编译,而在C++文件使用,由于两种编译器对函数名的解析不一样,因此需要在C++文件中使用extern "C"进行声明,否则会发生链接错误:
#ifdef _cplusplus
extern "C"{
#endif
int func(int a, int b);
#ifdef _cplusplus
}
#endif
extern "C"的调用约定会使得编译后的导出函数的名称和原来函数的名字相同。但是,
extern "C"不能用于导出一个类的成员函数,只能用于导出全局函数。
72.怎样在使用标准调用约定_stdcall时,不改变导出函数的名字
在工程目录下手动建立一个文件projectname.def ,利用project->Add to project->Files把该文件添加到工程中,在文件中写入:
LIBRARY projectname
EXPORTS
Fun1
Fun2
73.全局变量,局部变量,静态全局变量,静态局部变量,静态函数
在一个.cpp文件中定义的全局变量,在所有的cpp文件中都是可见的。其作用域是全局和全时间的。局部可以重新定义和全局变量同名的变量来屏蔽掉全局变量,局部变量的生存期是其所在的大括号。
静态全局变量是在静态内存区域分配的空间,其作用域是全时间但仅仅在定义它的cpp文件中有效,在其他的源文件中是不可见的。静态全局变量的值一旦被修改,在其生存期内是不会改变的。静态局部变量也是在静态内存区域分配的内存空间,其作用域是全时间的但仅仅在其定义的函数中可见,其他函数和cpp文件中都不可见。
静态函数,仅仅在本cpp文件中声明,定义和使用的函数应该定义为静态函数,防止其他cpp文件调用。其他普通函数应该在头文件中声明,在头文件对应的cpp文件中定义。使用时包含对应的头文件就可以了。
74.关于MakeFile
规则:target ... : prerequisites ...
command
...
...
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),即伪目标。prerequisites就是,要生成那个target所需要的文件或是目标。 command也就是make需要执行的命令。(任意的Shell命令)
工作方式:
1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。
依赖关系:即需要的头文件,或生成可执行文件所需要的所有.obj文件。需要的头文件就是在cpp的头部列出的所有的include""但这其中不包括编译器提供的库文件,只是依赖自己编写的.h文件。可执行文件的依赖的与头文件和与头文件对应的cpp文件生成的obj文件。
隐含规则:它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来
伪目标:
.PHONY: Clean
Clean:
Shell command
..........
这样在终端中输入: make Clean
就可以执行Clean下的这段代码,一般用于清理obj临时文件,而且放在makefile文件的末尾。
还可以用于多个终极目标时,在第一行设这伪目标,伪目标的依赖文件就可以是多个要生成的可执行文件。
命令行属性:
- :减号表示执行一条语句时如果发生错误,不终止而是继续执行。
+ :加号表示,只要本条规则的目标文件过期,始终执行该条command,不论在make
命令中如何设置选项。
@ :执行本条命令时不再屏幕上打印命令的内容。
上述三个属性可以进行任意的叠加。
注释: 注释采用#开始,一般注释放在最后或第一行,如果注释放在一条语句的后面时要紧跟在语句的后面,不能有空格,因为有时候空格会被理解为字符。如果要用到#字符以
/#来完成。
包含其他的makefile文件:
Include filename
不可以以tab开头,filename可以是含有路径的文件名称。Include不一定放在一个makefile的开始,而是在需要的位置进行包含,原理就是把包含的文件原封不动的展开在包含的位置上。
自定义命令:
如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束,如:
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在“define”和“endef”中的两行就是命令序列。
使用条件语句控制编译:
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
判断两个变量是不是相等,决定如何编译。
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
判断一个变量是否已经定义来决定如何编译。
搜索目录:Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)。VPATH 变量写在makefile文件的开始部分。
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。
vpath %.h ../headers
vpath %.c foo
vpath % blish
使用变量:
变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。
= :这种赋值的方法是把右边的一个字符串或者变量赋给左边,不管右边的变量是否定义,没有定义的话就当做字符串,在以后定义的话就做类似宏的替换。
:= :立即展开的变量,如果没有定义会被当做字符串,如果是没有定义的变量的引用$(a)就会被忽略。
?= :这种赋值是,如果变量已经赋值,则没有动作。如果没有赋值,则进行赋值。
+= :追加赋值,把一个字符串或者变量追加到一个变量的末尾并用空格分开。
还有一些系统预定义变量:包括
CC cc
CXXFLAGS g++
CFLAGS -o
MAKE make
SHELL
PWD
AR ar
ARFLAGS -ruv
自动化变量:
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
所有比目标新的依赖目标的集合。以空格分隔。
$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+
这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$*
如果目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。
75.C语言文件操作
头文件:stdlib.h
文件指针:FILE *fp;
打开文件:fp = fopen("const char *filename", "MODEL"); 返回文件指针或者NULL。
r ->只读文本文件;w->只写文本文件;a->向文本文件追加数据;
rb ->只读二进制文件;wb->只写二进制文件;ab->向二进制文件追加数据;
r+->先读后写一个文本文件;w+->先写后读一个文本文件;a+->向文本文件追
加数据;
rb+->先读后写一个二进制文件;wb+->先写后读一个二进制文件;ab+->向二
进制文件追加数据;
关闭文件: fclose(fp); 返回0,或者EOF。
写入一个字符: char c = fputc (ch, fp);返回写入的字符或者EOF。
读出一个字符: char c = fgetc (fp); 返回一个字符或者EOF。
判断文件是否结束:feof (fp); 返回1真,或者0假。
写入一个数据: fread (buffer, size, count, fp); 返回count或者0;
Buffer,存放地址。Size,一个数据项的大小,count,多少个数据项。
读出一个数据: fwrite (buffer,size, count,fp); 返回count或者0.
写入字符串: fputs (str, fp); 返回0或者EOF。
读出字符串: fgets (str,n,fp); 返回str地址或者EOF。
只是读取n-1个最后添加一个'/0'
立即写入: fflush(fp);
文件指针移动: fseek(fp, offset, pos);
Pos= SEEK_SET,SEEK_END,SEEK_CUR。
获取文件指针: ftell(fp); 返回文件指针的当前位置。
获取错误号: ferror(fp); 返回0或者错误号码。
清除错误号码: clearerr(fp);