学校上过数学系的 C 课程, 上机 VC 操作做课堂作业过, 后来就一直动态类型语言.. 直到学 Go 类型卡住了.. 被学长带着重学 C, 以下是开头的笔记:
10 个人的性别信息, 最少用多少 bits 来存储?
实际计算机最小的处理单位是 8bits.
最少是 16bits, 顺序 10bits 的 01 来表示性别.
假如还有变性人, 还有每个人性别位置, 多少位?
每个人 5 种情况 (5^10)bits,
> 5 ^ 10 / 8 / 8 / 8 / 8 / 8 / 8 / 8
4.656612873077393
(8*8)bits
编程语言里只能操作四种位数, 8, 15, 32, 64bits. 就算用到只需要 20bits, 还是会用到 32 bits. 属于硬件设计范围.
表示 34 个数字, 需要多少 bits, 34 种不同的情况, 只要能区分?
34 个不同情况需要 64bits
8bits 能表示多少数字?
256, 能够表示 0~255, 非负是 -127~128
-1 的表示是 11111111, -1 会用来表示最大
未知大小的数据, 通过一个字典, 每个字符串一个需要, 然后按序号取. 空间可以动态重新申请, 然后获取地址, 这样就可以表示很大的数据.
34bits 的内存不能表示 4G 以上的地址. 因为 32bits 能表示 1024 * 1024 * 4.
存储 100*32bits 的数据, 先在堆里动态申请内存, 然后记录地址, 每次 new 新建对象, 都申请到固定大小的内容, 然后存储地址, 引用, 指针.
32bits 电脑的指针是 32bits 的, 引用对象的值表示地址. 如果引用对象的值是 0, 那么就是 null
JS undefined 是有指向内存, 但内存没有数据, 而 null 是地址都是 0.
系统会提供虚拟地址. 进程的虚拟地址是从 1 开始的, 0 在各种语言里都是 null.
打印一下 Go 里的地址...
package main
import (
"fmt"
)
func main() {
a := 1
fmt.Println(&a)
}
C 不保存类型的信息.
值类型比引用类型更快, 因为引用类型都会涉及到内存申请. 坑爹的 Python 全是引用类型...
申请的内存要记得释放.. 或者垃圾回收..
内存有四块区域
- 程序区
存放程序本身, 汇编代码, 操作系统管理的内存. 程序不可修改, 可以读取. 进程的内存是连续的, 可以通过计算推测.
存放指令和操作数. 比如 1 + 1
是 ld 1, ld 1, add
.
变量就是寄存器的地址. 1 + a
是载入 1 和 a 的地址. 基础类型可以直接在程序里用, 不需要内存. ld 1, lda 00001, add
- 静态数据区
存放程序一定会用到的内些固定数据. 比如字符串常量, 但数字可以直接在程序区用, 而字符串不能出现在程序区.
需要在编译时能得到. 运行时动态的数据没法知道.
- 栈
存放程序函数和局部变量. 栈的大小固定, 一般为 2m, 从栈空开始, 先进后出的. 是人为划分的一块连续虚拟内存. 操作类似 push pop.
int i = 4
这样就会进栈 4.
函数执行完毕函数内的栈会被清空, 不同的函数之间不会干扰.
{
和 }
表示一个块级作用域, 其中标记了进栈和清栈的范围. for 循环的 {
和 }
是有块级作用域的.
一个进程只有一个栈. 函数执行时, 参数按照逆序先进栈, 函数执行完毕后包括参数一起退栈, 但写入一个返回值进栈. 逆序是约定为了读取方便.
变量赋值会在原先的数据上写, 而不会重新进栈.
栈是编译器管理的, 程序员不能处理, 但用 api alloca
可以申请内存
- 堆是完全给程序控制的, 动态内存
使用 API alloc
和 free
.
alloc 的参数是申请内存的字节数. 返回值是申请的内存
比如申请字符串 "hello world"
, 代码是:
char[] c = alloc(11*2)
这时 char 类型的长度对应是 2, 采用 utf16 编码. 然后进行赋值:
cc[0] = 'h';
cc[1] = 'e';
然后拷贝字符串:
char[] c1 =alloc (11*2)
cc[0] = 'h';
cc[1] = 'e';
// ...
char[] c2 =alloc (11*2)
strncpy(c2, 11, c1)
最后释放内存:
free(c1);
free(c2);
所以字符串拼接很慢.. 涉及到创建和拷贝.
拷贝字符串就...
char[] r1="ss"
char[] r2="ss"
char[] r3 = alloc (2 * (strlen(r1) + strlen(r2)))
strcopy(r3, strlen(r1), r1)
strcopy(r3 + strlen(r1), strlen(r1), r2)
申请一段内存, 然后扩展长度的伪代码:
char[] a = alloc (2)
char[] b = alloc (6)
strncpy(b, 2, a)
free(a)
a = b
更通用的 API memcpy(dest,size,src)
如何遍历输出当前函数下所有变量的值?
思路, 在开头结尾取内存地址, 然后遍历:
int* start =alloca(4)
int* end =alloca(4)
指针可以加减进行偏移, 根据不同的类型不同大小的便宜:
int 类型的移动 4bits, char 移动是 2bits.
没有具体类型的 void* 不能移动, 因为没有大小信息.
char 可能有多种长度, wchar_t.
常用的长度和类型的对应:
8 bool byte sbyte
16 char short ushort
32 int float uint
64 long double ulong
? void object string
单位是 bits.
字符串表示有 ASSIC, UTF8, UTF16 多种类型不同的长度.
clang 使用, clang code.c
对代码进行编译.
unsigned int 在 C 里可以赋值 -1, 而不会报错; 对应 Go 的 uint 赋值 -1 将会报错.
因为 C 没有做多余的检查, 对应机器真实的操作.
写盗号是通过分析内存中数据的位置, 推算变量占用的空间, 通过手动操作指针获取数据得到的.
安全的编程语言会关闭这种可能性.
通过指针实现多返回值.
Point p = {1, 2, 3};
Struct 类型, 数据是存在栈上的, 因为 p 是变量, 变量是存放在栈上的.
这里的 p 对应数据长度固定, 整个都是存在放栈上的.
Stuct 类似数组, 名称很大程度是语法糖, *pb
就是 pb[0]
p.x
和 (&p)->x
是一回事, 指针使用 ->
, 非指针使用 .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。