一生一芯
一生一芯做到了正式阶段,当然,预学习阶段的东西也忘得差不多了.
看PA2视频
我直接进行一个视频的嗯看,虽然动辄一个小时的视频,确实很难顶 可以结合ddca(digital design and computer archteture)的课一起看?
再次学习copilot的使用``
’tab’,自动用灰色文本建议, ‘alt’+’]’/’[‘切换建议
使用ctrl+enter在新选项卡中
点击accept solutions–>即可
使用copilot chat
ctrl+shift+p–>copilot chat
了解git的原理
每次都卡死在这里,真的是无无又奈奈啊
数字逻辑电路: 模拟器
#define REGS_FOREACH(_) _(X) _(Y)
#define RUN_LOGIC X1 = !X && Y; \
Y1 = !X && !Y;
#define DEFINE(X) static int X, X##1;
#define UPDATE(X) X = X##1;
#define PRINT(X) printf(#X " = %d; ", X);
int main() {
REGS_FOREACH(DEFINE);
while (1) { // clock
RUN_LOGIC;
REGS_FOREACH(PRINT);
REGS_FOREACH(UPDATE);
putchar('\n'); sleep(1);
}
}
X-macro:好处和坏处? X-macro是C语言中的一种编程技巧,它使用预处理器宏来生成重复的代码。X-macro的名字来源于它的典型用法,即定义一个宏,这个宏接受一个参数(通常被命名为X),然后在一系列的代码行中重复使用这个参数。
例如,你可能会看到像这样的X-macro定义:
#define LIST_OF_VARIABLES(X) \
X(Variable1) \
X(Variable2) \
X(Variable3)
然后,你可以使用这个X-macro来生成一系列的代码行,例如:
#define DECLARE_VARIABLE(V) int V;
LIST_OF_VARIABLES(DECLARE_VARIABLE)
这将会展开为:
int Variable1;
int Variable2;
int Variable3;
X-macro的优点是它可以减少重复的代码,并且使得添加、删除或修改列表中的元素变得非常简单。只需要在LIST_OF_VARIABLES
宏中添加、删除或修改元素,然后所有使用这个列表的代码都会自动更新。
然而,X-macro的缺点是它可能会使代码变得难以理解和维护,特别是对于不熟悉这种技巧的开发者来说。此外,由于它依赖于预处理器,因此它可能会导致一些难以调试的问题,例如宏展开错误或者名称冲突。
Vim中执行外部命令
在 Vim 中,你可以使用 :!
命令来执行外部命令。在你的例子中,你想要编译当前的 C 文件并运行它。以下是如何做到这一点:
首先,确保你已经保存了你的文件。你可以按
:w
来保存。然后,输入
:!gcc % && ./a.out
。这个命令做了两件事:gcc %
:这将使用 gcc 编译器来编译当前文件(%
在 Vim 中表示当前文件)。如果编译成功,gcc 将生成一个名为a.out
的可执行文件。./a.out
:这将运行刚刚编译的程序。注意,&&
是一个 shell 命令,它表示“如果前一个命令成功执行,那么执行后一个命令”。
按回车键执行命令。
gcc -E a.c -o a.i,会看到预处理后的文件
int main() {
static int X, X1; static int Y, Y1;;
while (1) {
X1 = !X && Y; Y1 = !X && !Y;;
printf("X" " = %d; ", X); printf("Y" " = %d; ", Y);;
X = X1; Y = Y1;;
putchar('\n'); sleep(1);
}
}
更完整的实现: 数码管显示
import fileinput
TEMPLATE = '''
\033[2J\033[1;1f
AAAAAAAAA
FF BB
FF BB
FF BB
FF BB
GGGGGGGGGG
EE CC
EE CC
EE CC
EE CC
DDDDDDDDD
'''
BLOCK = {
0: '\033[37m░\033[0m', # STFW: ANSI Escape Code
1: '\033[31m█\033[0m',
}
VARS = 'ABCDEFG'
for v in VARS:
globals()[v] = 0
stdin = fileinput.input()
while True:
exec(stdin.readline())
pic = TEMPLATE
for v in VARS:
pic = pic.replace(v, BLOCK[globals()[v]]) # 'A' -> BLOCK[A], ...
print(pic)
需要搜索一下ANSI Escape Code(ANSI转义码)(chatgpt写博客也很爽)
你还体验了 UNIX 哲学 Make each program do one thing well Expect the output of every program to become the input to another
Vim给多行添加空格
- 按
Ctrl + v
进入可视块模式。 - 使用
j
或k
键选择你想要添加空格的行。 - 按
Shift + i
进入插入模式。 - 输入空格。
- 按
Esc
键退出插入模式。 以上是错误的,这是chatgpt在中文互联网上抓取的知识(不知道为什么用英文问chatgpt它的答案也是错的,大概是因为英文上抓取的也是错误,因为我看到浏览器首页的答案就是错误的那个) 正确的是:通过stackchange上的答案 - 按
Ctrl + v
进入可视块模式。 - 使用
j
或k
键选择你想要添加空格的行。 - 输入:’<’> normal I 注意space后要添加空格
jyy的小技巧
在 Linux 系统中,/tmp
目录是用于存储临时文件的地方。这个目录通常被系统管理员配置为可以在每次系统启动时清空,以确保不会在其中积累太多的临时文件。以下是有关 /tmp
目录的一些重要信息:
用途:
/tmp
目录是用来存储临时文件和临时目录的地方。这些文件和目录通常只在当前会话中有用,不会保留在系统上。定期清理: 大多数 Linux 发行版会在每次系统启动时清空
/tmp
目录。这是通过在启动过程中运行脚本来实现的,该脚本负责删除/tmp
中的所有内容。权限:
/tmp
目录通常对所有用户开放写权限,以便任何用户都可以在其中创建临时文件。这是通过设置目录权限为 1777(drwxrwxrwt
)来实现的,其中 “t” 标志表示粘滞位(sticky bit)。临时文件的生命周期: 由于
/tmp
目录在系统启动时被清空,因此不应该在其中存储需要持久保存的文件。如果需要长期存储的文件,请使用其他目录。其他位置: 除了
/tmp
目录外,有些系统还可能有/var/tmp
目录,用于存储较为持久的临时文件,不会在系统启动时被清空。
总的来说,/tmp
目录是一个用于存储短暂和临时文件的标准位置,但请注意,不要在其中存储需要持久保存的数据。jyy每次讲课都是在这里面写它仅作展示的文件
C程序的语义
C程序的状态机模型
状态机的一个视角
- 状态 = 堆 + 栈
- 初始状态 = main的第一条语句
- 迁移 = 执行一条简单语句
递归的汉诺塔
void hanoi(int n, char from, char to, char via) {
if (n == 1) printf("%c -> %c\n", from, to);
else {
hanoi(n - 1, from, via, to);
hanoi(1, from, to, via);
hanoi(n - 1, via, to, from);
}
return;
}
非递归汉诺塔
typedef struct {
int pc, n;
char from, to, via;
} Frame;
#define call(...) ({ *(++top) = (Frame) { .pc = 0, __VA_ARGS__ }; })
#define ret() ({ top--; })
#define goto(loc) ({ f->pc = (loc) - 1; })
void hanoi(int n, char from, char to, char via) {
Frame stk[64], *top = stk - 1;
call(n, from, to, via);
for (Frame *f; (f = top) >= stk; f->pc++) {
switch (f->pc) {
case 0: if (f->n == 1) { printf("%c -> %c\n", f->from, f->to); goto(4); } break;
case 1: call(f->n - 1, f->from, f->via, f->to); break;
case 2: call( 1, f->from, f->to, f->via); break;
case 3: call(f->n - 1, f->via, f->to, f->from); break;
case 4: ret(); break;
default: assert(0);
}
}
}
这样的思路可以将任何递归程序转化成非递归,但我们先把这份代码弄懂先
在C语言中,__VA_ARGS__
是一个预处理器宏,它用于表示可变参数的列表。这个宏通常在定义接受可变参数的宏时使用。(还有哪些预处理器宏? FILE LINE func)例如__LINE__
是 C 和 C++ 中的一个预处理宏,它会在编译时被替换为源代码中的当前行号。这个宏可以用于记录或输出代码中的行号信息,通常用于调试、错误报告等目的。
以下是一个简单的示例:
#include <stdio.h>
int main() {
printf("This message is from line %d.\n", __LINE__);
return 0;
}
在上述代码中,__LINE__
会被替换为该行的行号。当程序运行时,输出将是类似于 “This message is from line 5.” 的内容,其中 5 是 printf
语句所在的行号。
__LINE__
是在预处理阶段执行的,因此它不会在运行时保留行号信息。这使得它在生成错误消息、调试输出等情况下非常有用,可以帮助开发者追踪和定位代码中的问题。
例如,你可以定义一个名为PRINTF
的宏,它接受一个格式字符串和任意数量的额外参数,然后将这些参数传递给printf
函数:
#define PRINTF(format, ...) printf(format, __VA_ARGS__)
然后,你可以像这样使用这个宏:
PRINTF("Hello, %s!\n", "world");
这将展开为:
printf("Hello, %s!\n", "world");
在你的代码中,__VA_ARGS__
被用于call
宏的定义中,表示call
宏可以接受任意数量的参数,并将这些参数用于初始化Frame
结构体的实例。(variable arguments可变参数列表)
call指令的实现
#define call(...) ({ *(++top) = (Frame) { .pc = 0, __VA_ARGS__ }; })
先top指针自增再把Frame结构体的实例赋值给top指针指向的内存
状态机的一个视角
状态 = 内存M + 寄存器 R
gdb调试的适合可以使用layout asm
来查看汇编代码
可以使用layout src
来查看C代码
也可以使用layout split
来查看汇编代码和C代码
既可以是C代码视角也可以是机器指令的视角
操作系统上的程序
- 所有的指令都只能计算
- deterministic: mov,add,sub,call ,…
- non-deterministic: jmp,ret,syscall,…
- 但这些指令甚至都无法使程序停下来(NEMU: 加条trap指令)
rdrand $rax
是一条x86汇编指令,用于生成一个随机数并将其存储在rax
寄存器中。
rdrand
指令是Intel在其处理器中实现的一种硬件随机数生成器。它可以生成高质量的随机数,适用于加密和安全相关的应用。
这条指令的使用需要硬件支持。你可以通过检查CPU的特性标志来确定是否支持rdrand
指令。在Linux系统中,你可以通过查看/proc/cpuinfo
文件来检查这个特性标志。
请注意,rdrand
指令生成的随机数并不适合所有的应用。在某些情况下,你可能需要使用其他的随机数生成方法,例如使用软件的伪随机数生成器,或者使用其他的硬件随机数生成设备。
在GDB中,watchpoint是一种特殊的断点,用于监视表达式的值的变化。当表达式的值发生变化时,程序会暂停执行。
你可以使用watch
命令来设置一个watchpoint。例如,如果你想要监视变量x
的值,你可以使用以下命令:
watch x
这样,每当x
的值发生变化时,程序就会暂停执行,你就可以查看x
的新值以及导致x
值变化的代码。
你也可以使用rwatch
命令来设置一个read watchpoint,当程序读取指定表达式的值时,程序会暂停执行。
还可以使用awatch
命令来设置一个access watchpoint,当程序读取或写入指定表达式的值时,程序会暂停执行。
要查看当前设置的所有watchpoint,你可以使用info watchpoints
命令。
要删除一个watchpoint,你可以使用delete
命令,后面跟上watchpoint的编号。例如,delete 1
会删除编号为1的watchpoint。
一条特殊的指令
调用操作系统syscall
- 把(M,R)完全交给操作系统,任其修改
- 一个有趣的问题: 如果程序不打算完全信任操作系统,怎么办?
- 实现与操作系统中的其他对象交互
- 读写文件/操作系统状态(例如把文件内容写入M)
- 改变进程的状态,例如创建进程/销毁自己 程序 = 计算 + syscall
- 问题: 怎么构造一个最小的Hello world?
gcc --verbose hello.c
和gcc -static hello.c
- –verbose 可以查看编译选项(可真不少)
- printf变成了puts@plt
- -static会复制libc
直接硬来
强行编译+链接: gcc -c+ld +