CS61C Lec 1 - Number Representation, C
0 写在前面
没有了textbook,写起笔记还挺不方便的,或者说显得不那么系统规范了
1 Number Representation
需要提及的是,这里再次说到了
You can add/subtract/multiply two’s complement numbers as if they were unsigned. You’ll magically get the right answer (Not really magical, just mathematical).
例如,以4位为例,计算-2+1,即1110 + 0001,按无符号数算(这里的意思应该是以位级表示去算),得到1111,而-1的补码表示就是1111,这其实也是csapp里有时回避有时提及的问题,无论是直接对1110和0001作二进制加法,还是把它们看成无符号数14 + 1得到15再得到位级表示1111,最终结果截断成4位后,位级表示一定和把1110和0001按补码理解得到的结果的位级表示是一样的
Bias Notation
这种表示下,位数,0b000…00(0b表示二进制数)是最小的数,它表示,而0b111…11是最大的数,表示
standard bias是,以3位为例,0b001表示哪个十进制数?
0b001按unsigned理解是1,然后加上即可,即表示-2
反过来,问-2在standard bias下,它的3-bit biased notation是什么,那么先减去bias得到1,再用unsigned写出来,即0b001
2 C Basics
目前看了一节,还好学校学过C……这video的详细程度和语速都比CS61B不友好了不少啊……
2.1 Compilers (vs. Interpreters)
2.2 C vs. Java


内容有点杂,先记部分吧,或许之后的lab会详细些?
The C standard doesn’t define the absolute size of integer types, other than char.
It only guarantees relative sizes
sizeof(long long)sizeof(long)sizeof(int)sizeof(short)
int size depends on computer, what integer type is most efficient.
为了可移植性,使用C99引入的stdint.h
1 | int8_t uint8_t // 8 位 |
2.3 Pointer
想象内存是一个无限大的数组,每一个格子有一个编号,这就是它的地址,格子里面可以放值(value),指针就是用来记录地址的
1 | int *p; // Tells compiler that variable p is address of an int. |
我们可以用指针做些事
1 | void addOne (int x) { |
函数指针
1 |
|
Pointers and Structures
1 | typedef struct { |
Word Alignment

sizeof(type) returns number of bytes in object, including any padding(填充的) needed for alignment.
By Standard C99 definition, sizeof(char) == 1
2.4 Arrays
1 | int arr[] = {0, 1}; |
An array variable is a “pointer” to the first element, it can’t be changed.
arr[0] is the same as *arr, arr[1] is the same as *(arr + 1) (当然,这里+2并不是说地址直接+2,即多2个bytes,而是根据类型来决定,例如上例中,假设arr的两个元素的地址是0x100和0x104,那么arr + 1其实得到的是0x104,即arr + 1 * sizeof(int))
Declared arrays are only allocated while the scope is valid.
1 | char *foo() { |
2.5 Memory Management
void *malloc(size_t n) allocates a block of uninitialized memory:
size_t nis an unsigned integer type big enough to “count” memory bytes.- Returns
void *pointer to block of memory on heap. - A return of
NULLindicates no more memory (always check for it !).
1 | typedef struct { |
void free(void *ptr) dynamically frees heap memory.
ptr is a pointer containing an address originally returned by malloc() or realloc().
When you free memory, be sure to pass the original address returned from malloc(). Otherwise, crash (or worse!)
void *realloc(void *ptr, size_t size) resizes a previously allocated block at ptr to a new size.
- Returns new address of the memory block (In doing so, it may need to copy all data to a new location.).
realloc(NULL, size);behaves likemallocrealloc(ptr, 0);behaves likefree
1 |
|
举个例子
1 | struct Node { |
2.6 Memory Location
Struct declaration doesn’t allocate memory. Variable declaration does allocate memory.
So far we’ve talked about several different ways to allocate memory for data:
- Declaration of a local variable
int i; struct Node list; char* string; int arr[5]; - Dynamic allocation at runtime by calling allocation funcion
ptr = malloc(sizeof(struct Node) * n); - One more possibility is that data declared outside of any procedure. It has “global” scope.
1 | int myGlobal; |
C has 3 pools of memory for variables
- Static Storage: global/static variable storage, basically permanent, entire program run.
- The Stack: local variable storage, parameters, return address.
- The Heap (dynamic malloc storage): data lives until deallocated by programmer.

Where are variables allocated?
Global: If declared outside a function, allocated in data (static) storage.
Local: If declared inside function, allocated on the stack and freed when function returns. In C, a local variable has automatic storage duration, which means that its lifetime begins when execution enters the block where it is declared and its lifetime ends when execution leaves that block or function.
For both these memory types, the management is automatic. You don’t need to worry about deallocating when you’re no longer using them. But a variable doesn’t exist anymore once a function ends.
2.7 Stack and The Heap

In a typical RISC-V-style calling convention:
-
The stack grows downward.
-
On function entry, the function decreases the stack pointer
spto allocate a new stack frame. -
On function exit, it increases sp back to restore the caller’s frame.
When a function returns, its stack frame is “freed” in the sense that the stack pointer moves back to the caller’s frame. The memory is now considered available for future function calls.
But the old bytes are usually still there.
They are no longer valid for your program to use, but they may still physically exist in the stack memory until something else overwrites them.
So after the function returns, the local variable is logically dead. The memory location may still contain the old bit pattern. But the program no longer has the right to access it through that variable or through a pointer to it.
The heap is dynamic memory – memory that can be allocated, resized, and freed during program runtime.
- Useful for persistent memory across function calls.
- But biggest source of pointer bugs, memory leaks, …
- Similar to Java
newcommand allocates memory…but with key differences below. - Huge pool of mem (usually >> stack), but not allocated in contiguous order.
- Back-to-back (紧接的) requests for heap memory could result in blocks very far apart
2.8 When Memory Goes Bad
1 | int* ptr() { |
What Happens
-
yis a local variable. It lives inptr’s stack frame. -
When
ptrreturns, its stack frame is deallocated: the stack pointer moves back. -
Deallocation does not erase memory. The bytes for y may still contain 3.
-
So the first
*stackAddrmay print 3.
Why the Second Print Changes
-
Calling
printfcreates a new stack frame. -
This new frame reuses the same stack memory where
yused to be. -
printfoverwrites that memory with its own data. -
So the second
*stackAddrreads garbage, e.g. -858993460. -
-858993460 is 0xCCCCCCCC, a common debug fill pattern in MSVC, but it is not guaranteed (
printf返回后填充了0xCCCCCCCC).
补充:
-
整个程序(每个线程)只有一个栈。
-
函数调用是在同一个栈上叠加栈帧 (stack frame),不是创建新栈。
-
栈帧里只有返回地址、保存的寄存器、参数、局部变量。
-
堆、数据段、代码段是进程级别的独立区域,不属于栈帧。
-
函数可以访问堆和数据段,但它们是全局共享的。
常见问题:
free两次realloc可能移动位置,不能再用原来的地址了p是malloc得来的,p++而不备份p原来的地址,没法free了
3 C Generics
A function pointer is a variable storing the starting address of a function. Functions live in the code/text area. Function pointers allow us to define higher-order functions, such as map, filter, and generic sort.
Generics is short for generic functions.
3.1 Why Generic Functions?
We want to write general-purpose code.
- Generic code reduces code duplication and means you can make improvements and fix bugs in one place rather than many.
- Generics are used throughout C for functions to sort any array, search any array, free arbitrary memory, and more.
In general, generics:
- Should generally work regardless of argument type.
- Update blocks of memory, regardless of data type stored in those blocks.
3.2 Writing Generics
1 | void swap_int(int* p1, int* p2) { |
解引用指针时,我们必须在compile time时就知道需要从内存访问的字节数
1 | int main() { |
void 是无类型,编译器不知道 p 指向的数据有多大、是什么类型,所以 不允许 *p
1 | /* Copy n bytes from memory area src to memory area dest. */ |
void* dest目标内存地址,指向要写入的地方。void*表示“任意类型指针”,因为memcpy只按字节复制,不关心数据类型。const void* src源内存地址,指向要读出的地方。const表示memcpy不会修改src指向的数据。size_t n要复制的字节数。size_t是无符号整数类型,通常用来表示大小、长度
要复制的源数据块,和你写入的目标数据块,不能有任何一部分是同一块内存。如果它们有交集,就叫 overlap(重叠)
如果memory areas overlap了,就要使用memmove,但memcpy是更快的
1 | /* |
这样我们就能写出通用的swap
1 | void swap(void* ptr1, void* ptr2, size_t nbytes) { |
下面我们实现一个函数,它接受一个数组并交换数组的第一个元素和最后一个元素
1 | void swap(void* ptr1, void* ptr2, size_t nbytes); |


