C语言知识点
__declspec(naked) 裸函数添加后编译器不对函数进行编译
调用约定
__cdecl 从右至左入栈 调用者清理栈
__stdcall 从右至左入栈 自身清理堆栈
__fastcall ECX/EDX传送前两个 剩下:从右至左入栈 自身清理堆栈
C语言中的数据类型:
基本类型 分为 整数类型和浮点类型
构造类型 分为 数组类型、结构体类型、共用体(联合)类型
指针类型
空类型(void)
学习数据类型的三个要素:
1、存储数据的宽度
2、存储数据的格式
3、作用范围(作用域)
数组和多维数组编译器的计算方式
int arr[10][10][10] = {0};
取arr[9][6][7]编译器的计算方式为9*10*10+6*10+7.
取arr[4][6][5]编译器的计算方式为4*10*10+6*10+5
int arr[10][3] = {0};
取arr[3][2]编译器计算方式为 3*3 + 2
指针
一种带*号的数据类型,特征为:
1. 宽度为4
2. 可以做 ++ 或 --操作
3. 可以和整数做相加和相减操作
4. 可以和另一个同类型的指针做相减操作
5. 可以做比较操作
& 取地址操作对应的汇编指令为lea 取地址操作会在原来的数据类型前加1个*
* 取值操作只可以对带*号的类型进行取值,取地址后数据类型减一个*
结构体指针
struct Student
{
int a;
int b;
};
Student* student;
结构体指针取值使用->符号
student->a;
student->b;
数组指针:
定义方式 int (*px)[5];
取值操作需要使用2次*操作 *(*(px))也可以使用[]取值和*操作时一致的px[0][0]
以此类推int (*px)[5][3];
取值需要3次*操作
*(*(*(px+1)+2)+3) = 1*5*3*4 + 2*3*4 + 3*4 + 4
函数指针
定义方式 int (*fun)(int,int);
特征 占用4个字符 可以做比较操作 因为目标i的宽度补确定所以无法做其他的运算操作
位运算
&与运算 2&3 结果为2 2个位都为1结果才是1
0000 0010
0000 0011
---------------
0000 0010
|或运算 2|3 结果为3 2个位有一个位是1结果就是1
0000 0010
0000 0011
---------------
0000 0011
~非运算 ~2 结果位FD 0的运算结果为1 1的运算结果为0
0000 0010
---------------
1111 1101
^异或运算 2^3 结果为1 2个位运算都为0的结果为0 都为1的结果为0 0和1的运算结果为1
0000 0010
0000 0011
-----------------
0000 0001
<< 左位移指令对应的汇编指令为SHL
>> 右位移指令有符号数的汇编指令为SAR 无符号数的汇编指令为SHR
一些常见的C语言操作
#define 宏定义 分为无参数宏定义和有参数的宏定义
无参数宏定义
# define TRUE 1
# define FALSE 0
# define PI 3.1415926
# define DEBUG 1
注意事项
1、只作字符序列的替换工作,不作任何语法的检查
2、如果宏定义不当,错误要到预处理之后的编译阶段才能发现
有参数的宏定义
# define MAX(A,B) ((A) > (B)?(A):(B))
代码 x= MAX( p, q)将被替换成 y=((p) >(q)?(p):(q)
注意事项:
1、宏名标识符与左圆括号之间不允许有空白符,应紧接在一起.
2、宏与函数的区别:函数分配额外的堆栈空间,而宏只是替换.
3、为了避免出错,宏定义中给形参加上括号.
4、末尾不需要分号.
5、define可以替代多行的代码,记得后面加 \
#define MALLOC(n,type)\
((type*)malloc((n)*sizeof(type)))
重复包含的解决办法(不是绝对的)
malloc申请内存的使用方式:
int* ptr;//声明指针
//在堆中申请内存,分配128个int
ptr = (int *)malloc(sizeof(int)*128);
//无论申请的空间大小 一定要进行校验 判断是否申请成功
if(ptr == NULL)
{
return 0;
}
//初始化分配的内存空间
memset(ptr,0,sizeof(int)*128);
//使用。。。
*(ptr) = 1;
//使用完毕 释放申请的堆空间
free(ptr);
//将指针设置为NULL
ptr = NULL;
注意事项:
1、使用sizeof(类型)*n 来定义申请内存的大小
2、malloc返回类型为void*类型 需要强制转换
3、无论申请的内存有多小 一定要判断是否申请成功
4、申请完空间后要记得初始化.
5、使用完一定要释放申请的空间.
6、将指针的值设置为NULL.
静态链接库和动态链接库
创建静态链接库:
1、在VC6中创建项目:Win32 Static Library
2、在项目中创建两个文件:xxx.h 和 xxx.cpp
xxx.h文件:
#if !defined(AFX_TEST_H__DB32E837_3E66_4BE7_B873_C079BC621AF0__INCLUDED_)
#define AFX_TEST_H__DB32E837_3E66_4BE7_B873_C079BC621AF0__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
int Plus(int x, int y);
int Sub(int x, int y);
int Mul(int x, int y);
int Div(int x, int y);
#endif
xxx.cpp文件:
int Plus(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
3、编译
二、使用静态链接库:
方式一:
将xxx.h 和 xxx.lib复制到要使用的项目中
在需要使用的文件中包含:#include "xxx.h"
在需要使用的文件中包含:#pragma comment(lib, "xxx.lib")
方式二:
将xxx.h 和 xxx.lib复制到要使用的项目中
在需要使用的文件中包含:#include "xxx.h"
静态链接库的缺点:
使用静态链接生成的可执行文件体积较大,造成浪费
我们常用的printf、memcpy、strcpy等就来自这种静态库
一、创建DLL
1、源文件中: 2、头文件中
int __stdcall Plus(int x,int y) extern "C" _declspec(dllexport) __stdcall int Plus (int x,int y);
{ extern "C" _declspec(dllexport) __stdcall int Sub (int x,int y);
return x+y; extern "C" _declspec(dllexport) __stdcall int Mul (int x,int y);
} extern "C" _declspec(dllexport) __stdcall int Div (int x,int y);
int __stdcall Sub(int x,int y)
{
return x-y;
}
int __stdcall Mul(int x,int y)
{
return x*y;
}
int __stdcall Div(int x,int y)
{
return x/y;
}
说明:
1、extern 表示这是个全局函数,可以供各个其他的函数调用;
2、"C" 按照C语言的方式进行编译、链接
__declspec(dllexport)告诉编译器此函数为导出函数;
二、使用DLL
方式一:隐式连接
步骤1:将 *.dll *.lib 放到工程目录下面
步骤2:将 #pragma comment(lib,"DLL名.lib") 添加到调用文件中
步骤3:加入函数的声明
extern "C" __declspec(dllimport) __stdcall int Plus (int x,int y);
extern "C" __declspec(dllimport) __stdcall int Sub (int x,int y);
extern "C" __declspec(dllimport) __stdcall int Mul (int x,int y);
extern "C" __declspec(dllimport) __stdcall int Div (int x,int y);
说明:
__declspec(dllimport)告诉编译器此函数为导入函数;
方式二:显示链接
步骤1: //定义函数指针
typedef int (__stdcall *lpPlus)(int,int);
typedef int (__stdcall *lpSub)(int,int);
typedef int (__stdcall *lpMul)(int,int);
typedef int (__stdcall *lpDiv)(int,int);
步骤2: //声明函数指针变量
lpPlus myPlus;
lpSub mySub;
lpMul myMul;
lpDiv myDiv;
步骤3: // //动态加载dll到内存中
HINSTANCE hModule = LoadLibrary("DllDemo.dll");
步骤4: //获取函数地址
myPlus = (lpPlus)GetProcAddress(hModule, "_Plus@8");
mySub = (lpSub)GetProcAddress(hModule, "_Sub@8");
myMul = (lpMul)GetProcAddress(hModule, "_Mul@8");
myDiv = (lpDiv)GetProcAddress(hModule, "_Div@8");
步骤5: //调用函数
int a = myPlus(10,2);
int b = mySub(10,2);
int c = myMul(10,2);
int d = myDiv(10,2);
特别说明:
Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。
HMODULE 是代表应用程序载入的模块
HINSTANCE 在win32下与HMODULE是相同的东西 Win16 遗留
HWND 是窗口句柄
其实就是一个无符号整型,Windows之所以这样设计有2个目的:
1、可读性更好
2、避免在无意中进行运算
使用序号导出函数隐藏函数名
1、*.h文件
int Plus (int x,int y);
int Sub (int x,int y);
int Mul (int x,int y);
int Div (int x,int y);
2、*.cpp文件
int Plus(int x,int y)
{
return x+y;
}
int Sub(int x,int y)
{
return x-y;
}
int Mul(int x,int y)
{
return x*y;
}
int Div(int x,int y)
{
return x/y;
}
3、*.def文件
EXPORTS
Plus @12
Sub @15 NONAME
Mul @13
Div @16
4、使用序号导出的好处:
名字是一段程序就精华的注释,通过名字可以直接猜测到函数的功能
通过使用序号,可以达到隐藏的目的.