CS61C FALL 2023 数字表示与C

前言

此文章仅用于记录个人学习cs61c过程中对课程内容的理解,如果你在学习过程中遇到了困难可以参考参考,但是本文并不能充当原课程的替代品

由于水平有限/语文功底不行,文章可能存在诸多谬误,欢迎指正

另外,因为一些原因(懒/学过),稿主省略了部分内容

不推荐单纯的收藏/粘贴复制到笔记中,个人看来这样学习是效率最低的

数字表示

  • 采样与模拟;位可以编码anything
  • 进制转换;进制适用性
  • 二进制加法;负数表示;bias encoding

C与Memory Management

#1: Abstraction

高级编程语言–汇编语言–机器语言–硬件架构描述–逻辑门描述

C的特点

  • 足够接近底层,能够充分利用底层特点
  • 编译语言
    • 将C代码直接翻译为基于机器的机器语言:编译后链接
    • 编译&运行快速
    • 基于架构,无法跨平台
  • 预处理.c–>.i
  • 充分相信程序员…也因此有很多风险

指针、数组、字符串

指针

形形色色的指针:万能指针void*、函数指针、空指针

C是pass-by-value型语言

数组

内存是一个巨大的数组,C数组是一个连续的内存块

建议写法:

int a[ARRAY_SIZE];

指针几乎和数组名相等,不同点在于:increment、declaration

数组非常原始:

  • 无越界监察
  • 数组作为参数就是一个指针
  • 函数返回的指针有可能无法使用——作为局部变量被摧毁

字符串

C字符串就是个字符数组后面加个’\0’,而strlen(str)返回的长度不包含末尾的’\0’

字对齐

现代计算机内存

现代计算机的内存是可以寻址的,每个字节8bit,且有独特的地址

64bit机器的字长为8B,指针的大小也是8B

内存分配与字对齐

指针告诉了编译器每次获取多少bit的内存——而字对齐的获取方式往往是最快速的

结构体的大小就采用字对齐的方式进行分配:

struct foo {
	int32_t a;
	char b;
	struct foo *c;
}//12B

字节序:大端&小端

——高位编址/低位编址

将0x1234abcd写入到以0x0000开始的内存中,则结果为:

addressbig-endianlittle-endian
0x00000x120xcd
0x00010x340xab
0x00020xab0x34
0x00030xcd0x12

note:一个地址存一个字节,2位16进制数才是一个字节

若不注意字节序的话,传递一个0x12345678可能会被翻译为0x78563412

目前小端为主流,因为这样的话,数据类型转换不用考虑地址的问题(?)

比特序:MSB&LSB

Most Significant Bit/Least Significant Bit

CPU存储字节内部的8个bit同样存在顺序。如,存储0xB4(10110100):

MSB10110100
LSB00101101

MSB的CPU从左往右读,LSB的CPU从右往左读——而这并不会造成混乱,因为都是把CPU读出来的正确的数传递给对方

程序地址空间

从上至下分别为:堆栈、堆、静态区、代码区(text/code)

  • 堆栈存放函数的局部变量,初始时在栈顶,向下扩展
  • 堆存放malloc分配的内存,自动进行resize,初始时临近静态区,向上扩展
  • 静态区存放静态变量,大小始终不变
  • 代码区存放你的代码,在程序一开始运行时加载,大小始终不变

这些内存自动进行管理,无需担心;堆的变量可能需要手动free

地址从上到下为:0xFFFF FFFF ~ 0x0000 0000,其中0x00000000不可写不可读,可定义NULL指针

堆栈

每次调用一个函数就有一个stack frame被创建,每个stack frame包含:

  • 返回时回到的地址/调用该函数的地址
  • 参数
  • 存放局部变量的空间

stack是一个连续的内存空间,通过stack pointer指向;函数返回时,stack pointer指向上一个stack frame

后入先出

栈里头传递指针是ok的,但是传递局部变量的指针绝对不行

程序员管理的内存,分配时需指定需要分配的字节数,必须手动free

——非连续性的分配:否则可能会出现两块已分配内存间隔非常远的情况

建议使用sizeof,不同的机器int大小可能不同——产生bug

typedef struct { ... } TreeNode; 
TreeNode *tp = (TreeNode *) malloc(sizeof(TreeNode)); 

int *ptr = (int *) malloc(20*sizeof(int));
... // check for NULL 
free(ptr); // implicit typecast to (void *)

// void *malloc(size_t n)
// void free(void *ptr)

注意,free时一定要确保free正确的地址

realloc

时常检查NULL是必须的——因为当你耗尽内存的时候,返回的指针是NULL

——堆数据可能会被分配到新地址,原数据会被复制过去

// void *realloc(void *ptr, size_t size)
int *ip; ip = (int *) malloc(10*sizeof(int));
… … … // check for NULL, set 10 ints 
ip = (int *) realloc(ip, 20*sizeof(int));
// contents of first 10 elements retained 
… … … // check for NULL 
realloc(ip,0); // equivalent to free(ip)
堆的多种bug
  • 内存泄漏(忘记free):程序可能会变得越来越慢直到崩溃
  • free后使用:读取到错误信息(如果被占用)/非法修改数据
  • 二次free:free后使用bug/使堆数据崩溃(?)
  • 忘记realloc可能会移动数据
  • free堆栈数据,free非malloc产生的指针,malloc后忘记类型转换,不检查NULL…

内存管理的实现

我们希望实现快速的malloc和free,最小化的内存开销,同时避免内存的碎片化使用(块大小104,但是只是用了100)

K&R Implementation

每个堆块都有一个header,记录了块的大小、指向下一个块的指针

所有的free过的block被保存在一个循环链表中——而被分配的block的指针域被弃用

malloc

在空闲的block中寻找一个足够大的块——若不存在则向操作系统请求内存——还是不够,就会失败

如果有很多足够大的块该如何选择?

  • best-fit:选择最小的
  • first-fit:选择第一个找到的——足够快,但是有很多碎片
  • next-fit:选择 从上一次分配空闲的地方开始查找到的 第一个块
free

会检查相邻的块是否也free,是的话就会融合为一个更大的块;否则直接加入free list

链表示例

struct Node {
 	char *data;
 	struct Node *next;
};
int main() {
 	struct Node *head = NULL;
 	add_to_front(&head, "abc");// free nodes, strings here…
}
void add_to_front(struct Node **head_ptr, char *data) {
 	struct Node *node = (struct Node *) malloc(sizeof(struct Node));
 	node->data = (char *) malloc(strlen(data) + 1); // 字符串需要+1
 	strcpy(node->data, data); // strcpy also copies null terminator
 	node->next = *head_ptr;
 	*head_ptr = node;
}

后记

以上就是本文的全部内容啦,欢迎大家交流

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值