标准函数库[链接]
小端序与大端序
大端序符合人类阅读习惯,但多数计算机平台采用小端序,这跟芯片的制作工艺有关,例如0x5ecff7f0
小端序在内存中显示为 f0 f7 cf 5e
。
指针
指针操作
一个int类型指针 + 1相当于加4个字节,因为int类型分配的空间就是4个字节(与平台架构有关),外挂程序就是通过修改指针赋值实现某些游戏功能。
指针转换
指针可以进行强制转换,但转换后获取的值可能会有偏差,例如char类型的指针与int类型的指针,虽然&a
与b
指向的是同一个地址,但char取值只取一个字节,而int取值为四个字节:
char a = 'd'; int* b = (int*) &a; printf("a = 0x%x\n", *&a); // *&a = 0x64 printf("b = 0x%x\n", *b); // *b = 0xcccccc64 out: a = 0x64 b = 0xcccccc64
二级指针/三级指针
二级指针存放一级指针的地址,三级指针存放二级指针地址
char a = 'd'; char* b = &a; char** c = &b; printf("a = 0x%x\n", *&a); printf("b = 0x%x\n", *b); printf("c = 0x%x\n", **c); printf("c = 0x%p\n", *c); out: a = 0x64 b = 0x64 c = 0x64 c = 0x14fcd4
指针外挂
可以使用Cheat Engine对相关地址的值进行修改,也可以自己写个C程序对指针进行修改,低权限进程无法修改高权限进程值。
指针数组
数组变量默认就是一个指针,如下所示:a
是一个指针,所以*pa == a
,pa[0] == a[0], pa[1] == a[1]
,b
是一个值,所以*pb == &b
。指针也可以指向具体某个数组值的地址:*pa4 = &a[4]
int a[5] = {1,2,3,4,5}; int b = 6; int* pa = a; int* pb = &b; int* pa4 = &a[4]; // 指针也可以指向具体某个数组值的地址
数组、函数、指针
数组传递参数,由于数据变量是指针类型,所以默认传进函数的是指针,也就意味着在主函数内部修改值,外部的值也是会跟着改变
void test(int a[]) { for (int i = 0; i < 3; i++) { printf("i = %d\n", a[i]); a[i] = a[i] + 100; } } void test2(int c) { printf("c = %d\n", c); c = 100; } int main() { int b[] = {1,2,3}; test(b); for (int j = 0; j < 3; j++) { printf("j = %d\n", b[j]); } /* 打印被修改的值,而不是1,2,3: j = 101 j = 102 j = 103 */ int d = 2; test2(d); printf("d = %d\n", d); /* 打印原始值,不会因为函数值被修改而改变 d = 2*/ }
函数名默认也是一个指针,所以数组值也可以是一组函数,需要注意返回类型必须一样
void* f[2] = { test, test2 }; printf("f[0] = %p\n", f[0]); printf("f[1] = %p\n", f[1]); out: f[0] = 00007FF70C0613D9 f[1] = 00007FF70C0613E3
定义一个新函数指向函数指针(会有很多这种使用场景,根据具体业务使用)。
int b[] = { 1,2,3 }; void(*pf)(int[]) = test; pf(b);// 调用新函数
pf的值是一个二级指针,指向一个新的复制函数的地址。

方法里面调用函数指针数组:
void test() { printf("test\n"); } void test3() { printf("test3\n"); } void test2(void (*c[3])()) { c[2](); return; } int main() { void(*ptest)() = test; void* c[3] = {ptest, test, test3}; // 一般指针指向函数,在方法里面调用需要强转 void(*b[3])() = {ptest, test, test3}; // 声明为函数指针数组 test2(b); }
自定义类型
在上面的方法用,void* c[3] = {ptest, test, test3}
只是一般指针指向函数,而不是函数指针,如果传入的参数只是一般的指针,而在方法里面调用,此时就需要自定义类型对函数进行强转
typedef void (*PT)(); // 定义函数类型 void test() { printf("test\n"); } void test3() { printf("test3\n"); } void test2(void* c[3]) { PT p = (PT)c[0];// 将一般指针强转为函数指针 p(); return; } int main() { void(*ptest)() = test; void* c[3] = { ptest, test, test3 }; // 一般指针指向函数 void(*b[3])() = { ptest, test, test3 }; // 声明为函数指针数组 test2(c); }
Const 声明位置
int a = 6; int b = 7; int* const pa = &a; int const* pb = pa; pa = &b; // 报错,变量已声明为常量,只能指向&a,不能修改为其他指向 *pa = 10; pb = &b; // 报错,变量指针已声明为常量,不能修改指针里面的值 *pb = 10; const int* const pc = &a; // 全部只读
结构体:根据声明类型决定结构体内存大小,取地址为&s.name,&s.age
,结构体大小因为涉及到内存里的对齐(例如int
4个字节,char
1个字节,结构体为8个字节),所以会比实际的声明大一些,获取结构体大小直接使用sizeof
就可以了。
#include <iostream> struct Student { char* name; char age; int clazz; }; typedef struct dslbq { char a : 2; int b; short c; } d; // 定义结构体为新类型 struct dslbq { char a : 2; int b; short c; } d; // 声明d变量 、 int main() { Student s; s.age = 30; s.name = (char*)"dslbq"; s.clazz = 3; Student* ps = &s; // 取结构体地址 ps -> name = (char*)"beijing"; // 写结构体声明地址 printf("%s\n", ps -> name); // 读结构体声明地址 }

数组变量初始化时,默认分配一块空间,并且这个变量为const,不能重新指向另一个地址,因为已经分配,避免浪费。
char b[3]; b[0] = 'aa'; b = { 'aa' }; // 报错
Union:只能使用其中的一个字段,赋值时会把其他值同时更新
union Human { char a; int age; }; struct Student { char* name; char age; int clazz; union { // 匿名联合体 int ua; int ub; }; }; int main() { Human human; human.a = 'b'; human.age = 21; }


枚举:对设定值后面的值依次累加1
enum Enum { enum1, // 设置值:0 enum2, // 设置值:1 enum3 // 设置值:2 }; int main() { Enum e; e = enum1; e = enum2; }
文件包含
函数声明写在.h
文件,函数实现写在.c
文件,.c文件include .h
实现具体内容,业务调用直接包含.h
文件,这样就可以只暴露接口,隐藏具体实现的代码
引入三方库
引入.h
与.c
文件到工程指定目录
宏定义(预处理):Windows大量的使用宏定义,是因为声明变量、函数等需要在栈中开辟空间,而用宏就直接进行文本替换,不需要进栈出栈走函数的那种体系,效率更高。
#define _CRT_SECURE_NO_WARNINGS #define PX printf("main\n"); #define PP(x) printf("main=>%s\n", x); // 格式:预处理指令 宏[宏参数] 替换体 #ifdef windows // 条件预编译,比如不同操作系统采用不同操作 int x = 1; #else int x = 0; #endif
可变参数
#include <stdarg.h> #define PR(...) printf(__VA_ARGS__) // 可变宏定义参数 #define VAR(x) V ## x // 动态语法,例如VAR(2)则动态声明V2这个变量,##:将后面的x粘到前面的参数 int pr(int num, ...) { // 可变函数参数 va_list valist; va_start(valist, num); for (int i = 0; i < num; i++){ int arg = va_arg(valist, int); printf("%d\n", arg); } va_end(valist); return 0; } int main(int argc, char* argv[]) { int VAR(1) = 2; pr(5, 2, 3, 4, 5, 6); PR("%d\n", 12); }
内存

栈区 / 堆栈(stack)
先进后出,程序运行时由程序自动分配,存放函数的参数值、局部变量的值,程序结束时由程序自动释放。
堆区(heap)
先进先出,程序运行时在内存开辟另一块存储区域,一般由程序员分配释放,用malloc
,calloc
,realloc
等分配内存的函数分配得到的就是在堆上。
全局区(静态区static)
全局变量、函数体等
编译过程
- 预处理(Preprocessiong)
预处理指令文本替换:#include,宏定义 - 编译成汇编(Compilation)
转换成对应汇编代码 - 汇编转二进制(Assemble)
汇编代码转换成二进制代码 - 链接(Linking)
链接操作系统库代码,打包到本程序中
发表回复