纯随手笔记,没有章法没有顺序。几乎没有可读性。
学习笔记
存储类别
const:保证指针只想的内存区域不可修改
static:如果修饰块作用域、函数原型作用域、函数作用域则赋予变量静态存储期,不改变变量的作用域,变量成为静态变量。若修饰全局变量,则该变量的作用域缩小为当前文件(内部链接),可以想象成私有化。
自动变量:auto修饰,起强调作用。特点:自动存储期、块作用域、无链接,不会自动初始化
寄存器变量:使用register建议计算机将变量放入寄存器。其修饰的变量不可获取地址
静态变量:在程序运行期间一直在内存中存在,例如函数内变量用static修饰,下次调用函数时这个变量的值还是上次调用该函数时的值。具有静态存储期的变量会被自动初始化,这点不同于自动变量。
外部链接:全局变量,若指出该变量是外部变量(来此外部),用extern声明。(例如在其他文件中定义的全局变量,在本文件中用extern再次声明,第一次声明叫定义式声明,第二次声明叫引用式声明。只有在定义式声明中才能初始化变量)。
函数也有外部函数(默认)、静态函数、内联函数(c99)。
静态函数使用static修饰,外部函数在同一程序其他文件中也可以extern修饰引用声明表明当前文件中所使用的函数定义在别处
misc
fgets会保留换行,gets_s不保留换行,但需要实现错误处理函数,较为麻烦。总体而言fgets更好
const修饰的内容是不可变的,比如const int a[]那就是a的数组内容不可变,int const a*那就是a的指向不能变
ctype.h是C标准函数库中的头文件,定义了一批C语言字符分类函数,用于测试字符是否属于特定的字符类别,如字母字符、控制字符等等。既支持单字节字符,也支持宽字符。
宽字符是宽度始终为16 位的多语言字符代码。 字符常量的类型是 char ;对于宽字符,该类型是 wchar_t 。 由于宽字符始终具有固定大小,因此使用宽字符集可以简化使用国际字符集进行的编程。 宽字符串文本 L”hello” 将成为类型为 wchar_t 的六个整数的数组。
- 作用域:块,函数作用域(goto),函数原型作用域,文件作用域
- 链接:外部链接,内部链接,无链接
在文件中直接定义的全局变量是外部链接,其他文件也可以访问得到(一般称之为全局作用域),而如果是static定义的全局变量则是内部链接(一般称之为文件作用域) - 存储期:静态存储期,线程存储期,自动存储期,动态分配存储期
注意:全局变量的static关键字声明的是变量或函数的链接属性!而局部变量的static关键字则是声明静态存储期,作用域还是上层块,但是存放在静态内存区。全局变量默认都是静态的,都是存放在静态存储区中的 - 存储类别:自动(关键字auto显示声明,自动变量不会自动初始化!),寄存器(关键字register),静态块作用域(块作用域的静态变量,其实就是函数内部声明的静态变量,放在静态内存里面,不会随函数结束而消失),静态外部链接(外部链接的静态变量,也就是普通的全局变量,全局变量的初始化只能是常量表达式,不能带变量,这个很容易理解,如果需要强调引用外部定义的全局变量,可以用extern关键字),静态内部链接(内部链接的静态变量,也就是用static修饰的全局变量,静态存储期)
函数的关键字: inline,static(限定作用域),extern
一定要注意size_t类型的参与运算!这是一个无符号整型,不要拿来和有符号整型做运算!相关函数malloc,sizeof,strlen等等
限定符:const,volatile(允许其他程序修改这个值,防止编译器优化),restrict(c99允许编译器优化。只能用于指针,该指针是访问这片地址的唯一且初始的方式),_Atomic(c11 ,跟stdatomic.h里面的函数一起用 比如 原子操作_Atomic int hogs; atomic_store(&hogs,12))
二进制方式打开的文件可以看见\r\n^Z,而使用文本方式打开的文件可以看到\r,其他的都看不到了。
c11特性:fopen的x模式,独占打开;任何w模式都会截断文件内容
c99:stdbool.h 新增_Bool类型,占1bit的位字段
对齐特性c11:_Alignof(float)查询float的对齐要求,_Alignas(8) 指定对齐要求
在宏定义中用#号来做字符串化,##叫做预处理粘合剂
c11泛型选择表达式_Generic(x,int:0,float:1,default:3)
_Noreturn修饰函数,说明该函数不会将控制返回给上层函数
assert.h中的assert()函数可以在运行时断言,_Static_assert(a==b,”error msg”)是在编译时就完成断言
随机数
linux中unistd.h提供sleep(),time.h提供time(),stdlib.h提供srand()和rand。rand里面有使用静态变量,所以只需要一次下种,后续调用rand都不用下种。
数据结构与算法
红黑树
红黑树是一种自平衡的二叉查找树,具有良好的最坏情况运行时间,能够在对数时间内完成查找、插入和删除操作。红黑树的每个节点都有一个颜色属性(红色或黑色),通过颜色约束来保持树的平衡。
排序算法
- 锦标赛排序:通过构建锦标赛树(类似于堆)来选择最大或最小元素,适用于外部排序。
- 桶排序:将数据分到有限数量的桶中,每个桶再分别排序,适用于数据分布均匀的情况。
进程管理
多进程管理
在C语言中,常用的多进程管理函数包括:
fork()
:创建子进程。execve()
:在子进程中执行新的程序。wait()
和waitpid()
:等待子进程结束。exit()
和_exit()
:终止进程。
当一个进程通过fork()
创建子进程后,如果父进程正常退出,子进程会被init
进程收养,从而避免僵尸进程的产生。
进程通信
管道
- 无名管道:用于父子进程之间的通信。
- 有名管道:通过
mkfifo
创建,可以像文件一样使用read
和write
进行读写。
1 | open(const char *path, O_RDONLY); // 只读 |
数据表示
负数的表示
负数在计算机中通常以补码形式表示,补码等于二进制反码加1。
浮点数标准
- 单精度浮点数:1位符号,8位指数,23位尾数,精度为7-8位。
- 双精度浮点数:1位符号,11位指数,52位尾数,精度为15位。
中间的指数部分8位,需要映射,比如8位存储的数值是128,那就映射位128-127=1;这样可以避免在中间的8位上直接存储负数
掩码操作
- 设置位:使用或操作。
- 清空位:将掩码取反后与操作。
- 切换位:使用异或操作。
- 检查位:使用与操作。
结构体位字段
结构体中的位字段允许对数据进行更精细化的操作。
对齐指定
使用_Alignas
和_Alignof
来指定对齐方式。
1 |
|
GCC扩展
__attribute__
机制
GCC的__attribute__
可以修饰函数和变量,常用的修饰符包括:
- 函数修饰符:
noreturn
、format
、const
、constructor
、destructor
。 - 变量修饰符:
aligned
、packed
。
1 | __attribute__(()) |
信号通信
信号处理
- 发送信号:使用
kill
和raise
。signal.h
- 接受信号:使用
alarm
和pause
。unistd.h
1 | void (*signal(int sig, void (*func)(int)))(int); |
这个通信的使用方式就是:1. 定义信号处理函数 2. 注册信号处理函数if(signal(SIGALRM,sighander)==SIG_ERR)pass()else{pass()}
3. 等待信号处理。信号列表可以翻man手册,alarm信号可以用来设置超时什么的功能。
信号处理的使用步骤:
- 定义信号处理函数。
- 注册信号处理函数。
- 等待信号处理。
文件操作
IO还可以选择级别:有底层IO和标准IO可选。在大多数情况下都是使用标准IO,因为不能保证所有操作系统都使用相同的底层IO
文件打开模式
r+
:读写模式。w+
:截断读写模式(清空原有内容)。x
:要求文件存在且未被占用(C11特性)。
如果要打开的文件是纯linux的文件,则二进制打开方式和文本打开方式没有区别
标准IO与底层IO
- 标准IO:使用
fopen()
、fclose()
、fread()
、fwrite()
等函数。 - 底层IO:使用
open()
、read()
、write()
等函数。
1 | fopen()、getc()、putc()、exit()、fclose() |
文件流与缓冲区
- 文件流:C语言将文件视为流,可以使用
setvbuf()
为流指定缓冲区。 - 缓冲区修改:修改缓冲区内容后使用
fflush()
刷新缓冲区。
getc()和putc()函数需要参数指定来源输入来源,而getchar()和putchar()是默认的标准输入stdin,getc()如果读取到EOF则说明到达文件尾部
fopen()与fclose()要成对使用。fclose()也存在返回值,如果文件关闭成功则为0。在编程规范中需要将该返回值考虑进函数设计当中。
rewind()函数的功能是移动指标到文件开头,fseek(fp,offset_l,SEEK_END),ftell(fp)功能是返回当前指针所在位置(适用于以二进制方式打开的文件)。fflush()是刷新缓冲区
dgetpos()和fsetpos()函数:不用long这个数量级不够大的数据类型,而使用了新类型fpos_t,使用并不复杂,可以直接看说明书
ungetc()可以将字符放回输入缓冲区。典型的使用场景:要读取某个字符前的所有内容,而不破坏缓冲区的剩下内容,在读到指定字符后将指定字符放回。
setvbuf()函数 指定或创建一个缓冲区
fread()和fwrite()函数会使用二进制形式存储和读取数据。也就是数据序列化与反序列化。返回值是成功的数据块数量
feof()和ferror()函数用于区分到达文件结尾的eof还是读取错误的eof
C语言把东西看作流,一个文件可以是一个流,可以用setvbuf为这个流指定一个缓冲区,这个流可以用来当作输出流,也可以用来当作输出流。
常用的流还有:标准输出流stdin,通常来源于键盘,标准输出流和错误输出流,通常是显示器。流与流的对接就像对接水管一样,可以把文件的流对接到stdin上完成读取文件功能,也可以将stdout流对接到文件流上完成文件写入功能。
如果文件流用setvbuf指定缓冲区,然后修改缓冲区的内容,再fflush,文件的内容会被修改吗?不能!这是未定义行为
fopen函数是C标准库里面的,而open函数是操作系统linux提供的,是低级操作。fopen返回的是文件流,有缓冲;而open返回的是文件描述符,无缓冲。一般来说,普通文件用fopen打开,而设备文件只能用open打开。
预处理指令
常用预处理指令
预处理指令:#define、#include、#ifdef、#else、#endif、#ifndef、#if、#elif、#line、#error、#pragma
关键字:_Generic、_Noreturn、_Static_assert
函数/宏:sqrt()、atan()、atan2()、exit()、atexit()、assert()、memcpy()、
memmove()、va_start()、va_arg()、va_copy()、va_end()
##
号可作为黏合剂,例子:#define XNAME(n) x##n
意味者后面的int XNAME(1) = 10;
效果等于int x1=10;
…三个点表示可变参数,也可以用在宏定义里面
宏定义展开规则
- 一般的展开规律像函数的参数一样:先展开参数,再分析函数,即由内向外展开
- 当宏中有
#
运算符的时候,不展开参数 - 当宏中有
##
运算符的时候,先展开函数,再分析参数 ##
运算符用于将参数连接到一起,预处理过程把出现在##
运算符两侧的参数合并成一个符号,注意不是字符串
当遇到宏嵌套时,如A(B(C(x)))从最外层的宏A开始找到最内层宏C(含有# ##的宏可以看成最内层,因为# ##会破坏更内层的宏),然后展开最内层宏C。
再然后循环上述过程,从最外层宏A……
assert库
assert()是在运行时检查,还有_Static_assert()函数则在编译时检查表达式
stdarg.h
可变参数库,可变参数的三个小点必须是最后一个参数,且前面必有一个int型的形参,用以处理可变参数的数量
1 | int test3(int k, ...) { |
多线程编程
线程创建与管理
linux c的线程库pthread.h:
- 创建线程:使用
pthread_create()
。 - 线程退出:使用
pthread_exit()
。 - 线程挂起:使用
pthread_join()
。
1 |
|
共享内存
同机子的多个进程之间通信的效率最高的方式。
具体步骤是:创建共享内存,映射共享内存,撤销共享内存。
创建:shmget,映射:shmat,取消映射:shmdt(),删除共享内存shmctl()去控制
多个进程共有的信息是shmid,比如两个进程都是这个shmid那么就可以共享内存
1 |
|
消息队列
存在内存拷贝 所以比共享内存要慢
1 |
|
守护进程
守护进程的实现
- 利用主进程推出,子进程被init接管的这个模式完成对终端的脱离。
- 在子进程中使用setsid函数完成创建新会话。(因为子进程会继承父进程的会话,组,控制终端,也需要脱离开来),setsid的作用就是创建一个新会话并让该进程担任会话组的组长。
- 要改变当前的工作目录,如果长期占用mount的目录则会导致无法umount
- 还要设置文件掩码,umask
- 打开的文件也是继承而来的,也需要退出。
计算机网络
OSI七层模型与TCP/IP四层模型
- 应用层、表示层、会话层 —— 对应TCP/IP的应用层。
- 传输层 —— 对应TCP/IP的传输层。
- 网络层 —— 对应TCP/IP的网络层。
- 数据链路层、物理层 —— 对应TCP/IP的网卡层和硬件层。
数据链路层
- 以太网帧格式:802.3以太网:7个字节前导码,1个字节帧开始符(这8字节是物理层加的)6字节目标mac,6字节源mac,2字节以太网类型,500数据负载,4字节冗余校验FCS 共1518字节 12帧间距 以太网类型:0800 ipv4,86DD ipv6
- CRC冗余校验:用于数据校验。CRC冗余校验码:约定一个除数,比如5位,那么就有4位的CRC余数。验证过程即mod=0,纠错码的方式:重复把戏,匹配把戏,简单求和把戏,阶梯求和把戏,矩阵定位把戏,海明码(海明码的生成稍稍有些复杂,需要复习)
无线
ieee 802.11 无线lan标准/b/a/g/n/ac/ax对应wifi1,2,3,4,5,6
PPPoE帧占8字节,占用数据的1500字节当中,所以mtu调整到1492
else
以太网以MAC作为设备标识,而IP层以IP作为设备唯一标识。
在网络层,ICMP报文头8字节,IP报文头20字节,留给上层数据1492-28 = 1464
利用ping命令可以测试到目标路径的指定MTU是否过大,ping binbla.com -f -l 1464
在windows 中使用``tracert binbla.com` 命令来观察从当前到目标host的路由途径。而linux 中使用routetrace命令,发送的是UDP报文。
在tracert路由追踪中,根据生存时间(TTL)来逐个确认中间路由。当TTL归零则不再路由转发直接返回结果或不返回结果(显示*号)。
这个protocol字段有些奇怪,既然已经是IPV4的报文首部,为什么还要多此一举。4表示IP协议,6表示TCP协议,17表示UDP协议。
值得记住的是IHL字段,在没有可选项的报文中,该字段的值通畅设置为5,单位4字节,表示IP首部共长20字节。
标识,表示同一个数据包的分片
flag:数据包是否可以分片,TTL生存时间,两个地址
IPv6的报文首部
DNS与PTR
ARP(ipv6中用ICMPv6替代功能) ,查询IP对应的MAC地址。ARP广播式发送到同网段所有设备,收到目标设备的响应。Proxy ARP路由器的功能:可以将ARP请求转发给临近的网段。
RARP:不能通过dhcp分配IP地址的嵌入式设备,用这个协议向RARP服务器请求IP地址。
ICMP:可以用来确认IP包是否到达目标地址,通知在发送过程中IP包被废弃的具体原因。做网络调试会经常用到,但不能过分依赖,毕竟部分设备可以不理会ICMP报文。
ICMP是传送层(TCP/UDP)内容,但行驶的是网络层(IP)的功能
ICMP在v4上只是辅助功能,而ICMPv6却是主要功能,替代ARP的邻居探索消息,ICMP的重定向,ICMP路由器选择消息,自动设置IP地址
也就是,从路由器拿取前缀,自己选一个地址,通过路由器告知他人。
实际上大部运营商提供的光猫上网服务都是锥形NAT的,而3G、4G网络、公共WiFi等因为安全因素都是对称式NAT。