旧文迁移,新年第一篇,稍微回顾(bu)了下C(翁凯老师的C进阶),以后遇到了再记上来吧~
C复习
地址
&取地址,只能对存在地址的变量取地址
指针
定义时,*代表类型是指针
1 | int* p, q; |
使用时,*代表取存储的地址处的数据
应用场景:
函数要改变传入值,函数要返回多个结果数据(return返回运算状态)
常见错误:
定义指针类型后未赋值就开始使用->里面存的值未初始化,使用即在错误的地址取值
1 | //这四种是等价的,只是对于无名参数只能在声明函数时使用 |
数组名是指针常量,于是每个元素都是常量
1 | const int a[4] = { 1,2,3,4 }; |
常量
const的作用范围仅限于直接对象,例如int * const q代表指针q是常量,即里面保存的地址不能变,但是地址上的值却是可变的
枚举类型(不用符号常量化)
1 | enum name{name1[=num],name2[=num],name3,….};//编号从零开始,可以指定,后续自增 |
结构体
定义:
1 | struct _a { |
赋值:
可单独赋值,也可整体赋值(使用{})
(注意:结构体变量名不是结构体变量的地址,所以才有p->next这种鬼)
typedef
C高阶的标志,所有类型重定义,略略略~
1 |
|
联合
宏
1 |
(注意最好不要加;不然容易死。。。)
位运算
输出一个数的二进制形式:
1 |
|
位段:
1 | struct weiduan |
Windows基础
内核对象
C的结构体吧,存储在系统空间,使用对象句柄(HANDLE)访问,它的所有者是操作系统而非进程,用户只能通过操作系统提供的特定接口访问特定的对象。
对于进程,每个进程都有一个句柄表,句柄相当于索引
索引 | 指向内核对象内存块的指针 | 访问掩码 | 标志 |
---|---|---|---|
1 | 0x00000000 | (不可用) | (不可用) |
2 | 0x05000000 | 0x???????? | 0x00000001 |
… | … | … | … |
所以多个进程间不能直接使用句柄共享内核对象,默认句柄不会被子进程继承,可以通过创建时设置标志允许继承,当对对象命名后,在有权限的情况下,其他进程就可以通过对象名打开那个对象。
进程
进程是资源拥有的基本单位,上一篇已经记了,虚拟内存空间分布,其中用户空间属于进程独有,一般一个程序加载后布局:
另一部分,进程还有个进程内核对象:
进程控制块是进程实体的一部分,是操作系统中最重要的记录型数据结构。PCB中记录了操作系统所需的,用于描述进程进展情况及控制进程运行所需的全部信息。PCB是进程存在的惟一标志。一般把PCB存放在操作系统专门开辟的PCB区内。
在进程控制块中,主要包括下述4方面的信息:
(1)进程描述信息
进程标识符:每个进程都有惟一的进程标识符,用以识别不同的进程。
用户名或用户标识号:每个进程都隶属于某个用户,有利于资源共享与保护。
家族关系:标识进程之间的家族关系。
(2)处理机状态信息
通用寄存器、指令计数器、程序状态字(PSW)、用户栈指针等
(3)进程调度信息
进程状态:指明进程的当前状态,以作为进程调度和进程对换时的依据。
进程优先级:用于描述进程使用处理机的优先级别的一个整数,优先级别高的进先获得处理机。
进程调度所需的其他信息:如进程已等待CPU的时间总和、进程已执行的时间总和等。
事件:指进程被阻塞的原因。
(4)进程控制信息
程序和数据的地址:指出该进程的程序和数据所在的内存或外存地址,以便再调度到该进程执行时,能从中找到其程序和数据。
进程同步和通信机制:指实现进程同步和进程通信时所必须的机制,如消息队列指针、信号量等。这些数据应全部或部分地存放在PCB中。
实际上进程不会执行任何东西,它只是线程的容器(未使用到多线程时,也是进程的主线程执行整个程序),在C中,我们的程序是以_tmain/_tWinMain函数为入口点,但是实际编译时,在主函数之前还需要做一些初始工作,这部分代码由编译器添加,所以程序入口点地址(称作”入口函数吧”)并不是主函数地址。下面是一个程序的生命周期执行过程:
一旦用户登录一个系统后将拥有一个安全令牌(security token),以后访问受保护资源时都会使用这个令牌,令牌会传给子进程,一个进程拥有令牌后不能改变,若要更高权限的令牌也只能创建新进程并在那时申请更高权限令牌(UAC).
线程
与进程一样,线程也分两部分:在内核空间中的线程内核对象和用户空间中的线程栈
内核对象:
同进程,也记录着线程执行的一些信息,便于线程切换(用户态上下文和内核态的不一样)等:
1 | typedef struct _CONTEXT { |
线程栈:
创建线程后,会在用户空间申请一块区域存放线程局部数据(传入参数,局部变量都在这个栈中)
因为线程共享同一进程空间,所以线程是可以访问所属进程中所有的句柄,内存,其他线程的数据的
只有窗口和挂扣对象属于线程,其他用户对象都属于进程,所以若不显式关闭,线程结束时只有前两类句柄会被自动关闭(使用计数-1)
虚拟内存
虚拟内存
使用VirtualAlloc()使用MEM_REVERSE参数可以预定一块地址空间,此时它在虚拟内存里被预定但是并未被映射到物理存储,所以还是不能使用的,可以使用MEM_COMMIT参数调拨物理存储器空间,这样就能使用这块地址了。
内存映射文件
内存映射文件的物理存储器来自磁盘上已有的文件。例如运行exe文件,加载dll文件等,都是直接将文件映射到内存(这与CreateFile不一样,它只是打开文件,可以通过文件句柄,偏移指针访问文件内容,而内存映射是可以完全映射内容的),当同一程序被同时多次执行,它只会被映射一次,其他进程只需复制它的映射视图即可,不过默认情况下他们的全局变量和静态变量不会被共享,而且当一个进程修改了其他部分(如hook API修改了代码)会发生写时复制。
堆
对这种神奇的东西细节留到堆溢出部分来记录,现在先记下大概的使用。它是一块预定的地址空间区域,物理存储器始终是页交换文件,进程初始化时会被分配一个默认堆,可使用GetProcessHeap()获取句柄,用户可以自己创建HeapCreate()堆取,然后从定义的堆取里面分配HeapAlloc()内存
DLL
隐式调用
在编写程序时,只包含dll的头文件,其中头文件会声明导出数据结构,符号,函数和变量,它需要用到*.lib,生成的可执行文件IAT中会有各种需要导入的dll数据,在程序运行前dll就会被加载到内存中。
显示调用
在编写程序时,包含头文件并使用LoadLibrary()显式加载dll,加载的dll不会出现在IAT中,只有程序运行到函数出才会动态加载dll并在使用完以后可以通过FreeLibrary卸载,在进程/线程对此dll进行装载/卸载时可以执行指定的初始化/退出代码(具体笔记在消息勾取处)