概览

课件

pdf 文件

课程连接

https://www.bilibili.com/video/BV1iW411d7hd?p=1

概览 overview

2024040323050517.png

课程主题 course theme

2024040323053228.png
抽象是好的,但不要忘记现实

五个真实的场景

场景一 整数不是整型,浮点数不是实数

2024040323174245.png
整数不是整型,浮点数不是实数

计算机算术

2024040514001305.png

场景二 你需要了解汇编

2024040514083685.png
你需要了解汇编

场景三 随机存取存储器是一种非物理抽象

2024040514122354.png
随机存取存储器是一种非物理抽象

内存引用错误示例

2024040514150371.png

#include <stdio.h>
 
typedef struct {
int a[2];
double d;
} struct_t;
 
double fun(int i);
 
int main() {
double result0 = fun(0);
printf("%.12f\n", result0);
double result1 = fun(1);
printf("%.12f\n", result1);
double result2 = fun(2);
printf("%.12f\n", result2);
double result3 = fun(3);
printf("%.12f\n", result3);
double result4 = fun(4);
printf("%.12f\n", result4);
double result5 = fun(5);
printf("%.12f\n", result5);
double result6 = fun(6);
printf("%.12f\n", result6);
return 0;
}
 
double fun(int i) {
volatile struct_t s; // volatile关键字告诉编译器不应优化与此类型的变量相关的任何操作。这在多线程环境中,或涉及硬件交互的位置,比如内存映射的硬件寄存器,特别重要。
s.d = 3.14;
s.a[i] = 1073741824; // 可能超出范围
return s.d;
}

结果:

3.140000000000
3.140000000000
3.139999866486
2.000000610352
3.140000000000
3.140000000000
3.140000000000

2024040514293884.png

在给出的结构中,a 是一个整数数组,a[0] 就是数组的第一个元素。
a[0] 的类型是 int。在大多数的现代系统中,int 通常是 4 字节(32 位),但这是依赖于具体的编译器和硬件的。如果你需要确定 int 的字节大小,一个常见的方式是使用 sizeof 运算符,例如 sizeof(int)。
所以,a[0] 大概占用 4 字节,但可能会随着不同的编译器和硬件而变化。

a 中的每个元素都是 4 个字节,d 中的每个元素都是 8 个字节。

在内存中,数组元素会被连续地存储起来。在你提供的代码中,struct_t 有一个整型数组 a 和一个双精度浮点数 d。这个结构的内存布局具体取决于编译器以及目标硬件架构的内存对齐规则。

对于一个典型的 32 位的整数和 64 位的双精度浮点数(这取决于具体的编译器和硬件),可能的内存布局如下:

| a[0] (4 bytes) | a[1] (4 bytes) | padding (4 bytes) | d (8 bytes) |

请注意,我在中间插入了一个 4 字节的填充(padding)。这是因为一些硬件需要(或更高效的进行)特定类型的数据与特定的内存地址对齐(比如,双精度浮点数 d 需要与 8 字节边界对齐),所以编译器可能会自动做这样的填充。

在具有内存越界和填充的系统中,如果你试图为 a[2] 赋值,则可能会写入 d 的内存位置或其填充范围内,并导致 fun() 函数返回的值发生更改。这是未定义行为,它的结果可能因不同的编译器和优化选项而异。

这个结构体的具体布局可以通过结构体内存布局或内存对齐插入查询的 C 或 C++ 库来查看,但这超出了你的问题的范围。

总的来说,访问数组 a 的下标 2 是越界操作,因为数组 a 只定义了两个元素 a[0] 和 a[1],这可能会导致一些不可预知的行为,尤其是在 s 是 volatile 的时候。

建议:不要读取或写入数组的界限以外的数据,因为这可能会导致一些难以预测和诊断的错误。

2024040514501019.png

场景四 性能优于渐进复杂度

2024040515034895.png
性能优于渐进复杂度

2024040515074598.png

2024040515172253.png

这两个函数的区别在于它们访问数组的顺序。

copyij 函数是沿着每一行从左到右走,走完一行再下到下一行,就像我们阅读文字一样。首先看完第一行的内容,然后再到下一行,再下一行等等。

copyji 函数则是沿着每一列从上到下走,走完一列再移到下一列,就像雨水从上往下落,然后再从左到右扩展。

在计算机的内存中,先走行的方法(也就是 copyij 做的事情)是最有效的,因为计算机先存储数组的第一行,然后是第二行,以此类推。这使得计算机能更好地预测你将要看哪一块数据,并把它提前取到手边,这样就把等待数据的时间缩短了。

然而在 copyji 函数中,它是一列列的对数组进行操作,这使得计算机取数据的过程效率变低,因为每次都只取一个,而不是一大块,这样就要花更多的时间等待数据被取出来。这就好比你在一个图书馆里,一次只拿一本书而不是一叠书,你就需要多次跑动来回才能取齐。而 copyij 就好比你一次性取了一整叠书,所以要跑的次数就少了。

场景五 计算机不仅仅是执行程序

2024040515210151.png
计算机不仅仅是执行程序

课程体系

2024040516205224.png

课程前景

2024040516245563.png

课程用书

2024041318302245.png

课程组成部分

2024041318542895.png

教学大纲

程序和数据

2024041322422182.png

内存层次结构

2024041322441826.png

独特的控制流

2024041322471087.png

虚拟内存

2024041322483090.png

网络和并发

2024041322520648.png