<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>NknSのSitE</title><description>Ich Liebe Dich —— Kiana Kaslana</description><link>https://nkns.cc</link><item><title>Verilog Notes</title><link>https://nkns.cc/notes/verilog/verilog-notes</link><guid isPermaLink="true">https://nkns.cc/notes/verilog/verilog-notes</guid><description>WTF I didn&apos;t learn anything!</description><pubDate>Mon, 27 Apr 2026 20:04:10 GMT</pubDate><content:encoded>&lt;h1&gt;Verilog HDL&lt;/h1&gt;
&lt;h2&gt;Chapter I 简介&lt;/h2&gt;
&lt;h4&gt;什么是 Verilog HDL?&lt;/h4&gt;
&lt;p&gt;Verilog HDL 是一种硬件描述语言，可以完整描述电路和算法，方便仿真。&lt;/p&gt;
&lt;p&gt;Verilog HDL具有以下描述能力：设计的行为特性、设计的数据流特性、设计的结构组成、包含相应监控和设计验证方面的时延和波形产生机制。&lt;/p&gt;
&lt;h3&gt;主要能力&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;基本逻辑门，例如 &lt;strong&gt;and&lt;/strong&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;strong&gt;nand&lt;/strong&gt; 等&lt;/li&gt;
&lt;li&gt;用户定义原语 UDP 可以是组合逻辑原语，也可以是时序逻辑原语&lt;/li&gt;
&lt;li&gt;开关级基本结构模型，例如 &lt;strong&gt;pmos&lt;/strong&gt; &lt;strong&gt;nmos&lt;/strong&gt; 等&lt;/li&gt;
&lt;li&gt;通过三种方式建模：行为描述、数据流描述、结构化描述&lt;/li&gt;
&lt;li&gt;有两种基本数据类型：线网类型 &lt;code&gt;wire&lt;/code&gt; 和寄存器类型 &lt;code&gt;reg&lt;/code&gt; 。线网类型表示物理连线，寄存器类型表示抽象数据存储原件&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Chapter II HDL 指南&lt;/h2&gt;
&lt;h3&gt;模块&lt;/h3&gt;
&lt;p&gt;模块是 Verilog 的基本描述单位，用于描述某个设计的功能或者结构，以及它与外界通信的端口（类似于提供函数的参数）。一个设计的结构可以使用开关机原语、门级原语和用户定义的原语方式描述，设计的数据流行为可以使用连续赋值语句进行描述； 时序行为使用过程描述。&lt;/p&gt;
&lt;p&gt;模块可以被另一个模块使用。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module module_name (port_list);
	Declarations:
		reg, wire, parameter,
		input, output, inout,
		function, task, ...
	Statements:
		Initial statement
		Always statement
		Module instantiation
		Gate instantiation
		UDP instantiation
		Continuous assignment
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明部分：用于定义不同的项。例如模块描述中使用的参数和寄存器。语句定义设计的功能和结构。说明部分和语句可以散步在模块中的任何地方；但是变量、寄存器、线网和参数等的说明部分必须在使用前出现。&lt;/p&gt;
&lt;p&gt;为了使得模块描述清晰和有良好的可读性，最好&lt;strong&gt;将所有的说明部分放在语句前&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module HalfAdder (A, B, Sum, Carry);
    input A, B;
    output Sum, Carry;
    
    assign #2 Sum = A ^ B;
    assign #5 Carry = A &amp;#x26; B;
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，这个模块的名字是 &lt;code&gt;HalfAdder&lt;/code&gt; 。模块有四个接口：两个输入端口 A 和 B，两个输出端口 Sum 和 Carry。由于没有定义端口位数，所有端口大小都为 1 位；同时因为没有各个端口的数据类型说明，这四个端口都是&lt;strong&gt;线网&lt;/strong&gt;数据类型。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/04/6a75086b717a203ac642a27e4c18562a.png%5D&quot; alt=&quot;image-20260427221029374&quot;&gt;&lt;/p&gt;
&lt;p&gt;模块包含两条描述半加器数据流行为的连续赋值语句，从这种意义上讲，这些语句在模块中出现的顺序无关紧要。语句是&lt;strong&gt;并发&lt;/strong&gt;的。&lt;/p&gt;
&lt;p&gt;在模块中可以用以下方式描述设计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据流方式&lt;/li&gt;
&lt;li&gt;行为方式&lt;/li&gt;
&lt;li&gt;结构方式&lt;/li&gt;
&lt;li&gt;以上三种方式的混合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几种描述方式将在后文中提到。&lt;/p&gt;
&lt;h3&gt;时延&lt;/h3&gt;
&lt;p&gt;Verilog HDL 模型中的所有时延都根据时间单位定义。下面是一个带时延的连续赋值语句示例。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign #2 Sum = A ^ B;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 &lt;code&gt;#2&lt;/code&gt; 指的是&lt;strong&gt;两个时间单位&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;使用编译指令将时间单位与物理时间相关联。这样的编译器指令需要在模块前定义。如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;` timescale 1ns / 100ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此语句说明时延时间单位为 1 ns 并且时间精度为 100 ps，即
$$
\left| t_{实际} - t_{预期} \right| \leq 100\ ps
$$
如果没有这样的编译器指令，Verilog HDL 模拟器会指定一个缺省时间单位。IEEE没有规定缺省时间单位。&lt;/p&gt;
&lt;h3&gt;数据流描述方式&lt;/h3&gt;
&lt;p&gt;该描述最基本的机制就是使用连续赋值语句。在连续赋值语句中，某个只被指派给线网变量。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign [delay] LHS_net = RHS_expression;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;右边表达式操作数无论何时&lt;strong&gt;发生变化&lt;/strong&gt;，都需要&lt;strong&gt;重新计算&lt;/strong&gt;，并且在指定的时延后赋值给左边的&lt;code&gt;wire&lt;/code&gt;变量。时延定义的是右边表达式变化与赋值给左边表达式之间的持续时间。如果没有定义时延值，缺省时延为 0。&lt;/p&gt;
&lt;p&gt;这是一个 2-4 解码器电路的描述。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`timescale 1ns/ 1ns
module Decoder2x4 (A, B, EN, Z);
    input A, B, EN;
    output [0:3] Z;
    wire Abar, Bbar;
    
    assign #1 Abar = ~A;
    assign #1 Bbar = ~B;
    assign #2 Z[0] = ~(Abar &amp;#x26; Bbar &amp;#x26; EN);
    assign #2 Z[1] = ~(Abar &amp;#x26; B &amp;#x26; EN);
    assign #2 Z[2] = ~(A &amp;#x26; Bbar &amp;#x26; EN);
    assign #2 Z[3] = ~(A &amp;#x26; B &amp;#x26; EN);
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/35ff5a56232e040e9d92000d9997b774.png&quot; alt=&quot;image-20260502135913667&quot;&gt;&lt;/p&gt;
&lt;p&gt;以反引号 ` 开始的语句是编译器指令，编译器指令 timescale 将模块中的所有时延的单位设置为 1 ns ，时间精度为 1 ns&lt;/p&gt;
&lt;p&gt;模块 Decoder2x4 有 3 个输入端口和 1 个 4 位输出端口。线网类型说明了两个连线形变量 Abar 和 Bbar 。此外，模块包含 6 个连续赋值语句。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/1b5d5751c91a47ee170103396eb91755.png&quot; alt=&quot;image-20260502143411176&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参见波形图。当 EN 在第 5 ns 变化时 ,语句 3、4、5 和 6 执行。这是因为 EN 是这些连续赋值语句中右边表达式的操作数。 Z[0] 在第 7 ns 时被赋予新值 0。当 A 在第15 ns 变化时, 语句 1、5 和 6 执行。执行语句 5 和 6 不影响 Z[0] 和 Z[1] 的取值。执行语句 5 导致 Z[2] 值在第 17 ns 变为 0。执行语句 1 导致 Abar 在第 16 ns 被重新赋值。由于Abar的改变，反过来又导致 Z[0] 值在第 18 ns 变为 1。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;连续赋值语句是并发执行的，也就是说各个语句的执行顺序与其在描述中出现的顺序无关。&lt;/p&gt;
&lt;h3&gt;行为描述模式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;initial 语句：此语句只执行一次&lt;/li&gt;
&lt;li&gt;always 语句：此语句循环执行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;只有寄存器类型数据能够在这两种语句中被赋值。寄存器类型数据在被赋新值前保持原有值不变。所有的 initial 语句和 always 语句在 0 时刻并发执行。&lt;/p&gt;
&lt;p&gt;下例为 always 语句对 1 位全加器电路建模的示例。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module FA_Seq(A, B, Cin, Sum, Cout);
    input A, B, Cin;
    output Sum, Cout;
    reg Sum, Cout;
    reg T1, T2, T3;
    always
        @ (A or B or Cin) begin
            Sum = (A ^ B) ^ Cin;
            T1 = A &amp;#x26; Cin;
            T2 = B &amp;#x26; Cin;
            T3 = A &amp;#x26; B;
            Cout = (T1 | T2) | T3;
        end
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/797efb35118c12a1c5b622a42913f352.png&quot; alt=&quot;image-20260504143306403&quot;&gt;&lt;/p&gt;
&lt;p&gt;模块 FA_Seq 有三个输入和两个输出。&lt;strong&gt;由于 Sum、Cout、T1、T2 和 T3 在 always 语句中被赋值，它们被说明为 reg 类型（是寄存器类型的一种）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;always 语句中有一个与事件控制（紧跟在@后面的表达式）相关联的 begin-end 对，这意味着只要 A、B 或 Cin 发生变化，顺序过程就执行。顺序过程结束后，always 语句继续等待事件。&lt;/p&gt;
&lt;p&gt;在顺序过程中出现的语句是过程赋值模块化的实例。模块化过程赋值在下一条语句执行前完成执行，有一个可选的时延，可以细分为两种类型：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;语句间时延：时延语句执行的时延&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;Sum = (A ^ B) ^ Cin;
#4 T1 = A &amp;#x26; Cin;	// 这条语句延迟 4 个时间单位执行
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;语句内时延：右边表达式计算与左边表达式赋值间的时延&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;Sum = #3 (A ^ B) ^ Cin;	// 右边计算结束之后等待 3 个时间单位再赋值给 Sum
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果没有定义时延，缺省为 0 时延。&lt;/p&gt;
&lt;p&gt;下面是 initial 语句中的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`timescale 1ns / 1ns
module Test (Pop, Pid);
    output Pop, Pid;
    reg Pop, Pid;
    
    initial
        begin
            Pop = 0;
            Pid = 0;
            Pop = #5 1;
            Pid = #3 1;
            Pop = #6 0;
            Pid = #2 0;
        end
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/8b07c29f42f2fab2dcae9d25ac187bda.png&quot; alt=&quot;image-20260504151028318&quot;&gt;&lt;/p&gt;
&lt;p&gt;这是对应的波形。正如上面所说，begin-end 对里面是&lt;strong&gt;顺序执行&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;结构化描述形式&lt;/h3&gt;
&lt;p&gt;在 Verilog HDL 中可使用如下方式描述结构&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;内置门原语&lt;/li&gt;
&lt;li&gt;开关级原语（&lt;strong&gt;晶体管&lt;/strong&gt;级）&lt;/li&gt;
&lt;li&gt;用户自定义原语&lt;/li&gt;
&lt;li&gt;模块实例&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过线网相互连接。下面是使用内置门原语描述的全加器实例&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module FA_Str(A, B, Cin, Sum, Cout);
    input A, B, Cin;
    output Sum, Cout;
    wire S1, T1, T2, T3;
    
    xor
    	X1 (S1, A, B),
    	X2 (Sum, S1, Cin);
    and
    	A1 (T3, A, B),
    	A2 (T2, B, Cin),
    	A3 (T1, A, Cin);
    or
    	O1(Cout, T1, T2, T3);
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;门实例由 wire 类型相连，列表中的第一个是输出，剩下的是输入。&lt;/p&gt;
&lt;p&gt;下面是一个 4 位全加器的结构描述&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module FourBitFA (FA, FB, FCin, FSum, FCout);
    parameter SIZE = 4;
    input [SIZE:1] FA, FB;
    output [SIZE:1] FSum;
    input FCin;
    output FCout;
    wire [1:SIZE-1] FTemp;
    FA_Str
    	FA1(.A(FA[1]), .B(FB[1]), .Cin(FCin), .Sum(FSum[1]), .Cout(FTemp[1])),
    	FA2(FA[2], FB[2], FTemp[1], FSum[2], FTemp[2]),
        // 相同形式一直到 FA4
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里第一个语句是&lt;strong&gt;命名关联方式&lt;/strong&gt;，即端口的名称和它连接的线网被显式地描述了，每一个的形式都是 &lt;code&gt;.port_name (net_name)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第二个语句则是&lt;strong&gt;位置关联方式&lt;/strong&gt;，这里的顺序很重要，必须和模块定义的参数顺序相同。&lt;/p&gt;
&lt;h3&gt;混合设计描述模式&lt;/h3&gt;
&lt;p&gt;在模块中，以上的描述模式可以自由混合。下面是一个混合设计方式的 1 位全加器实例&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module FA_Mix (A, B, Cin, Sum, Cout);
    input A, B, Cin;
    output Sum, Cout;
    reg Cout;
    reg T1, T2, T3;
    wire S1;
    
    xor X1 (S1, A, B);
    
    always @ (A or B or Cin) begin
        T1 = A &amp;#x26; Cin;
        T2 = B &amp;#x26; Cin;
        T3 = A &amp;#x26; B;
        Cout = (T1 | T2) | T3;
    end
    assign Sum = S1 ^ Cin;
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要 A 或 B 上有事件发生，&lt;strong&gt;门实例&lt;/strong&gt;立即被执行。只要 A 或 B 或 Cin 上有事件发生，就执行 always 语句， 只要 S1 或 Cin 上有事件发生，就执行连续赋值语句。&lt;/p&gt;
&lt;h3&gt;设计模拟&lt;/h3&gt;
&lt;p&gt;Verilog HDL 不仅提供描述设计的能力，同时还可以提供仿真模拟的功能。设计验证可以通过在初始化语句中写入相应的语句自动与期待的响应值比较完成。&lt;/p&gt;
&lt;p&gt;下面是一个测试模块 Top 的例子，测试 FA_Swq 模块。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`timescale 1ns / 1ns
module Top;
    reg PA, PB, PCi;
    wire PCo, PSum;
    
    FA_Swq F1(PA, PB, PCi, PSum, PCo)	// 结构化描述
    
    initial begin: ONLY_ONCE
        reg [3:0] Pal;
        for(Pal = 0; Pal &amp;#x3C; 8; Pal = Pal + 1) begin
            {PA, PB, PCi} = Pal;
            #5 $display (&quot;PA, PB, PCi = %b%b%b&quot;, PA, PB, PCi, &quot; PCo, PSum = %b%b&quot;, PCo, PSum);
        end
    end
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;系统任务 &lt;code&gt;$display&lt;/code&gt; 调用中的时延控制规定该任务在 5 个单位时间后执行。这段时间基本代表了逻辑处理时间。&lt;/p&gt;
&lt;p&gt;注意这里 Pal 在初始化语句内部被定义。为了完成这一功能，begin-end 块必须被标记。这种情况下，ONLY_ONCE 是顺序过程标记。如果顺序过程内没有局部生命的变量就不需要被标记。这是测试模块的输出和对应的波形。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/47ebf0904b6a46a51e4188b5723dee00.png&quot; alt=&quot;image-20260504162320511&quot;&gt;&lt;/p&gt;
&lt;p&gt;下面给出一个验证与非门交叉连接构成的 RS_FF 模块的测试模块&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`timescale 10ns / 1ns
module RS_FF (Q, Qbar, R, S);
    output Q,Qbar;
    input R, S;
    
    nand #1 (Q, R, Qbar);
    nand #1 (Qbar, S, Q);
endmodule

module Test;
    reg TS, TR;
    wire TQ, TQb;
    
    RS_FF NSTA(.Q(TQ), .S(TS), .R(TR), .Qbar(TQb));
    
    initial begin:
        TR = 0;
        TS = 0;
        #5 TS = 1;
        #5 TS = 0;
        TR = 1;
        #5 TS = 1;
        #5 TS = 0;
        TR = 0;
        #5 TS = 0;
        #5 TS = 1;
    end
    
    initial
        $monitor(...);	// 输出显示，这里缺省了
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是 Test 模块的波形&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/a8a3d0c162f40f5979afcb069c01d7f3.png&quot; alt=&quot;image-20260504163059100&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Chapter III Verilog 语言要素&lt;/h2&gt;
&lt;h3&gt;标识符&lt;/h3&gt;
&lt;p&gt;Verilog HDL 中的标识符可以是任意一组数字、字母、$和_符号的组合，但是标识符的第一个字符必须是字母或者下划线。比如&lt;code&gt;Count&lt;/code&gt; &lt;code&gt;COUNT&lt;/code&gt; &lt;code&gt;_R1_D2&lt;/code&gt; &lt;code&gt;FIVE$&lt;/code&gt; 等&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;转义&lt;/strong&gt;标识符可以在一条标识符中包含任何可打印字符。以&lt;code&gt;\&lt;/code&gt;开头，以空白结尾（空格或制表符或换行符），例如 &lt;code&gt;\7400&lt;/code&gt; &lt;code&gt;\.*.$&lt;/code&gt; 等，一个需要注意的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\OutGate	与 OutGate 相同。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着转义用的反斜线和空格不是转义标识符的一部分。&lt;/p&gt;
&lt;p&gt;Verilog HDL 定义了一系列保留字，叫作关键字，它仅用于某些上下文中。&lt;/p&gt;
&lt;p&gt;另外，转义标识符和关键字并不完全相同。标识符 &lt;code&gt;\initial&lt;/code&gt;和标识符 &lt;code&gt;initial&lt;/code&gt; 不相同。&lt;/p&gt;
&lt;h3&gt;注释&lt;/h3&gt;
&lt;p&gt;在 Verilog HDL 中有两种形式的注释&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;/* Type I
	I can enter to a new line!! */

// Type II Single line :( 
// but simple! :)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;格式&lt;/h3&gt;
&lt;p&gt;Verilog HDL 区分&lt;strong&gt;大小写&lt;/strong&gt;，也就是说大小写不同的标识符是不同的。此外，Verilog HDL 是自由格式的，即中间的空白符号不影响程序编写，这一点和 C 语言差不多。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;// no enter
initial begin Top = 3&apos;b001; #2 Top = 3&apos;b011; end

// enter
initial
    begin
        Top = 3&apos;b001;
        #2 Top = 3&apos;b011;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的两种写法实际是相同的。&lt;/p&gt;
&lt;h3&gt;系统任务和函数&lt;/h3&gt;
&lt;p&gt;以 &lt;code&gt;$&lt;/code&gt; 开始的标识符表示系统任务或系统函数。任务提供了一种封装行为的机制（类似函数），这种机制可以在设计的不同部分被调用。任务可以返回 0 个或任意个值，允许延迟，而函数只能返回一个值，而且不允许延迟，除此之外任务和函数相同。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;$display (&quot;Hi, you have reached LT today&quot;);
$time
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;编译指令&lt;/h3&gt;
&lt;p&gt;以 ` 开始的某些标识符是编译器指令。在 Verilog 语言编译时，这些指令将会在整个编译过程中有效。完整的编译器指令有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`define `undef
`ifdef `else `endif
`default_nettype
`include
`resetall
`timescale
`unconnected_drive `nounconnected_drive
`celldefine `endcelldefine
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;define 和 undef&lt;/h4&gt;
&lt;p&gt;define 指令用于文本替换，它很像 C 语言中的 #define 指令，如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`define MAX_BUS_SIZE 32
...
reg [`MAX_BUS_SIZE - 1 : 0] AddReg;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一旦指令被编译，在整个编译过程中都有效，例如，通过另一个文件中的 define 指令，MAX_BUS_SIZE 能够被多个文件使用。&lt;/p&gt;
&lt;p&gt;如果取消前面的定义呢？可以使用 undef&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`define WORD 16
...
wire [`WORD : 1] Bus;
...
`undef WORD
// 这之后出现的 WORD 将不再有效
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ifdef else 和 endif&lt;/h4&gt;
&lt;p&gt;这些编译指令用于条件编译：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`ifdef WINDOWS
parameter WORD_SIZE = 16
`else
parameter WORD_SIZE = 32
`endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在编译过程中，如果有名字是 WINDOWS 的文本宏被定义了，那么就使用第一种参数声明。否则使用第二种参数。&lt;/p&gt;
&lt;p&gt;else 指令对于 ifdef 指令是可选的。&lt;/p&gt;
&lt;h4&gt;default_nettype&lt;/h4&gt;
&lt;p&gt;这个指令用于为隐式线网指定线网类型，也就是将那些没有说明的连线的类型。&lt;/p&gt;
&lt;p&gt;这里需要补充的是，线网类型亦有区别。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`default_nettype wand
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这句话为缺省定义的线网视为线与类型。因此，如果在此指令后面的任何模块中没有说明的连线，那么该线网被假定为线与类型。&lt;/p&gt;
&lt;p&gt;wire 家族的其他成员涉及到多个驱动源驱动同一根电线的问题，这门课里不做过多考虑。只需要知道这个指令是干嘛的就好。&lt;/p&gt;
&lt;h4&gt;include&lt;/h4&gt;
&lt;p&gt;include 指令用于嵌入内嵌文件的内容。文件既可以用相对路径名去定义，也可以用全路径名定义。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`include &quot;../../primitives.v&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译时，这一行由文件 &lt;code&gt;../../primitives.v&lt;/code&gt; 替代。&lt;/p&gt;
&lt;h4&gt;resetall&lt;/h4&gt;
&lt;p&gt;这条指令将左右的编译指令重新设置为缺省值。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`default_nettype wand
...
`resetall
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条指令使得缺省连线类型恢复为线网类型。&lt;/p&gt;
&lt;h4&gt;timescale&lt;/h4&gt;
&lt;p&gt;在 Verilog HDL 中，所有时延都要用单位时间表述。使用 timescale 指令将时间单位和实际时间相关联。这条指令用于定义时延的单位和时延精度。timescale 指令格式为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`timescale time_unit / time_precision
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 time_unit 和 time_precision 有值 1 / 10 / 100 以及单位 s ms us ns ps 和 fs 组成。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`timescale 1ns / 100ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示时延单位为 1ns ，精度为 100ps 。timescale 指令在模块说明外部出现，并且影响后面所有的时延值。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`timescale 1ns / 100ps
module AndFunc (Z, A, B);
    output Z;
    input A, B;
    
    and #(5.22, 6.17) Al (Z, A, B);
endmodule

`timescale 10ns / 1ns
module TB;
    reg PutA, PutB;
    wire GetO;
    initial
        begin
            PutA = 0;
            PutB = 0;
            #5.21 PutB = 1;
            #10.4 PutA = 1;
            #15 PutB = 0;
        end
    AndFunc AFl(GetO, PutA, PutB);
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里每个模块都有自己的 timescale，例如第一个模块中的 5.22 6.17 对应的就是 5.22ns 和 6.17ns，而第二个模块中的 5.21 对应的是52.1ns。两边的误差也是等比例放大。&lt;/p&gt;
&lt;h4&gt;unconnected_drive 和 nounconnected_drive&lt;/h4&gt;
&lt;p&gt;在模块实例化中，出现在这两个指令间的所欧未连接输入端口或者为正偏电路状态或者是反正偏电路状态。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`unconnected_drive pull1
...
/* 这中间的所有未连接输入端口连接到高电平 */
`nounconnected_drive

`unconnected_drive pull0
...
/* 这中间的所有未连接输入端口连接到低电平 */
`nounconnected_drive
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;celldefine 和 endcelldefine&lt;/h4&gt;
&lt;p&gt;这两个指令将模块标记为但愿模块。他们表示包含模块定义。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;`celldefine
module FD1S3AX(D, CK, Z);
	...
endmodule
`endcelldefine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个指令是留给各个 EDA 开发商用的，生产好的原件不需要大家点开再去看里面的结构了，所以拿这个封装好，仿真就不用跑里面的东西了。&lt;/p&gt;
&lt;h3&gt;值集合&lt;/h3&gt;
&lt;p&gt;Verilog HDL 有下列四种基本的值：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;0 逻辑 0 或 “假”&lt;/li&gt;
&lt;li&gt;1 逻辑 1 或 “真”&lt;/li&gt;
&lt;li&gt;x 未知&lt;/li&gt;
&lt;li&gt;z 高阻&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这四种值的解释被内置于语言中。在门外的输入或者一个表达式中的为&lt;code&gt;z&lt;/code&gt; 的值通常被解释为&lt;code&gt;x&lt;/code&gt;。此外，x 和 z 都是不区分大小写的。也就是说，&lt;code&gt;0x1z&lt;/code&gt;和 &lt;code&gt;0x1Z&lt;/code&gt;大小相同。&lt;/p&gt;
&lt;p&gt;Verilog HDL 中有三类常量：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;整形&lt;/li&gt;
&lt;li&gt;实数型&lt;/li&gt;
&lt;li&gt;字符串型&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下划线符号可以被任意用在整数或者实数中，它们就数量本身没有意义，用来提高易读性。例如&lt;code&gt;1_000_000&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;整型数&lt;/h4&gt;
&lt;p&gt;可以按照一下两种形式书写：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;简单的十进制数格式&lt;/p&gt;
&lt;p&gt;例如&lt;code&gt;32&lt;/code&gt; &lt;code&gt;-15&lt;/code&gt; 等，这种形式的整数值代表一个有符号的数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基数格式&lt;/p&gt;
&lt;p&gt;这种格式的整数格式为&lt;code&gt;[size] &apos;base value&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;size 定义以位计的常量的位长；base 为 o/O/b/B/d/D/h/H （八、二、十、十六进位）；value 是基于 base 的值的数字序列，值中的所有字母（包括 x 和 z）不区分大小写。&lt;/p&gt;
&lt;p&gt;例如：&lt;code&gt;5&apos;037&lt;/code&gt; &lt;code&gt;4&apos;D2&lt;/code&gt; &lt;code&gt;4&apos;B1x_01&lt;/code&gt; &lt;code&gt;7&apos;Hx&lt;/code&gt; &lt;code&gt;4&apos;hZ&lt;/code&gt; ......&lt;/p&gt;
&lt;p&gt;这里后面两个的意思是位 x 和位 z，即 xxxxxxx 和 ZZZZ 。x（或 z ）在十六进制值中代表 4 位 x（或  z ），在八进制中代表 3 位 x（或  z ），在二进制中代表 1 位 x（或 z ）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​	基数格式的数通常为无符号数，这种形式的整型数的长度定义是可选的，如果没有定义长度，那么长度就是对应 value 的位数。&lt;/p&gt;
&lt;p&gt;​	如果定义的长度比常量指定的长度长，那么就在左边补 0。但是如果数最左边一位为 x 或者 z，那么就相应地用 x 和 z 补位。？字符在数中可以代替值 z 在值 z 被解释为不分大小写的情况下提高可读性。&lt;/p&gt;
&lt;h4&gt;实数&lt;/h4&gt;
&lt;p&gt;表示同 C 语言，十进制计数法或科学记数法均可。&lt;/p&gt;
&lt;h4&gt;字符串&lt;/h4&gt;
&lt;p&gt;字符串是双引号内的字符序列。字符串不能分成多行书写。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;&quot;INTERNAL ERROR&quot;
&quot;REACHED-&gt;HERE&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用八位 ASCII 值表示的字符可以看作是无符号整数。因此字符串是 8 位 ASCII 值的序列。为了保存字符串 &lt;code&gt;&quot;INTERNAL ERROR&quot;&lt;/code&gt; ，变量需要 8 * 14 位。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;reg [1 : 8 * 14] Message;
...
assign Message = &quot;INTERNAL ERROR&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反斜线用于对确定的特殊字符转义。转义规则同 C 语言。&lt;/p&gt;
&lt;h3&gt;数据类型&lt;/h3&gt;
&lt;p&gt;Verilog HDL 有两大类数据类型。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;线网类型。net type 表示 Verilog 结构化元件间的物理连线。它的值由驱动元件的值决定，例如连续赋值或门的输出。如果没有驱动连接到线网，线网的缺省值为 z。&lt;/li&gt;
&lt;li&gt;寄存器类型。register type 表示一个抽象的数据存储单元，它只能在 always 和 initial 语句中被赋值，而且它的值从一个赋值到另一个赋值被保存下来。寄存器类型的变量具有 x 的缺省值。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;线网类型&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire tri wor trior wand triand trireg tri1 tri0 supply0 supply1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单的线网类型说明语法为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;net_kind [msb:lsb] net1, net2, ..., netN;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;net_kind 对应上面的线网类型，msb 和 lsb 是用于定义线网范围的常量表达式。范围定义可选，缺省为 1。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire Rdy, Start;	// 2 个 1 位的连线
wand [2:0] Addr;	// Addr 是 3 位线与
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当线网有多个驱动时，不同的类型行为不同。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wor Rde;
...
assign Rde = Blt &amp;#x26; Wyl;
...
assign Rde = Kbl | Kip;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 Rde 有两个驱动源，实际的有效值由 &lt;code&gt;wor&lt;/code&gt; 类型的真值表决定。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;wire 和 tri&lt;/p&gt;
&lt;p&gt;最常见的线网类型。tri 即三态线网&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Chapter VII 数据流模型化&lt;/h2&gt;
&lt;h3&gt;连续赋值语句&lt;/h3&gt;
&lt;p&gt;将值赋给线网&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign LHS_target = RHS_expression
// eg.
wire [3.0] Z, Preset, Clear;
assign Z = Preset &amp;#x26; Clear;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;赋值的目标是 Z，表达式右端是 &quot;Preset &amp;#x26; Clear&quot;&lt;/p&gt;
&lt;p&gt;执行时间点：右侧表达式的操作数上面有值发生变化了，发生时，表达式被计算。如果结果值有变化，新结果赋值给左边的线网。其中，右边值发生变化的过程叫作发生了&lt;strong&gt;事件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在上面的例子中，Preset 或 Clear 变化，就计算右边的整个表达式。如果结果变化，那么结果就赋值到线网 Z。&lt;/p&gt;
&lt;p&gt;连续赋值的目标类型如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;标量线网&lt;/li&gt;
&lt;li&gt;向量线网&lt;/li&gt;
&lt;li&gt;向量的常数型位选择&lt;/li&gt;
&lt;li&gt;向量的常数型部分选择&lt;/li&gt;
&lt;li&gt;上述类型的任意地拼接运算结果&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面是连续赋值语句的另一些例子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign BusErr = Parity | (One &amp;#x26; OP);
assign Z = ~ (A | B) &amp;#x26; (C | D) &amp;#x26; (E | F);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要 A / B / C / D / E / F 的值变化，最后一个连续赋值语句就执行。&lt;/p&gt;
&lt;p&gt;在下一个例子中，目标是一个向量线网和一个标量线网的拼接结果。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire Cout, Cin;
wire [3:0] Sum, A, B;
...
assign {Cout, Sum} = A + B + Cin;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 A 和 B 是 4 位宽，加操作的结果最大能够产生 5 位结果。左端表达二手的长度指定为 5 位（Cout 1 位， Sum 4 位）赋值语句因此促使最右边的 4 位结果赋值给 Sum，第 5 位 Cin 赋值给 Cout。&lt;/p&gt;
&lt;p&gt;下面说明如何在一个连续赋值语句中编写多个复制方式。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign Mux = (S == 0) ? A : &apos;bz
    Mux = (S == 1) ? B : &apos;bz
    Mux = (S == 2) ? C : &apos;bz
    Mux = (S == 3) ? D : &apos;bz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是连续四个 &lt;code&gt;assign Mux = (S == i) ? L_i : &apos;bz&lt;/code&gt; 语句的简化书写形式。&lt;/p&gt;
&lt;h3&gt;举例&lt;/h3&gt;
&lt;p&gt;下面采用数据流方式描述 1 位全加器&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module FA_Df(A, B, Cin, Sum, Cout);
    input A, B, Cin;
    output Sum, Cout;
    
    assign Sum = A ^ B ^ Cin;
    assign Cout = (A &amp;#x26; Cin) | (B &amp;#x26; Cin) | (A &amp;#x26; B);
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在本例中，有两个连续赋值语句。这些语句是并发的，与书写顺序无关。只有连续赋值语句右端表达式中操作数的值发生变化，连续赋值语句就被执行。&lt;/p&gt;
&lt;h3&gt;线网说明赋值&lt;/h3&gt;
&lt;p&gt;连续赋值可以作为线网说明本身的一部分。这要的赋值被称为线网说明赋值。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire [3:0] Sum = 4&apos;b0;
wire Clear = &apos;b1;
wire A_GT_B = A &gt; B, B_GT_A = B &gt; A;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;线网寿命赋值说明线网与连续赋值。寿命线网然后编写连续赋值语句是一种方便的形式。参见下例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire Clear;
assign Clear = &apos;b1;
// 等价于线网声明赋值：
wire Clear = &apos;b1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不允许在同一个线网上出现多个线网声明赋值。如果多个赋值是必须的，则必须使用连续赋值语句。&lt;/p&gt;
&lt;h3&gt;时延&lt;/h3&gt;
&lt;p&gt;如果在连续赋值语句中没有定义时延，如前面的例子，则右端表达式的值立刻被赋给左端表达式。时延为 0。如下例所示显式定义连续赋值的时延。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign #6 Ask = Quiet || Late;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;规定右边表达式结果的计算岛器赋给左边目标需经过 6 个时间单位时延。例如，如果在时刻 5，Late 值发生变化，那么赋值的右端表达式被计算，并且 Ask 在时刻 11 被赋予新值。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/83dd2f1c068267ec47df13c0af0ff55a.png&quot; alt=&quot;image-20260510024125893&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果右端在传输给左端之前发生变化，会发生什么呢？在这种情况下应用最新的变化值。下例显示了这种行为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign #4 Cab = Drm;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;图中显示了这种变化的效果。右端发生在时延间隔内的变化被滤掉。例如，在时刻 5， Drm 的上升边沿预定在时刻 9 显示在 Cab 上，但是因为 Drm 在 8 下降为 0，预定在 Cab 上的值被删除。同样，18 到 20 之间的那次也是被过滤掉了。&lt;strong&gt;如果时间间隔内右端值变化，那么前面的值不能传输到输出。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/9b3102c9152041d7659120cc5cce10c0.png&quot; alt=&quot;image-20260510024526252&quot;&gt;&lt;/p&gt;
&lt;p&gt;对于每个时延定义，总共能够指定三类时延值：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;上升时延&lt;/li&gt;
&lt;li&gt;下降时延&lt;/li&gt;
&lt;li&gt;关闭时延&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这三类时延的语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign # (rise, fall, turn-off) LHS_target = RHS_expression
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是当三类时延值定义为 0 时，如何解释时延的实例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;assign #4 Ask = Quiet || Late;
assign # (4, 8) Ask = Quick;
assign # (4, 8, 6) Arb = &amp;#x26; DataBus;
assign Bus = MemAddr [7:4];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在第一个赋值语句中，上升时延、下降时延、截止时延和传递到 x 的时延相同，都为 4。在第二个语句中，分别是 4 8 4 4，在第三个赋值中，分别是 4 8 6 4。在最后的语句中，所有的时延都为 0。&lt;/p&gt;
&lt;p&gt;上升时延对于向量线网目标意味着什么呢？如果右端从非 0 向量变化到 0 向量，那么就使用下降时延。如果右端值到达 z，那么使用下降时延；否则使用上升时延。&lt;/p&gt;
&lt;h3&gt;线网时延&lt;/h3&gt;
&lt;p&gt;时延也可以在线网中说明定义。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire #5 Arb;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时延表明 Arb 驱动原址改变与线网 Arb 本身间的时延。考虑下面对线网 Arb 的连续赋值语句：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire #2 Arb = Bod &amp;#x26; Cap;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假定在时刻 10，Bod 上的时间促使表达式计算。如果结果不同，则在 2 个时间单位后赋值给 Arb。但是因为定义了线网时延，实际对 Arb 的赋值发生在时刻 17 (10 + 2 + 5)。如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/95994a750309ee238b5baf31ffa38308.png&quot; alt=&quot;image-20260510032559981&quot;&gt;&lt;/p&gt;
&lt;p&gt;再下面的图很好的描述了线网时延的效果。首先使用赋值时延，然后增加任意线网时延。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/5b3ff06d2ae0b8587511e60f3d79bdb6.png&quot; alt=&quot;image-20260510032649366&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果时延在线网说明赋值中出现，那么时延不是线网时延，二式赋值时延。下面是 A 的线网说明赋值，2 个时间单位是赋值时延，而不是线网时延。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wire #2 A = B - C;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;举例&lt;/h3&gt;
&lt;h4&gt;主从触发器&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module MSDFF_DF(D, C, Q, Qbar);
    input D, C;
    output Q, Qbar;
    wire NotC, NotD, NotY, Y, D1, D2, Ybar, Y1, Y2;
    
    assign NotD = ~D;
    assign NotC = ~C;
    assign NotY = ~Y;
    
    assign D1 = ~(D &amp;#x26; C);
    assign D2 = ~(C &amp;#x26; NotD);
    assign Y = ~(D1 &amp;#x26; Ybar);
    assign Ybar = ~(Y &amp;#x26; D2);
    assign Y1 = ~(Y &amp;#x26; NotC);
    assign Y2 = ~(NotY &amp;#x26; NotC);
    assign Q = ~(Qbar &amp;#x26; Y1);
    assign Qbar = ~(Y2 &amp;#x26; Q);
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;数值比较器&lt;/h4&gt;
&lt;p&gt;下面是 8 位参数定义的数值比较器数据流模型。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module MagnitudeComparator(A, B, AgtB, AeqB, AltB);
    parameter BUS = 8;
    parameter EQ_DELAY = 5, LT_DELAY = 8, GT_DELAY = 8;
    input [1:BUS] A, B;
    output AgtB, AeqB, AltB;
    
    assign #EQ_DELAY AeqB = A == B;
    assign #GT_DELAY AgtB = A &gt; B;
    assign #LT_DELAY AltB = A &amp;#x3C; B;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Chapter VIII 行为建模&lt;/h2&gt;
&lt;h3&gt;过程结构&lt;/h3&gt;
&lt;p&gt;下面两种语句是为一个设计的行为建模的主要机制。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;initial 语句&lt;/li&gt;
&lt;li&gt;always 语句&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个模块中可以包含任意个 initial 和 always，它们并行执行，一个对应的语句产生一个控制流。&lt;/p&gt;
&lt;h4&gt;initial语句&lt;/h4&gt;
&lt;p&gt;initial 语句只执行一次。在模拟开四是执行，即在 0 时刻开始执行。initial 语句的语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;initial [timeing_control] procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的 &lt;code&gt;procedural_statement&lt;/code&gt; 是下列语句之一：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;procedural_assignment {blocking or non-blocking} // 阻塞性或非阻塞过程赋值语句
procedural_continuous_assignment
conditional_statement
case_statement
loop_statement
wait_statement
disable_statement
event_trigger
sequential_block
parallel_block
task_enable {user or system}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺序过程 begin-end最长是用在进程语句中。这里的时序控制可以使时延控制，即等待一个确定的时间；或事件控制，即等待确定的事件发生或某一特定的条件威震。initial 语句的各个进程语句仅执行一次。注意 initial 语句在模拟的 0 时刻开始执行。initial 语句根据进程语句中出现的时间控制在以后的某个事件完成执行。&lt;/p&gt;
&lt;p&gt;下面是一个 initial 语句的实例。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;reg Yurt;
...
initial
    Yurt = 2;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述 initial 语句中包含无时延控制的过程赋值语句。initial 语句在 0 时刻执行，促使 Yurt 在 0 时刻被赋值为 2。下面是一个带有时延控制的 initial 语句。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;reg Curt;
...
initial
    #2 Curt = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;寄存器变量 Curt 在时刻 2 被赋值为 1。initial 语句在 0 时刻开始执行，在时刻 2 完成执行。&lt;/p&gt;
&lt;p&gt;下例是带有顺序过程的 initial 语句&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;parameter SIZE = 1024;
reg [7:0] RAM [0:SIZE - 1];
reg RibRag;

initial
    begin: SEQ_BLK_A
        integer Index;
       	RibReg = 0;
        for (Index = 0; Index &amp;#x3C; SIZE; Index = Index + 1) {
            RAM[Index] = 0;
        }
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺序过程由关键词 begin-end 定界，它包含顺序执行的进程语句，与 C 语言等高级编程语言相似。&lt;code&gt;SEQ_BLK_A&lt;/code&gt; 是顺序过程的&lt;em&gt;标记&lt;/em&gt;；如果过程中没有局部说明部分，不要求这一标记。&lt;/p&gt;
&lt;p&gt;例如，如果对 Index 的说明部分在 intial 语句之外，不需要标记。整数型变量 Index 已在过程中声明。并且，顺序过程包含 1 个带循环语句的过程性赋值。这一 initial 语句在执行时将所有的内存初始化为 0。&lt;/p&gt;
&lt;p&gt;下例是另一个带有顺序过程的 initial 语句。这个顺序过程包含时延控制的过程性赋值语句。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;parameter APPLY_DELAY = 5;
reg [0:7] port_A;
...
initial
    begin
        Port_A = &apos;h20
        #APPLY_DELAY Port_A = &apos;hF2;
        #APPLY_DELAY Port_A = &apos;h41;
        #APPLY_DELAY Port_A = &apos;h0A;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行时，Port_A 的值如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/11dec3e4d26b216cc3bf811971b620a1.png&quot; alt=&quot;image-20260510132922713&quot;&gt;&lt;/p&gt;
&lt;p&gt;Initial 语句主要用于初始化和波形生成。&lt;/p&gt;
&lt;h4&gt;always 语句&lt;/h4&gt;
&lt;p&gt;与 initial 语句相反，always 语句反复执行。与 initial 语句类似，always 语句语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;always [timing_control] procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;过程语句和时延控制（时序控制）的描述方式与上节相同。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;always Clk = ~Clk;	// 将无限循环
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此 always 语句有一个过程性赋值。因为 always 语句反复执行，并且在此例中没有时延控制，过程语句将在 0 时刻无限循环。因此，always 语句的执行必须带有某种时序控制，如下例的 always 语句，形式上与上面的实例相同，但带有时延控制。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;always #5 Clk = ~Clk;	// 产生时钟周期为 10 的波形
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是由事件控制的顺序过程的 always 语句。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;reg [0:5] InstrReg;
reg [3:0] Accum;
wire ExecuteCycle;

always @ (ExecuteCycle)
    begin
        case(InstrReg[0:1])
            2&apos;b00: Store(Accum, InstrReg[2:5]);
            2&apos;b11: Load(Accum, InstrReg[2:5]);
            2&apos;b01: Jump(Accum, InstrReg[2:5]);
            2&apos;b10: ;
        endcase
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺序过程 begin-end 中的语句按顺序执行。这个 always 语句意味着只要有事件发生，即值发生变化，ExecuteCycle 就执行顺序过程中的语句；顺序过程的执行意味着按顺序执行过程中的各个语句。&lt;/p&gt;
&lt;p&gt;下例为带异步预置的负边沿触发的 D 触发器的行为模型&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module DFF(Clk, D, Set, Q, Qbar);
    input Clk, D, Set;
    output Q, Qbar;
    reg Q, Qbar;
    always
        wait (Set == 1)
        begin
            #3 Q = 1;
            #2 Qbar = 0;
            wait(Set == 0);
        end
    always
        @ (negedge Clk)
        begin
            if (Set != 1)
                begin
                    #5 Q = D;
                    #1 Qbar = ~Q;
                end
        end
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里面第一条 always 语句由电平敏感事件控制，第二条 always 语句由边沿触发的事件控制。&lt;/p&gt;
&lt;h4&gt;两类语句在模块中的应用&lt;/h4&gt;
&lt;p&gt;一个模块可以有多条 always 和 initial，下面是一个例子。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module TestXorBehaviour;
    reg Sa, Sb, Zeus;
    
    initial
        begin
            Sa = 0;
            Sb = 0;
            #5 Sb = 1;
            #5 Sa = 1;
            #5 Sb = 0;
        end
    
    always
        @(Sa or Sb) Zeus = Sa ^ Sb;
    
    always
        @(Zeus)
        $display {&quot;At time %t, Sa = %d, Sb = %d, Zeus = %d&quot;, $time, Sa, Sb, Zeus};
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模块中的 3 条语句并行执行，产生这样的波形&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/13329d475d075ce97757ceb4fe20b7a9.png&quot; alt=&quot;image-20260510194822594&quot;&gt;&lt;/p&gt;
&lt;h3&gt;时序控制&lt;/h3&gt;
&lt;p&gt;时序控制与过程语句关联，主要有两种形式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;时延控制&lt;/li&gt;
&lt;li&gt;事件控制&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;时延控制&lt;/h4&gt;
&lt;p&gt;形式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;#delay procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个实例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;#2 Tx = Rx-5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时延控制定义为首次遇到这个语句和语句的执行的时间的间隔。时延控制表示在语句执行前的“等待时延”。上面的例子中，过程赋值语句在执行到这里 2 个时间单位后执行。&lt;/p&gt;
&lt;p&gt;另一个实例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;initial
    begin
        #3 Wave = &apos;b0111;
        #6 Wave = &apos;b1100;
        #7 Wave = &apos;b0000;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;initial 语句在 0 时刻被执行。首先，等待 3 个时间单位执行第一个赋值，然后等待 6 个时间单位执行第 2 个语句语句...执行完三条之后永远挂起。&lt;/p&gt;
&lt;p&gt;时延控制也可以用另一种形式定义：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;#delay;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一个语句促使在下一条语句执行之前等待给定的时延。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;parameter ON_DELAY = 3, OFF_DELAY = 3;
always
    begin
        #ON_DELAY;
        RefClk = 0;
        #OFF_DELAY;
        RefClk = 1;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时延控制中的时延可以是任意表达式，即不必限定为某一个常量。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;#Strobe;
Compare = TX ask;
#(PERIOD / 2);
Clock = ~Clock;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;显示零时延促发一个等待，等待所有其它在当前模拟时间被执行的时间执行完毕之后，才将其唤醒；模拟时间不前进。&lt;/p&gt;
&lt;p&gt;如果时延表达式的值为 x 或 z，那么它和 0 时延等效。如果时延表达式计算结果为负值，那么其二进制的补码值会被作为时延。&lt;/p&gt;
&lt;h4&gt;事件控制&lt;/h4&gt;
&lt;p&gt;在事件控制中，always 的过程语句基于事件执行。有两种类型的事件控制方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;边沿触发事件控制&lt;/p&gt;
&lt;p&gt;边沿触发事件控制如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;@event procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如下例所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;@(posedge Clock) Curr_state = Next_state;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;带有事件控制的进程或者过程语句的执行，必须等到指定事件发生。上例中，如果 Clock 信号从低电平变为高电平，那么就执行赋值语句；否则进程被挂起，直到 Clock 信号产生下一个正跳边沿。&lt;/p&gt;
&lt;p&gt;下面是进一步的实例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;@(negedge Reset) Count = 0;
@Cla Zoo = Foo;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在第一条语句中，赋值语句只在 Reset 上的负沿执行。第二条语句中，当 Cla 上有时间发生时，Foo 的值被赋给 Zoo，即等待 Cla 上发生事件时，Foo 的值被赋给 Zoo。&lt;/p&gt;
&lt;p&gt;也可以采用以下的形式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;@event;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个语句促发一个等待，直到指定的事件发生。下面是确定始终在周期的 initial 语句中使用的另一个例子。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;time RiseEdge, OnDelay;
initial
    begin
        // 等待时钟发生正边沿
        @(posedge ClockA);
        RiseEdge = $time;
        // 等待时钟发生负边沿
        @(negedge ClockA);
        OnDelay = $time - RiseEdge;
        $display(&quot;The on-period of clock is %t.&quot; Delay);
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事件之间也能够相或以表明“如果有任何事件发生”。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;@(posedge Clear or negedge Reset) Q = 0;
@(Ctrl_A or Ctrl_B) Dbus = &apos;bz;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意关键字 &lt;code&gt;or&lt;/code&gt; 并不意味着在 1 个表达式中的逻辑或。&lt;/p&gt;
&lt;p&gt;在 Verilog HDL 中 &lt;code&gt;posedge&lt;/code&gt; 和 &lt;code&gt;negedge&lt;/code&gt; 是表示正沿和负沿的关键字。信号的负沿是下属转换的一种：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 -&gt; x
1 -&gt; z
1 -&gt; 0
x -&gt; 0
z -&gt; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正沿是下述转换的一种：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;0 -&gt; x
0 -&gt; z
0 -&gt; 1
x -&gt; 1
z -&gt; 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;综合来说就是信号优先级从低到高是 0 x/z 1，从低到高变化是正沿，从高到低是负沿。&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;电平敏感事件控制&lt;/p&gt;
&lt;p&gt;在电平敏感时间控制中，进程语句或进程中的过程语句一直延迟到条件变为真后才执行。电平敏感时间控制形式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wait (Condition) procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;过程语句只有在条件为真时才执行，否则过程语句一直等待到条件为真。这里的过程语句是&lt;em&gt;可选的&lt;/em&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;wait (Sum &gt; 22) Sum = 0;
wait (DataReady) Data = Bus;
wait (Preset);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在第一条语句中，只有当 Sum 的值大于 22 时，才对 Sum 清 0。在第二条语句中，只有当 DataReady 为真，才将 Bus 赋给 Data。最后一条语句表示延迟至 Preset 变为真（1）时，后面的语句才可以继续执行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;语句块&lt;/h3&gt;
&lt;p&gt;语句块将两条或更多条语句组合成语法结构上相当于一条语句的机制。在 Verilog HDL 中有两类语句块。即：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;顺序语句块 begin-end：语句块中的语句按给定次序&lt;strong&gt;顺序&lt;/strong&gt;执行。&lt;/li&gt;
&lt;li&gt;并行语句块 fork-join：语句块中的语句&lt;strong&gt;并行&lt;/strong&gt;执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;语句块中的标识符是&lt;em&gt;可选&lt;/em&gt;的。如果有标识符，寄存器变量可以在语句块内部声明。带标识符的语句块可以被引用。语句块可使用禁止语句来禁止执行。此外，语句块标识提供唯一表示寄存器的一种方式。但是，要注意所有的寄存器都是静态的，即他们的值在整个模拟运行中不变。&lt;/p&gt;
&lt;h4&gt;顺序语句块&lt;/h4&gt;
&lt;p&gt;顺序语句块中的语句按顺序方式执行。每条语句的时延都和前面语句执行的模拟时间相关。一旦顺序语句块执行结束，下一条语句就执行。语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;begin
    {:block_id{declarations}}
    procedural_statement(s)
end
// eg.
begin
    #2 Stream = 1;
    #5 Stream = 0;
    #3 Stream = 1;
    #4 Stream = 0;
    #2 Stream = 1;
    #5 Stream = 0;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假定顺序语句块在第 10 个时间单位开始执行。两个时间单位后第 1 调语句执行，即第 12 个时间单位。此执行完成后，下 1 条语句在第 17 个时间单位执行（延迟 5 个时间单位）。然后下一条语句在第 20 个时间单位执行。以此类推。该顺序语句块执行过程中产生的波形如下图所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/6c6b1b6ca89bdfce0bb634a71800bdc0.png&quot; alt=&quot;image-20260511001808623&quot;&gt;&lt;/p&gt;
&lt;p&gt;下面是顺序过程的另一个实例。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;begin
    Pat = Mask | Mat;
    @(negedge Clk);
    FF = &amp;#x26;Pat
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，第一条语句首先执行，然后执行第二条。根据前面说的，这个语句在 Clk 上出现负沿才执行，之后继续执行。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;begin: SEQ_BLK
    reg[0:3] Sat;
    
    Sat = Mask &amp;#x26; Data;
    F = ^Sat;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个实例中，顺序语句块带有标记 &lt;code&gt;SEQ_BLK&lt;/code&gt; 并且有一个局部寄存器说明。在执行时，首先执行第一条语句，然后顺序执行。&lt;/p&gt;
&lt;h4&gt;并行语句块&lt;/h4&gt;
&lt;p&gt;带有定界符 fork-join，各个语句并行执行。每一条语句指定的时延值都与语句开始执行的时间相关。当并行语句块中最后的动作执行完成时，顺序语句块的语句继续执行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简单来说&lt;/strong&gt;，&lt;em&gt;并行语句块内的所有语句都不许在控制转出语句块之前完成执行。&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;fork
    {:block_id{declarations}}
    procedural_statement(s);
join
// eg.
fork
    #2 Stream = 1;
    #7 Stream = 0;
    #10 Stream = 1;
    #14 Stream = 0;
    #16 Stream = 1;
    #21 Stream = 0;
join
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果并行语句块在第 10 个时间单位开始执行，所有的语句并行执行并且所有的时延都是相对于时刻 10 的。如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/a6a86dc4cc8820d54802d471d68396a9.png&quot; alt=&quot;image-20260511010053802&quot;&gt;&lt;/p&gt;
&lt;p&gt;下面的例子混合使用了顺序语句和并行语句块。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;always
    begin: SEQ_A
        #4 Dry = 5;
        
        fork: PAR_A
            #6 Cun = 7;
            begin: SEQ_B
                EXE = Box;
                #5 Jap = Exe;
            end
            
            #2 Dop = 3;
            #4 Gos = 2;
            #8 Pas = 4;
        join
        #8 Bax = 1;
        #2 Zoom = 52;
        #6 $stop;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;always 语句中包含顺序语句块 SEQ_A，并且顺序语句块内的所有语句顺序执行。后面的 fork-join 执行全部结束后再接回顺序块执行。这里面 SEQ_B 的地位相当于 fork-join 的一个语句。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/a084ecb55691ca2afb51e5adccc99c5b.png&quot; alt=&quot;image-20260511144124170&quot;&gt;&lt;/p&gt;
&lt;h3&gt;过程性赋值&lt;/h3&gt;
&lt;p&gt;过程性赋值实在 intiial 语句或者 always 语句内的赋值。它只能对&lt;strong&gt;寄存器类型&lt;/strong&gt;的变量赋值。表达式的右端可以是任何表达式。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;reg[1:4] Enable, A, B;
...
#5 Enable = ~A ^ ~B;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable 是寄存器。根据时延控制，赋值语句被延迟 5 个时间单位执行。右端表达式被计算，并且赋值给 Enable&lt;/p&gt;
&lt;p&gt;过程性赋值与其周围的语句顺序执行。always 语句实例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;always
    @(A or B or C or D)
    begin: AOI
        reg Temp1, Temp2;
        Temp1 = A &amp;#x26; B;
        Temp2 = C &amp;#x26; D;
        Temp1 = Temp1 | Temp2;
        Z = ~Temp1;
    end
// 上面的语句可以被一条语句代替：
// Z = ~((A &amp;#x26; B) | (C &amp;#x26; D));
// 但是这里是为了说明顺序特性，特意写成这样
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;always 语句内的顺序过程在信号 A / B / C / D 发生变化时开始执行。 Temp1 的赋值首先执行，然后执行第二个赋值。在以前赋值中计算的 Temp1 和 Temp2 的值在第三条赋值语句中使用。最后一个语句使用的是第三条语句里面的 Temp1 的值。&lt;/p&gt;
&lt;p&gt;过程性赋值分两类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;阻塞性过程赋值&lt;/li&gt;
&lt;li&gt;非阻塞性过程赋值&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在讨论之前先聊聊语句内部时延。&lt;/p&gt;
&lt;h4&gt;语句内部时延&lt;/h4&gt;
&lt;p&gt;在赋值语句中右端出现的时延是语句内部时延。通过内部时延表达式，右端的值在赋给左端目标值前延迟。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;Done = #5 &apos;b1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重要的是有段表达式在语句内部时延之前计算，然后进入时延等待，再对左端目标赋值。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;Done = #5 &apos;b1;	// 语句内部时延控制

begin
    Temp = &apos;b1;
    #5 Done = Temp;	// 语句间时延控制
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上两段程序相同，相应的&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;Q = @(posedge Clk) D;

begin
    Temp = D;
    @(posedge Clk)
    Q = Temp;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面两条相同。&lt;/p&gt;
&lt;p&gt;还有一种重复事件控制得语句内部时延表示形式。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;repeat(express) @ (event_expression)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种控制形式用于根据一定数量的 1 个或多个事件来定义时延。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;Done = repeat(2) @(negedge ClkA) A_REG + B_REG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一语句执行时先计算右端的值，即 A_Reg + B_reg 的值，然后等待时钟 ClkA 上面的两个负沿，再将右端值赋给 Done。这一重复事件控制实例的等价形式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;begin
    Temp = A_REG + B_REG
    @(negedge ClkA);
    @(negedge ClkA);
    Done = Temp;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种形式的时延控制方式在给某些边或一定数量的同步赋值过程中非常有用。&lt;/p&gt;
&lt;h4&gt;阻塞性过程赋值&lt;/h4&gt;
&lt;p&gt;赋值操作符是 &lt;code&gt;=&lt;/code&gt; 的过程赋值是阻塞性过程赋值。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;RegA = 52;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是阻塞性过程赋值。&lt;/p&gt;
&lt;p&gt;阻塞性过程赋值在其后素有语句执行前执行，即在下一语句执行前该赋值语句完成执行。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;always
    @(A or B or Cin)
    begin: CARRY_OUT
        reg T1,T2,T3;
        
        T1 = A &amp;#x26; B;
        T2 = B &amp;#x26; Cin;
        T3 = A &amp;#x26; B;
        Cout = T1 | T2 | T3;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;T1 赋值首先发生，计算 T1，接着执行第二条语句，T2被赋值，之后是第三条...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;initial
    begin
        Clr = #5 0;
        Clr = #4 1;
        Clr = #10 0;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一条语句在 0 时刻执行，Clr 在 5 个时间单位后被赋值，接着是再 4 个时间单位后第二条语句，接着是再 10 个时间单位后第三条。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/856ca6cab111a1c873430ba19df2d98e.png&quot; alt=&quot;image-20260511171452018&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;begin
    Art = 0;
    Art = 1;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这种情况下，Art被赋值位 1.这是因为第一个 Art 被赋值为 0，然后执行下一条语句促使 Art 在 0 时延后被赋值为 1。因此对 Art 的 0 赋值被丢弃。&lt;/p&gt;
&lt;h4&gt;非阻塞性过程赋值&lt;/h4&gt;
&lt;p&gt;在费阻塞性过程赋值中，使用赋值符号 &lt;code&gt;&amp;#x3C;=&lt;/code&gt; 例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;begin
    Load &amp;#x3C;= 32;
    RegA &amp;#x3C;= Load;
    RegB &amp;#x3C;= Store;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在非阻塞性过程赋值中，对目标的赋值是非阻塞的，但是可以预定在将来某个时间步发生。当非阻塞性过程赋值被执行时，计算右端表达式，右端值被赋于左端目标，并继续执行下一条语句。预定的最早输出将在当前时间步结束时，这种情况发生在赋值语句中没有时延时。即对左端目标赋值。&lt;/p&gt;
&lt;p&gt;下面的例子更进一步解释这种赋值特征。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;initial
    begin
        Clr &amp;#x3C;= #5 1;
        Clr &amp;#x3C;= #4 0;
        Clr &amp;#x3C;= #10 0;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一条语句的执行使 Clr 在第 5 个时间单位被赋于值 1，第二条语句的执行使 Clr 在第 4 个时间单位被赋值为 0，最终，第 3 条语句的执行使 Clr 在第 10 个时间单位被赋值为 0。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/753b41ecb7de0935f442cc3c42ada263.png&quot; alt=&quot;image-20260511180259664&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;initial
    begin
        Cbn &amp;#x3C;= 0;
        Cbn &amp;#x3C;= 1;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 initial 语句执行后，因为同时对同一寄存器变量有多个赋值， Cbn 的值变得不确定，即 Cbn = x。Verilog HDL 标准中没有规定这种情况下应该怎么处理，那么根据特定的 Verilog 模拟器的事件调度算法，Cbn 将被赋值为 1 或 0。&lt;/p&gt;
&lt;p&gt;下面是同时使用阻塞性和非阻塞性过程赋值的实例。注意他们的区别。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;reg[0:2] Q_State;
initial
    begin
        Q_State = 3&apos;b011;
        Q_State &amp;#x3C;= 3&apos;b100;
        $display(&quot;Current value of Q_State is %b&quot;, Q_State);
        #5;
        $display(&quot;The delayed value of Q_State is %b&quot;, Q_State);
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后有以下结果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;Current value of Q_State is 011
The delayed value of Q_State is 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一条语句直接将 Q_State 赋值为 011，然后在第一个时间单位结束之后，非阻塞性赋值语句将 Q_State 赋值为 100。&lt;/p&gt;
&lt;h4&gt;连续赋值与过程赋值的比较&lt;/h4&gt;
&lt;p&gt;连续赋值和过程赋值有什么不同之处：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/2026/05/fe4fc9b4afa2256bbddcd5c3e33a2ee7.png&quot; alt=&quot;image-20260511181537683&quot;&gt;&lt;/p&gt;
&lt;p&gt;下面进一步解释这些差别：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module Procedural;
    reg A, B, Z;
    
    always
        @(B) begin
            Z = A;
            A = B;
        end
endmodule

module Continuous;
    wire A, B, Z;
    
    assign Z = A;
    assign A = B;
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;假定 B 在 10 ns 时有一个事件，在过程性赋值模块中，两条过程语句被依序执行。A 在 10 ns 时得到 B 的新值。Z 没有得到 B 的值，因为赋值给 Z 发生在赋值给 A 之前。在连续性赋值语句模块中，第二个连续赋值被触发，因为这里有一个关于 B 的事件。这引起了关于 A 的事件。A 引起了第一个连续赋值被执行，这相应引起 Z 得到了 A 的值。Z的新值为 A 而不是 B。然而，如果时间发生在 A 上，过程性模块中的 always 语句不执行，因为 A 不在那个 always 语句的实时控制事件清单中。然而连续赋值语句中的第一个连续赋值执行，并且 Z 得到 A 的新值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;if 语句&lt;/h3&gt;
&lt;p&gt;if 语句的语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;if (condition)
    pocedural_statement_1
{else if(condition2)
    procedural_statement_2}
{else
	procedural_statement_3}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果对 condition_1 求值的结果为一个非零值，那么 procedural_statement_1 被执行，如果 condition_1 的值为 0、x 或 z，那么 procedural_statement_1 不执行。&lt;/p&gt;
&lt;p&gt;如果存在一个 else 分支，那么这个分支就被执行。以下是一个例子。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;if(Sum &amp;#x3C; 60)
    begin
        Grade = C;
        Total_C = Total_C + 1;
    end
else if (Sum &amp;#x3C; 75)
    begin
        Grade = B;
        Total_B = Total_B + 1;
    end
else
    begin
        Grade = A;
        Total_A = Total_A + 1;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意条件表达式必须总是被括起来，如果使用 if-if-else 格式，那么可能会有二义性。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;if(Clk)
    if(Reset)
        Q = 9;
else
    Q = D;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问题是最后一个 else 到底属于哪一个 if？它是属于第一个 if 的条件 Clk 还是属于第二个 if 的条件 Reset ? 在 Verilog HDL 中，else永远与最近的没有 else 的 if 来解决。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;if (Sum &amp;#x3C; 100)
    Sum = Sum + 10;
if (Nickel_In)
    Deposit = 5;
else if (Dime_In)
    Deposit = 10;
else if (Quarter_In)
    Deposit = 25;
else
    Deposit = ERROR;

if (Ctrl)
    begin
        if (~Ctrl2)
            Mux = 4&apos;d2;
        else
            Mux = 4&apos;d1;
    end
else
    begin
        if (~Ctrl2)
            Mux = 4&apos;d8;
        else
            Mux = 4&apos;d4;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;case 语句&lt;/h3&gt;
&lt;p&gt;case 语句是一个多路条件分支形式，其语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;case (case_expr)
    case_item_expr{, case_item_expr}: procedural_statement
        ...
        ...
        [default: procedural_statement]
endcase
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;case 语句首先对条件表达式 case_expr 求值，然后一次对各个分支项切纸进行比较，第一个与条件表达式值相匹配的分支中的语句被执行。可以在 1 个分支中定义多个分支箱；这些值不需要互斥。缺省分支覆盖所有没有被分支白噢大师覆盖的其他分支。&lt;/p&gt;
&lt;p&gt;分支表达式和各个分支表达式不必都是常量表达式。在 case 语句中，x 和 z 值作为文字值进行比较。case 语句如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;parameter
	MON = 0, TUE = 1, WED = 2, THU = 3, FRI = 4, SAT = 5, SUN = 6;
reg [0:2] Day;
integer Pocket_Money;

case (Day)
    TUE: Pocket_Money = 6;
    MON,
    WEB: Pocket_Money = 2;
    FRI,
    SAT,
    SUN: Pocket_Money = 7;
    default: Pocket_Money = 0;
endcase
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 Day 的值位 MON 或者 WED，就选择分支 2。分支 3 覆盖了值 FRI / SAT / SUN，而分支 4 覆盖了余下的所有值，即 THU 和 位向量 111。case 语句的另一个实例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;module ALU(A, B, OpCode, E);
    input [3:0] A, B;
    input [1:2] OpCode;
    output [7:0] Z;
    reg [7:0] Z;
    parameter
    ADD_INSTR = 2&apos;b10,
    SUB_INSTR = 2&apos;b11,
    MULT_INSTR = 2&apos;b01,
    DIV_INSTR = 2&apos;b00;
    always
        @(A or B or OpCode)
        case (OpCode)
            ADD_INSTR: Z = A + B;
            SUB_INSTR: Z = A - B;
            MULT_INSTR: Z = A * B;
            DIV_INSTR: Z = A / B;
        endcase
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 case 表达式和分支箱表达式的长度不同会发生什么呢？在这种情况下，在进行任何比较浅所有的 case 表达式都同一位这些表达式的最长长度。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;case (3&apos;b101 &amp;#x3C;&amp;#x3C; 2)
    3&apos;b100: $display(&quot;First branch taken!&quot;);
    4&apos;b0100: $display(&quot;Second branch taken!&quot;);
    5&apos;b10100: $display(&quot;Third branch taken!&quot;);
    default: $display(&quot;Default branch taken!&quot;);
endcase
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;产生：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Third branch taken!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为第三个分支项表达式为 5 位，所有的分支箱表达式和条件表达式长度统一为 5。当计算 &lt;code&gt;3&apos;b101 &amp;#x3C;&amp;#x3C; 2&lt;/code&gt; 时，结果为 5&apos;b10100，并选择第 3 个分支。&lt;/p&gt;
&lt;h4&gt;case 中的无关位&lt;/h4&gt;
&lt;p&gt;在上文中，x 和 z 只从字面上解释，即作为 x 和 z 值。case 还有其他两种形式： casex 和 casez。这些形式对 x 和 z 值使用不同的解释。除了关键字 casex 和 casez 以外，语法与 case 语句完全一致。&lt;/p&gt;
&lt;p&gt;在 casez 语句中，出现在 case 表达式和任意分支项表达式中的值 z 被认为是无关值，即那个位被忽略。&lt;/p&gt;
&lt;p&gt;在 casex 语句中，值 x 和 z 都被认为是无关位。casez 语句实例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;case(Mask)
    4&apos;b1???: Dbus[4] = 0;
    4&apos;b01??: Dbus[3] = 0;
    4&apos;b001?: Dbus[2] = 0;
    4&apos;b0001: Dbus[1] = 0;
endcase
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;?&lt;/code&gt; 字符可以被用来代替字符 z，表示无关位。casez 语句表示如果 Mask 的第一位是 1，那么将 Dbus[4] 赋值为 0。如果 Mask 的第一位是 0，并且第 2 位是 1，那么 Dbus[3] 被赋值为 0，并以此类推。&lt;/p&gt;
&lt;h3&gt;循环语句&lt;/h3&gt;
&lt;p&gt;Verilog HDL 中有四类循环语句：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;forever 循环&lt;/li&gt;
&lt;li&gt;repeat 循环&lt;/li&gt;
&lt;li&gt;while 循环&lt;/li&gt;
&lt;li&gt;for 循环&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;forever 循环语句&lt;/h4&gt;
&lt;p&gt;这一形式的循环语句形式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;forever
    procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此循环语句连续执行过程语句。因此为了跳出这样的循环，中止语句可以与过程语句共同使用。同时，在过程语句中必须使用某种形式的时序控制，否则，forever 循环将在 0 时延后永远循环下去。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;initial
    begin
        Clock = 0;
        #5 forever
            Clock = ~Clock;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一实例产生时钟波形；时钟首先初始化为 0，并一直保持到第 5 个时间单位。此后每隔 10 个时间单位，Clock 反相一次。&lt;/p&gt;
&lt;h4&gt;repeat 循环语句&lt;/h4&gt;
&lt;p&gt;repeat 循环语句如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;repeat(loop_count)
    procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种循环语句执行指定次数的过程语句。如果循环计数表达式的值为 x 或 z，那么循环次数按 0 处理。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;repeat(Count)
    Sum = Sum + 10;

repeat(ShiftBy)
    P_Reg = P_Reg &amp;#x3C;&amp;#x3C; 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;repeat 循环语句与重复事件控制不同&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;// Type I
repeat(Count)
    @(posedge Clk) Sum = Sum + 1;

// Type II
Sum = repeat(Count) @(posedge Clk) Sum + 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的 Type I 表示计数的次数，也就是说后面的 Sum 在语句结束后变成了 Sum + Count，但是 Type II 中，程序在第一时间保留了 Sum + 1 的快照保存起来，之后等待 Count 个时钟上升沿，将之前存快照的 Sum + 1 的值赋值给 Sum。&lt;/p&gt;
&lt;p&gt;下面是一个小练习，你可以尝试解读这是什么意思：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;repeat(NUM_OF_TIMES) @(negedge ClockZ);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表示在程序在这里等待了 NUM_OF_TIMES 个时钟负沿。&lt;/p&gt;
&lt;h4&gt;while 循环语句&lt;/h4&gt;
&lt;p&gt;while 循环语句的语法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;while(condition)
    procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个循环语句执行过程赋值语句直到 condition 为假。如果开始的时候 condition 为假，那么里面的语句压根不会执行。这里说的 condition 为假的意思是 condition 为 &lt;strong&gt;0 / x / z&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，下面的语句中也会按照假处理：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;reg Acc;
wire BY;
while(BY &gt; 0)
    begin
        Acc = Acc &amp;#x3C;&amp;#x3C; 1;
        By = By - 1;
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;for 循环语句&lt;/h4&gt;
&lt;p&gt;for 循环语句的形式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-verilog&quot;&gt;for(initial_assignment; condition; step_assignment)
    procedural_statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个 for 循环语句按照指定的次数重复执行过程赋值语句若干次。初始赋值 initial_assignment 给出循环变量的初始值。condition 条件表达式指定循环在什么情况下必须结束。只要条件为真，循环中的语句就执行。step_assignment里面一般是 &lt;code&gt;i += 1&lt;/code&gt; 之类的这种控制循环变量的语句。&lt;/p&gt;</content:encoded></item><item><title>Lesson 1</title><link>https://nkns.cc/notes/patterson/risc-v-chapteri</link><guid isPermaLink="true">https://nkns.cc/notes/patterson/risc-v-chapteri</guid><description>I want to build a kernel</description><pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;计算机概要与技术&lt;/h1&gt;
&lt;p&gt;决定速度的因素：&lt;strong&gt;算法、编程语言、编译器、体系结构、处理器和存储系统、IO&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;计算机系统结构中的 8 个伟大思想&lt;/p&gt;
&lt;p&gt;面向摩尔定律的设计&lt;/p&gt;
&lt;p&gt;使用抽象简化设计&lt;/p&gt;
&lt;p&gt;加速大概率事件&lt;/p&gt;
&lt;p&gt;通过并行提高性能&lt;/p&gt;
&lt;p&gt;通过流水线提高性能&lt;/p&gt;
&lt;p&gt;通过预测提高性能&lt;/p&gt;
&lt;p&gt;存储器层次&lt;/p&gt;
&lt;p&gt;通过冗余提高可靠性&lt;/p&gt;</content:encoded></item><item><title>Chapter VIII</title><link>https://nkns.cc/notes/csapp/chapter_viii</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_viii</guid><description>CSAPP NOTE CHAP VIII</description><pubDate>Fri, 07 Nov 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 8 异常和中断&lt;/h1&gt;
&lt;h2&gt;基本概念&lt;/h2&gt;
&lt;h4&gt;中断 (Interrupt)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;来自CPU&lt;strong&gt;外部&lt;/strong&gt;的事件，由外部设备（如I/O控制器）产生。&lt;/li&gt;
&lt;li&gt;也称为&quot;异步中断&quot;，因为它与CPU的指令执行无关。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;异常 (Exception)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;来自CPU&lt;strong&gt;内部&lt;/strong&gt;的事件，是执行指令时产生的&quot;内部错误&quot;或特殊请求。&lt;/li&gt;
&lt;li&gt;也称为&quot;同步中断&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;处理过程&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;中断/异常请求&lt;/li&gt;
&lt;li&gt;CPU响应（中止当前程序）&lt;/li&gt;
&lt;li&gt;转去执行处理程序&lt;/li&gt;
&lt;li&gt;处理结束，返回断点继续执行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043916264.png&quot; alt=&quot;image-20251116043916264&quot;&gt;&lt;/p&gt;
&lt;h2&gt;中断和异常的分类&lt;/h2&gt;
&lt;h3&gt;异常的分类&lt;/h3&gt;
&lt;h4&gt;故障 (Faults)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;可纠正的错误。&lt;/li&gt;
&lt;li&gt;在指令&lt;em&gt;执行期间&lt;/em&gt;检测到。&lt;/li&gt;
&lt;li&gt;处理后，&lt;strong&gt;重新执行&lt;/strong&gt;引起故障的指令。&lt;/li&gt;
&lt;li&gt;（例如：除法出错、缺页）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;陷阱 (Traps)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;有意的异常。&lt;/li&gt;
&lt;li&gt;在指令&lt;em&gt;执行之后&lt;/em&gt;通知系统。&lt;/li&gt;
&lt;li&gt;处理后，&lt;strong&gt;执行下一条&lt;/strong&gt;指令。&lt;/li&gt;
&lt;li&gt;（例如：软中断 INT n、单步调试）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;中止 (Aborts)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;严重的、不可恢复的错误。&lt;/li&gt;
&lt;li&gt;程序&lt;strong&gt;无法继续执行&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;（例如：硬件故障、系统表非法值）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;中断的分类&lt;/h3&gt;
&lt;h4&gt;可屏蔽中断 (INTR)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;主要来自I/O设备的请求（如键盘、磁盘）。&lt;/li&gt;
&lt;li&gt;CPU可以通过设置标志寄存器中的IF标志位来&quot;屏蔽&quot;（暂时忽略）这些中断。&lt;/li&gt;
&lt;li&gt;中断类型号由中断控制器（如8259A）提供。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;F:/Roaming/Typora/typora-user-images/image-20251116044023149.png&quot; alt=&quot;image-20251116044023149&quot;&gt;&lt;/p&gt;
&lt;h4&gt;不可屏蔽中断 (NMI)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;由严重的硬件故障引起（如掉电、存储器奇偶校验错）。&lt;/li&gt;
&lt;li&gt;这种中断无法被IF标志屏蔽，CPU必须立即响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116044047949.png&quot; alt=&quot;image-20251116044047949&quot;&gt;&lt;/p&gt;
&lt;h2&gt;中断向量表&lt;/h2&gt;
&lt;h3&gt;中断向量表 (IVT) (实模式)&lt;/h3&gt;
&lt;p&gt;中断向量表是存放各种中断处理程序入口地址的表。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;位置:&lt;/strong&gt; 固定在主存的 &lt;code&gt;00000H&lt;/code&gt; 到 &lt;code&gt;003FFH&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大小:&lt;/strong&gt; 1KB。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中断数:&lt;/strong&gt; 共 256 种中断 (类型号 0 ~ FFH)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表项:&lt;/strong&gt; 每个中断占 4 字节，用于存放其处理程序的入口地址 (CS:IP)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;地址计算:&lt;/strong&gt; 中断向量地址 = 中断类型号 * 4&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116044137316.png&quot; alt=&quot;image-20251116044137316&quot;&gt;&lt;/p&gt;
&lt;h3&gt;中断描述符表（IDT）(保护模式)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在保护方式下，中断向量表称为 &lt;strong&gt;中断描述符表 (IDT)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;IDT 的起始物理地址由 &lt;code&gt;IDTR&lt;/code&gt; 寄存器决定。&lt;/li&gt;
&lt;li&gt;每个表项称为 &lt;strong&gt;门描述符&lt;/strong&gt;，占 8 个字节，存放处理程序的入口地址、类别、权限等。&lt;/li&gt;
&lt;li&gt;共 256 个表项，占用 2KB 主存空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116044429898.png&quot; alt=&quot;image-20251116044429898&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116044434865.png&quot; alt=&quot;image-20251116044434865&quot;&gt;&lt;/p&gt;
&lt;h2&gt;中断有关指令&lt;/h2&gt;
&lt;h3&gt;1. 软中断&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;形式:&lt;/strong&gt; &lt;code&gt;INT n&lt;/code&gt; (n 为 0-255)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能:&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;将标志寄存器 &lt;code&gt;EFLAGS&lt;/code&gt; 压栈。&lt;/li&gt;
&lt;li&gt;清除 &lt;code&gt;IF&lt;/code&gt; (中断允许) 和 &lt;code&gt;TF&lt;/code&gt; (陷阱) 标志位。&lt;/li&gt;
&lt;li&gt;将当前代码段 &lt;code&gt;CS&lt;/code&gt; 压栈。&lt;/li&gt;
&lt;li&gt;将下一条指令的地址 &lt;code&gt;EIP&lt;/code&gt; (返回地址) 压栈。&lt;/li&gt;
&lt;li&gt;从中断向量表 &lt;code&gt;(4*n)&lt;/code&gt; 处取出 &lt;code&gt;IP&lt;/code&gt;，&lt;code&gt;(4*n+2)&lt;/code&gt; 处取出 &lt;code&gt;CS&lt;/code&gt;，并加载到 &lt;code&gt;CS:EIP&lt;/code&gt; 寄存器，跳转到中断服务程序。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 中断返回&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;形式:&lt;/strong&gt; &lt;code&gt;IRET&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能:&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;从堆栈弹出 &lt;code&gt;EIP&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;从堆栈弹出 &lt;code&gt;CS&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;从堆栈弹出 &lt;code&gt;EFLAGS&lt;/code&gt; (这会自动恢复之前的 &lt;code&gt;IF&lt;/code&gt; 和 &lt;code&gt;TF&lt;/code&gt; 标志位)。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;中断处理举例&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;需求：&lt;/strong&gt; 实模式下，用空闲的中断类型号 &lt;code&gt;45H&lt;/code&gt;，实现将 &lt;code&gt;AX&lt;/code&gt; 中的内容以十六进制形式在屏幕上输出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;按指定功能编制中断处理子程序 &lt;code&gt;INTR45&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在中断向量表中，找到中断号 &lt;code&gt;45H&lt;/code&gt; 的位置。&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;INTR45&lt;/code&gt; 的入口地址 (CS:IP) 送入中断向量表 &lt;code&gt;4 * 45H&lt;/code&gt; 处。&lt;/li&gt;
&lt;li&gt;在主程序中通过 &lt;code&gt;INT 45H&lt;/code&gt; 调用新增的中断处理子程序。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;示例代码 (MASM 风格，仅为示例):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;; 设置中断向量
START:		MOV AX, 0
			MOV DS, AX      ; DS = 0, 指向 IVT
			
			CLI             ; 关中断
			MOV WORD PTR DS:[45H * 4], OFFSET INTR45    ; 设置 IP
			MOV WORD PTR DS:[45H * 4 + 2], CS           ; 设置 CS
			STI             ; 开中断

; ... 主程序 ...
; 调用中断
			MOV AX, [SI]
			INT 45H         ; 调用软中断
			ADD SI, 2
			LOOP L1
; ... 退出 ...

; --- 中断服务程序 ---
INTR45		PROC FAR
			PUSHA           ; 1. 保护现场
			; ...
			; (此处省略将 AX 中内容转为十六进制并显示的具体代码)
			; ...
			POPA            ; 5. 恢复现场
			IRET            ; 6. 中断返回
INTR45		ENDP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在中断处理子程序中，必须使用 &lt;code&gt;PUSHA&lt;/code&gt; / &lt;code&gt;POPA&lt;/code&gt; 保护和恢复现场。&lt;/li&gt;
&lt;li&gt;设置中断向量时，应先 &lt;code&gt;CLI&lt;/code&gt; 关中断，设置完毕后再 &lt;code&gt;STI&lt;/code&gt; 开中断，防止在修改过程中发生中断。&lt;/li&gt;
&lt;li&gt;必须使用 &lt;code&gt;IRET&lt;/code&gt; 指令实现中断返回。&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Chapter VII</title><link>https://nkns.cc/notes/csapp/chapter_vii</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_vii</guid><description>CSAPP NOTE CHAP VII</description><pubDate>Thu, 06 Nov 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 7 输入输出&lt;/h1&gt;
&lt;h2&gt;输入输出子系统&lt;/h2&gt;
&lt;h3&gt;I/O子系统层次结构&lt;/h3&gt;
&lt;p&gt;I/O子系统包括I/O软件和I/O硬件。软件分为用户空间和内核空间。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用户空间:&lt;/strong&gt; 用户程序 (printf), 运行时系统&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核空间:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;与设备无关的I/O软件&lt;/li&gt;
&lt;li&gt;设备驱动程序&lt;/li&gt;
&lt;li&gt;中断服务程序&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I/O硬件&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043721158.png&quot; alt=&quot;image-20251116043721158&quot;&gt;&lt;/p&gt;
&lt;h3&gt;I/O软件核心&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043758585.png&quot; alt=&quot;image-20251116043758585&quot;&gt;&lt;/p&gt;
&lt;h4&gt;与设备无关的I/O软件&lt;/h4&gt;
&lt;p&gt;提供统一的抽象视图，隐藏设备差异。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为设备驱动提供统一接口&lt;/li&gt;
&lt;li&gt;缓冲处理 (管理内核缓冲区)&lt;/li&gt;
&lt;li&gt;错误报告 (处理通用错误)&lt;/li&gt;
&lt;li&gt;逻辑块大小处理 (统一块大小)&lt;/li&gt;
&lt;li&gt;打开/关闭文件 (抽象为文件)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;设备驱动程序&lt;/h4&gt;
&lt;p&gt;与具体设备相关，由设备商提供。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包含I/O端口的访问逻辑&lt;/li&gt;
&lt;li&gt;实现具体I/O操作&lt;/li&gt;
&lt;li&gt;处理特定设备的中断&lt;/li&gt;
&lt;li&gt;根据I/O控制方式 (程序、中断、DMA) 实现&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;I/O硬件连接&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043813922.png&quot; alt=&quot;image-20251116043813922&quot;&gt;&lt;/p&gt;
&lt;h2&gt;I/O端口和I/O指令&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043833389.png&quot; alt=&quot;image-20251116043833389&quot;&gt;&lt;/p&gt;
&lt;h3&gt;设备控制器&lt;/h3&gt;
&lt;p&gt;I/O设备的电子部分，包含数据缓冲、状态/控制寄存器。这些寄存器统称为 &lt;strong&gt;&quot;I/O端口&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;I/O端口编址&lt;/h3&gt;
&lt;p&gt;为I/O端口编号。IA-32采用独立编址，I/O地址空间与内存地址空间分离，允许64K个8位端口。&lt;/p&gt;
&lt;h3&gt;I/O指令&lt;/h3&gt;
&lt;p&gt;访问I/O端口的特权指令。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IN:&lt;/strong&gt; 从端口读数据到AL/AX。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OUT:&lt;/strong&gt; 写数据从AL/AX到端口。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;数据传送方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无条件传送:&lt;/strong&gt; CPU直接读写，假定外设恒定就绪，只用于同步设备。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询传送:&lt;/strong&gt; CPU循环查询外设状态（&quot;忙-等待&quot;），若准备好则传送。浪费CPU资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043901704.png&quot; alt=&quot;image-20251116043901704&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中断传送:&lt;/strong&gt; CPU启动I/O后执行其他程序。外设就绪后发&quot;中断请求&quot;，CPU响应并处理数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043851632.png&quot; alt=&quot;image-20251116043851632&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DMA (直接存储器访问):&lt;/strong&gt; CPU让出总线控制权，外设与主存之间直接成批传送数据，完成后再中断CPU。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Chapter VI</title><link>https://nkns.cc/notes/csapp/chapter_vi</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_vi</guid><description>CSAPP NOTE CHAP VI</description><pubDate>Wed, 05 Nov 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 6 层次结构存储系统&lt;/h1&gt;
&lt;h2&gt;存储器的层次结构&lt;/h2&gt;
&lt;h3&gt;存储器的分类&lt;/h3&gt;
&lt;h4&gt;按存取方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;随机存取 (RAM):&lt;/strong&gt; 按地址访问，时间与地址无关。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;顺序存取 (SAM):&lt;/strong&gt; 按顺序存放和读出，如磁带。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;直接存取 (DAM):&lt;/strong&gt; 定位到区域后顺序存取，如磁盘。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;相联存储 (CAM):&lt;/strong&gt; 按内容检索，如快表。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;按特性分类&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;按可更改性:&lt;/strong&gt; 读写存储器 (RAM) / 只读存储器 (ROM)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按断电可保存性:&lt;/strong&gt; 非易失性 (ROM, 磁盘) / 易失性 (RAM, Cache)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按功能:&lt;/strong&gt; 高速缓存 (Cache), 主存 (Main Memory), 辅存 (Disk)。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;存储器的层次结构&lt;/h3&gt;
&lt;h4&gt;为什么需要层次结构？&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;单一存储元件很难同时满足大容量、高速度和低成本的要求。&lt;/li&gt;
&lt;li&gt;SRAM 速度快，但容量小且昂贵。&lt;/li&gt;
&lt;li&gt;磁盘容量大、成本低，但速度远低于CPU。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;核心思想&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;将各种存储器按层次组织，CPU执行时，数据在相邻两层之间复制传送，使整个系统在速度、容量和价格上取得平衡。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043522961.png&quot; alt=&quot;image-20251116043522961&quot;&gt;&lt;/p&gt;
&lt;h3&gt;局部性原理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时间局部性:&lt;/strong&gt; 被访问的某个存储单元在一个较短的时间间隔内很可能又被访问。（例如：循环中的变量）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;空间局部性:&lt;/strong&gt; 被访问的某个存储单元的邻近单元在一个较短的时间间隔内很可能也被访问。（例如：顺序执行指令、访问数组元素）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出现原因:&lt;/strong&gt; 程序指令按顺序存放，循环和子程序被重复执行；数据（尤其是数组）也连续存放并被按序访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043537498.png&quot; alt=&quot;image-20251116043537498&quot;&gt;&lt;/p&gt;
&lt;h2&gt;高速缓冲存储器&lt;/h2&gt;
&lt;h3&gt;Cache的工作原理&lt;/h3&gt;
&lt;p&gt;Cache是一种小容量高速SRAM，用于存放主存中被频繁访问的活跃程序块和数据块。&lt;/p&gt;
&lt;h4&gt;访存过程&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;CPU访存时，先检查Cache中是否有要访问的信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;命中 (Hit):&lt;/strong&gt; 若有，则直接在Cache中读写，不访问主存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺失 (Miss):&lt;/strong&gt; 若没有，则从主存中把该主存块复制到Cache中，再供给CPU。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个过程由硬件自动完成，对程序员透明。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116043558675.png&quot; alt=&quot;image-20251116043558675&quot;&gt;&lt;/p&gt;
&lt;p&gt;因此，为了提高程序的性能，程序员须编写出具有良好访问局部性的程序。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
#define M 256
#define N 256
int sumarrayrows(int a[M][N])
{
    int i, j, sum = 0;
    for (i = 0; i &amp;#x3C; M; i++)
    {
        for (j = 0; j &amp;#x3C; N; j++)
            sum += a[i][j];
    }
    return sum;
}	// fast
int sumarraycols(int a[M][N])
{
    int i, j, sum = 0;
    for (j = 0; j &amp;#x3C; N; j++)
    {
        for (i = 0; i &amp;#x3C; M; i++)
            sum += a[i][j];            
    }
    return sum;
}	// slow
int main()
{
    int a[M][N], sum;
    sum = sumarrayrows(a);
    sum = sumarraycols(a);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数组a按照行优先顺序存储&lt;/p&gt;
&lt;p&gt;函数sumarrayrows的内循环按行优先顺序访问数组a，具有良好的空间局部性&lt;/p&gt;
&lt;p&gt;函数sumarraycols的内循环，按列顺序访问数组a，空间局部性很差，执行效率低。&lt;/p&gt;</content:encoded></item><item><title>Chapter V</title><link>https://nkns.cc/notes/csapp/chapter_v</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_v</guid><description>CSAPP NOTE CHAP V</description><pubDate>Tue, 04 Nov 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 5 程序的执行&lt;/h1&gt;
&lt;h2&gt;程序执行概述&lt;/h2&gt;
&lt;p&gt;程序由指令组成，指令按顺序存放在连续的存储单元中。&lt;/p&gt;
&lt;p&gt;正常情况下，指令按其存放顺序执行。遇到需改变程序执行流程时，用相应的转移指令（包括无条件转移指令、条件转移指令、调用指令和返回指令等）来改变程序执行流程。&lt;/p&gt;
&lt;p&gt;将要执行的指令所在存储单元的地址由程序计数器PC给出，通过改变PC的值来控制执行顺序。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116041515319.png&quot; alt=&quot;image-20251116041515319&quot;&gt;&lt;/p&gt;
&lt;p&gt;CPU执行一条指令的过程可以分为以下几个步骤：&lt;/p&gt;
&lt;p&gt;①取指令。从PC所指出的存储单元中取出指令送到指令寄存器(IR)。&lt;/p&gt;
&lt;p&gt;②指令译码 对IR中的指令操作码进行译码，根据不同的指令操作码译出不同的控制信号。 根据寻址方式确定源操作数地址计算方式，若是存储器数据，则需要一次或多次访存；若是寄存器数据，则直接从寄存器取数后转到下一步进行数据操作。&lt;/p&gt;
&lt;p&gt;③源操作数地址计算并取操作数。&lt;/p&gt;
&lt;p&gt;根据寻址方式确定目的操作数地址计算方式，若是存储器数据，则需要一次或多次访存(间接寻址时);若是寄存器数据，则在进行数据操作时直接存结果到寄存器。&lt;/p&gt;
&lt;p&gt;如果是串操作或向量运算指令，则可能会并行执行或循环执行第③～⑤步多次。&lt;/p&gt;
&lt;p&gt;④执行数据操作&lt;/p&gt;
&lt;p&gt;⑤目的操作数地址计算并存结果。&lt;/p&gt;
&lt;p&gt;⑥下条指令地址计算并将其送PC。&lt;/p&gt;
&lt;p&gt;顺序执行时，下条指令地址的计算，只要将PC加上当前指令长度即可。如果译码的是转移类指令时，则需要根据条件标志、操作码和寻址方式等确定下条指令地址&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于上述过程的第①步和第②步，所有指令的操作都一样；而对于第③～⑤步，不同指令的操作可能不同，它们完全由第②步译码得到的控制信号控制。也即指令的功能由第②步译码得到的控制信号决定。对于第⑥步，若是定长指令字，处理器会在第①步取指令的同时计算出下条指令地址并送PC,然后根据指令译码结果和条件标志决定是否在第⑥步修改PC的值，因此，在顺序执行时，实际上是在取指令时计算下条指令地址，第⑥步什么也不做。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总结上述指令执行过程，每条指令的功能总是通过对以下四种基本操作进行组合来实现：&lt;/p&gt;
&lt;p&gt;①读取某存储单元内容(可能是指令或操作数或操作数地址)，并将其装入某个寄存器。&lt;/p&gt;
&lt;p&gt;②把一个数据从某个寄存器存储到给定的存储单元中。&lt;/p&gt;
&lt;p&gt;③把一个数据从某个寄存器传送到另一个寄存器或者ALU。&lt;/p&gt;
&lt;p&gt;④在ALU中进行某种算术运算或逻辑运算，并将结果传送到某个寄存器。&lt;/p&gt;
&lt;h2&gt;流水线方式下指令的执行&lt;/h2&gt;
&lt;p&gt;串行方式下，CPU在执行完一条指令后才取出下条指令执行。串行方式没有充分利用执行部件的并行性，因而指令执行效率低。&lt;/p&gt;
&lt;p&gt;指令的执行可以采用流水线方式，即将多条指令的执行相互重叠起来，以提高CPU执行指令的效率。&lt;/p&gt;
&lt;p&gt;假定一条指令流水线由如下5个流水段组成：&lt;/p&gt;
&lt;p&gt;取指令(IF):根据PC的值从存储器取出指令。&lt;/p&gt;
&lt;p&gt;指令译码(ID):产生指令执行所需的控制信号。&lt;/p&gt;
&lt;p&gt;取操作数(OF):读取存储器操作数或寄存器操作数。&lt;/p&gt;
&lt;p&gt;执行(EX):对操作数完成指定操作。&lt;/p&gt;
&lt;p&gt;写回(WB):将操作结果写入存储器或寄存器。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116041906979.png&quot; alt=&quot;image-20251116041906979&quot;&gt;&lt;/p&gt;
&lt;p&gt;如图&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如图所示，在理想状态下，完成4条指令的执行只用了8个时钟周期，若是非流水线方式的串行执行处理，则需要20个时钟周期。流水线方式充分利用了执行部件的并行性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;例：一条指令需要用以下5个阶段完成操作：
①取指：200ps;
②译码和读操作数：50ps;
③ALU操作：100ps;
④读存储器：200ps;
⑤结果写寄存器：50ps。
这条指令的总执行时间为200 + 50 + 100 + 200 + 50 = 600ps。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在串行执行情况下，指令不做切分，因此要求所有指令都在一个时钟周期内完成。&lt;strong&gt;由于系统的时钟周期是固定的，因此时钟周期需要设置为最复杂指令的延时。设上述指令为最复杂指令，则时钟周期必须设为600ps。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;系统的指令吞吐率为1/(600*10^(-12)) = 1.67GIPS。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116042038873.png&quot; alt=&quot;image-20251116042038873&quot;&gt;&lt;/p&gt;
&lt;p&gt;流水线指令示意图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116042110496.png&quot; alt=&quot;image-20251116042110496&quot;&gt;&lt;/p&gt;
&lt;p&gt;上述指令的执行时间为5个时钟周期。&lt;/p&gt;
&lt;p&gt;但在理想情况下，每个时钟周期有一条指令完成执行。指令吞吐率为1(250*10^(-12)) = 4GIPS，吞吐率是串行方式的4/1.67 = 2.4倍。&lt;/p&gt;
&lt;p&gt;按流水线方式执行指令的关键是，将指令集中所有指令都分成相同数目的功能段，并让每个功能段的执行时间都相同。&lt;/p&gt;
&lt;h3&gt;CICS 和 RISC&lt;/h3&gt;
&lt;p&gt;有利于流水线执行的指令集特征包括：&lt;/p&gt;
&lt;p&gt;l指令长度尽量一致，有利于简化取指令和指令译码操作&lt;/p&gt;
&lt;p&gt;l指令格式尽量规整，尽量保证源寄存器的位置相同。&lt;/p&gt;
&lt;p&gt;l采用load/store型指令风格。指令集中只有load指令和store指令能访问存储器，其他指令一律不能访问。可以保证除load和store指令外的其他指令在执行阶段都不访问存储器，有利于减少操作步骤，以规整流水线。&lt;/p&gt;
&lt;p&gt;数据和指令在存储器中要“对齐”存放。有利于减少访存次数，使所需数据在一个流水段内就能从存储器中得到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CISC指令系统设计的主要特点如下。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;①指令系统复杂。指令条数多，寻址方式多，指令格式多而复杂，指令长度可变，操作码长度可变。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;②指令周期长。绝大多数指令需要多个时钟周期才能完成。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③相关指令会产生显式的条件码，存放在专门的标志寄存器(或称状态寄存器)中，可用于条件转移和条件传送等指令。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④指令周期差距大。各种指令都能访问存储器，有些指令还需要多次访问存储器，使得简单指令和复杂指令所用的时钟周期数相差很大，不利于指令流水线的实现。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤难以进行编译优化。由于编译器可选指令序列增多，使得目标代码组合增加，从而增加了目标代码优化的难度。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;|                | &lt;strong&gt;简单指令&lt;/strong&gt; | &lt;strong&gt;复杂指令&lt;/strong&gt; |
| -------------- | ------------ | ------------ |
| &lt;strong&gt;使用频率&lt;/strong&gt;   | &lt;strong&gt;常使用&lt;/strong&gt;   | &lt;strong&gt;不常使用&lt;/strong&gt; |
| &lt;strong&gt;占指令系统&lt;/strong&gt; | &lt;strong&gt;20%&lt;/strong&gt;      | &lt;strong&gt;80%&lt;/strong&gt;      |
| &lt;strong&gt;占程序代码&lt;/strong&gt; | &lt;strong&gt;80%&lt;/strong&gt;      | &lt;strong&gt;20%&lt;/strong&gt;      |&lt;/p&gt;
&lt;p&gt;1975年IBM公司开始研究指令系统的合理性问题，John Cocke领导的一个研究小组提出了精简指令集计算机(Reduced Instruction Set Computer,简称RISC)的概念。&lt;/p&gt;
&lt;p&gt;RISC通过简化指令系统使计算机结构更加简单合理，从而提高机器的性能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RISC指令系统的主要特点包括：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;①指令数目少&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;②指令格式规整&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③采用load/store型指令设计风格&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④采用流水线方式执行指令&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤采用大量通用寄存器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑥采用硬连线路控制器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑦实现细节对机器级程序可见&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;①指令数目少。只包含使用频度高的简单指令。&lt;/p&gt;
&lt;p&gt;②指令格式规整。寻址方式少，指令格式少，指令长度一致，指令中操作码和寄存器编号等位置固定，便于取指令、指令译码以及提前读取寄存器内容等。&lt;/p&gt;
&lt;p&gt;③采用load/store型指令设计风格。一条指令的执行阶段最多只有一次存储器访问操作。&lt;/p&gt;
&lt;p&gt;④采用流水线方式执行指令。规整的指令格式有利于采用流水线方式执行，除load/store指令外，其他指令都只需一个或小于一个时钟周期就可完成，指令周期短。&lt;/p&gt;
&lt;p&gt;⑤采用大量通用寄存器。编译器可将更多的局部变量分配到寄存器中，并且在过程调用时通过寄存器进行参数传递而不是通过栈进行传递，以减少访存次数。&lt;/p&gt;
&lt;p&gt;⑥采用硬连线路控制器。指令少而规整使得控制器的实现变得简单，可以不用或少用微程序控制器。&lt;/p&gt;
&lt;p&gt;⑦实现细节对机器级程序可见。例如，有些RISC机器禁止一些特殊的指令序列，有些则规定条件转移指令后面必须填充若干条必须执行的指令等，这些都给编译器的设计和优化设定了相应的约束条件。&lt;/p&gt;
&lt;p&gt;RISC指令系统简单，所以：
CPU的控制逻辑简化
芯片上有更多的通用寄存器
可以采用速度较快的硬连线控制器
更适合于采用指令流水技&lt;/p&gt;</content:encoded></item><item><title>Chapter IV</title><link>https://nkns.cc/notes/csapp/chapter_iv</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_iv</guid><description>CSAPP NOTE CHAP IV</description><pubDate>Mon, 03 Nov 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 4 程序的链接&lt;/h1&gt;
&lt;p&gt;一个大的程序往往会分成多个源程序来编写，因而需要对各个不同源程序文件分别进行编译或汇编。&lt;/p&gt;
&lt;p&gt;多个源程序会生成多个不同的目标代码文件。这些目标代码文件中包含指令、数据等信息。&lt;/p&gt;
&lt;p&gt;此外，在程序中还会调用一些标准库函数，这些函数库也是一些目标代码文件。&lt;/p&gt;
&lt;p&gt;因此，编译之后，需要将目标代码文件，包括用到的函数库目标文件，链接生成一个可执行文件。&lt;/p&gt;
&lt;h2&gt;编译、汇编和链接&lt;/h2&gt;
&lt;h3&gt;编译和汇编&lt;/h3&gt;
&lt;p&gt;将源程序转化为执行程序的过程：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116034737050.png&quot; alt=&quot;image-20251116034737050&quot;&gt;&lt;/p&gt;
&lt;p&gt;对应的命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gcc hello.c -o hello.i -E
gcc hello.i -o hello.s -S
gcc hello.s -o hell.o -c
gcc hello.o -o hello
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;链接&lt;/h2&gt;
&lt;p&gt;将多个可重定位的目标文件合成一个可执行文件。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116034832125.png&quot; alt=&quot;image-20251116034832125&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;main.c：
int add(int ,int);
int main()
{
return add(20, 13);
}
test.c：
int add(int i,int j) {
   int x=i+j;
   return x;
}
/*
gcc -c -o main.o main.c
gcc -c -o test.o test.c
ld -o test main.o test.o
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将不同类型的源程序，编译、链接为一个执行程序：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116034951866.png&quot; alt=&quot;image-20251116034951866&quot;&gt;&lt;/p&gt;
&lt;p&gt;链接的任务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;符号解析&lt;/p&gt;
&lt;p&gt;将符号的引用与一个确定的符号定义建立关联。&lt;/p&gt;
&lt;p&gt;符号：全局变量名、函数名、静态的局部变量名&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;非静态的局部变量名不是符号&lt;/strong&gt;。&lt;strong&gt;参数名不是符号&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;编译器将所有符号存放在目标文件的符号表(symbol table)中。&lt;/p&gt;
&lt;p&gt;符号表是一个结构数组，每个表项包含符号名、长度和位置信息等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重定位&lt;/p&gt;
&lt;p&gt;在合并生成执行文件时，重新确定每条指令的地址、每个数据的地址。&lt;/p&gt;
&lt;p&gt;在指令中更新所引用符号对应的地址。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt; 观查前例中的main.o、test.o和test文件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;objdump -d test.o&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116035217817.png&quot; alt=&quot;image-20251116035217817&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;objdump -d test&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116035306823.png&quot; alt=&quot;image-20251116035306823&quot;&gt;&lt;/p&gt;
&lt;h2&gt;目标文件格式&lt;/h2&gt;
&lt;p&gt;目标代码（Object Code）：机器语言代码&lt;/p&gt;
&lt;p&gt;目标文件（Object File）：包含目标代码的文件&lt;/p&gt;
&lt;p&gt;广义的目标文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可重定位目标文件：编译或汇编输出的目标文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可执行目标文件：链接输出的目标文件&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;狭义&lt;/strong&gt;的目标文件：&lt;strong&gt;仅指可重定位目标文件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可重定位目标文件和可执行目标文件，可以看作是目标文件的两种视图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;链接视图（被链接）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行视图（被执行）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116035441132.png&quot; alt=&quot;image-20251116035441132&quot;&gt;&lt;/p&gt;
&lt;p&gt;可重定位目标文件主要由不同的节(section)组成。不同的节描述了目标文件中不同类型的信息及其特征。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;text&lt;/code&gt; 代码节&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;rodata&lt;/code&gt; 只读数据节&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;data&lt;/code&gt; 已初始化全局数据节&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;bss&lt;/code&gt; 未初始化全局数据节&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;symtab&lt;/code&gt; 节&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​	符号表(symbol table)。符号包括函数名、全局变量名。符号表保存与这些符号相关的信息。每个可重定位目标文件都有一个.symtab节。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.rel.text&lt;/code&gt; .text节相关的可重定位信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.rel.data&lt;/code&gt; .data节相关的可重定位信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.debug&lt;/code&gt; 节 调试用符号表。带-g选项的gcc命令会得到这张表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;line&lt;/code&gt; 节 C源程序中的行号和.text节中机器指令之间的映射。带-g选项的gcc命令会得到这张表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.strtab&lt;/code&gt; 节&lt;/p&gt;
&lt;p&gt;字符串表，包括.symtab节和.debug节中的符号以及节头表中的节名字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可执行目标文件由不同的段（segment）组成，描述节如何映射到存储段中。可多个节映射到同一段。如：可合并.data节和.bss节,并映射到一个可读可写数据段中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可执行文件和共享库文件必须具有程序头表，而可重定位目标文件无需程序头表。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可重定位目标文件必须具有节头表。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;符号表和符号解析&lt;/h2&gt;
&lt;h3&gt;符号和符号表&lt;/h3&gt;
&lt;p&gt;每个可重定位目标文件都有一个符号表，它包含了在该模块中定义的符号。有三种符号：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Global symbols：全局符号&lt;/p&gt;
&lt;p&gt;由该模块定义并能被其他模块引用的符号，包括函数和全局变量&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;External symbols：外部符号&lt;/p&gt;
&lt;p&gt;由其他模块定义并被该模块引用的全局符号&lt;/p&gt;
&lt;p&gt;注：在由多个模块组成的一个程序中，每个外部符号，都应该有一个对应的全局符号。否在链接时，会报“外部符号未定义”的错误&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Local symbols：局部符号&lt;/p&gt;
&lt;p&gt;由该模块定义，且仅由该模块引用的符号。例如在模块中定义的带&lt;strong&gt;static&lt;/strong&gt;的函数和全局变量，带static的局部变量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注：程序中的局部变量不是局部符号&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// 符号表（symtab）中每个条目的结构如下：
typedef  struct {
    int    name;    /*符号对应字符串在strtab节中的偏移量*
    int    value;    /*在对应节中的偏移量，可执行文件中是虚拟地址*/
    int    size;      /*符号对应目标所占字节数*/
   char  type: 4,  /*符号对应目标的类型：数据、函数、源文件、节*/
   	  binding: 4; /*符号类别：全局符号、局部符号、弱符号*/
   char  reserved;
   char  section;  /*符号对应目标所在的节，或其他情况*/
} Elf_Symbol;

// 例子
# main.c
int buf[2] = {1, 2};
extern void swap();
int main() 
{
  swap();
  return 0;
}
# swap.c
extern int buf[]; 
int *bufp0 = &amp;#x26;buf[0];
static int *bufp1;
void swap()
{
  int temp;
  bufp1 = &amp;#x26;buf[1];
  temp = *bufp0;
  *bufp0 = *bufp1;
  *bufp1 = temp;
}
/*
main.c 中的全局变量名buf为全局符号
main.c 中的函数名swap为外部符号
swap.c中的外部变量名buf为外部符号
swap.c中的函数名swap为全局符号
swap.c中的全局变量名bufp0为全局符号
swap.c 中的static变量名bufp1为局部符号
swap.c中，函数swap中的局部变量temp，不是局部符号。

*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116040133280.png&quot; alt=&quot;image-20251116040133280&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116040145444.png&quot; alt=&quot;image-20251116040145444&quot;&gt;&lt;/p&gt;
&lt;h3&gt;符号解析&lt;/h3&gt;
&lt;p&gt;将每个模块中引用的符号与某个目标模块中的定义符号建立关联。&lt;/p&gt;
&lt;p&gt;首先定义三个集合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;集合 E：可重定位目标文件集合&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;集合 U：是未解析符号的集合&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;集合 D：定义符号的集合&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;初始化E、U、D为空；
for 命令行中的文件f {
  if f为目标文件 {
    f --&gt; E；
    f中的未解析符号 --&gt; U；
    f中的定义符号 --&gt; D；
    if 添加了重复的定义符号
      报错退出；
  } else if f为库文件{
    for f中的模块m {
      for U中的未定义符号x {
              if x在m中定义 {
          将x从U转移到D中；
          if 添加了重复的定义符号
            报错退出；
        }
      }
    }
  }
}
合并E中的目标文件为可执行目标文件；
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;静态库的生成与使用&lt;/h3&gt;
&lt;p&gt;Linux 中，静态库文件采用存档档案(archive)的文件格式，文件后缀为.a。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// myproc1.c
#include &amp;#x3C;stdio.h&gt;
void myfunc1()
{
printf(&quot;%s&quot;,&quot;This is myfuncl from mylib!\n&quot;);
)

// myproc2.c
#include &amp;#x3C;stdio.h&gt;
void myfunc2()
{
printf(&quot;%s&quot;,&quot;This is myfunc2 from mylib!\n&quot;);
)
/*
生成静态库的过程：
gcc  -c  myprocl.c  myproc2.c
ar  rcs  mylib.a  myproc1.o  myproc2.o

*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;程序main.c使用了mylib.a中的函数myfunc1&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void myfuncl(viod);
int main()
{
myfunc1();
return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;main.c的编译链接过程：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gcc -c main.c&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gcc -static -o myproc main.o ./my1ib.a&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注：命令中使用 -static 选项指示链接器应生成一个静态链接的可执行目标文件。&lt;/p&gt;
&lt;h2&gt;重定位&lt;/h2&gt;
&lt;p&gt;链接器完成符号解析后，进入重定位过程。此过程分两步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;重定位节和符号定义&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重定位节中的符号引用&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ELF中重定位条目格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
 int offset;   /*节内偏移*/
  int symbol:24, /*所绑定符号*/
​    type: 8;     /*重定位类型*/
 } Elf32_Rel;
&quot;offset&quot;是需要修改的引用的节偏移
&quot;symbol&quot;标识引用应指向的符号
&quot;type&quot;指示链接器如何修改新引用
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重定位类型主要有两种：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lR_386_PC32&lt;/strong&gt;：重定位使用32位PC相关地址引用&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lR_386_32&lt;/strong&gt;：重定位使用32位绝对地址引用&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116040545725.png&quot; alt=&quot;image-20251116040545725&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;/*重定位算法*/
for 每个输入节s {
  for s中的每个重定位表项r {
    s的起始地址 + r.offset --&gt; refptr； // refptr为指向程序中的符号引用的指针
    if (r.type == R_386_PC32) {
      refaddr = s的起始地址 + r.offfset；// refaddr为符号引用的运行时地址
      *refptr = ADDR(r.symbol) + *refptr - refaddr;  // 修改符号引用的内容为相对地址
    }
    if (r.type == R_386_32)
      *refptr = ADDR(r.symbol) + *refptr;// 修改符号引用的内容为绝对地址
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;可执行文件的加载&lt;/h2&gt;
&lt;p&gt;通过调用 execve 系统调用函数来调用加载器&lt;/p&gt;
&lt;p&gt;加载器（loader）根据可执行文件的程序（段）头表中的信息，将可执行文件的代码和数据从磁盘“拷贝”到存储器中&lt;/p&gt;
&lt;p&gt;加载后，将 PC（EIP）设定指向 Entry point (即符号_start处)，最终执行 main 函数，以启动程序执行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116040701720.png&quot; alt=&quot;image-20251116040701720&quot;&gt;&lt;/p&gt;
&lt;p&gt;可执行文件的存储器映像：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116040714049.png&quot; alt=&quot;image-20251116040714049&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Chapter III-6</title><link>https://nkns.cc/notes/csapp/chapter_iii_6</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_iii_6</guid><description>CSAPP NOTE CHAP III-6</description><pubDate>Tue, 07 Oct 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 3.6 越界访问和缓冲区攻击&lt;/h1&gt;
&lt;h2&gt;缓冲区溢出&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt; C 语言没有对数组的边界作出约束，因此访问下标可能越界，称为 &lt;strong&gt;缓冲区溢出&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;char s[10];&lt;/li&gt;
&lt;li&gt;s[10] = -1; // 越界&lt;/li&gt;
&lt;li&gt;char *p = s;&lt;/li&gt;
&lt;li&gt;*(p + 13) = 40; // 越界&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​	第 2 行、第 4 行的数组访问，都超出了数组的边界，产生了缓冲区溢出。&lt;/p&gt;
&lt;h2&gt;缓冲区溢出攻击&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt; 由于数组/缓冲区是局部变量，都放在堆栈段里，而程序的返回点也在堆栈段里，因此利用缓冲区溢出修改对应返回点，让程序跳进自己的恶意代码即可攻击。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt; 没有对栈中作为缓冲区的数组做越界检查。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt; 利用缓冲区溢出转到自设的程序 hacker 去执行 outputs 漏洞：当命令行中字符串超 25 个字符时，使用 &lt;code&gt;strcpy&lt;/code&gt; 函数就会使缓冲 buffer 造成写溢出并破坏返址&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &quot;stdio.h&quot;
#include &quot;string.h&quot;
void outputs(char *str) 
{ 
    char buffer[16]; 
    strcpy(buffer,str); 
    printf(&quot;%s \n&quot;, buffer);
}
void hacker(void)
{
    printf(&quot;being hacked\n&quot;);
}
int main(int argc, char *argv[])
{
    outputs(argv[1]);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;编译时，关闭堆栈保护和 PIE (Position Independent Executable), 编译命令为&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gcc -g -m32 -fno-stack-protector -no-pie -fno-pic -o test test.c&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于无法从控制台直接输入 ascii 码，因此编写一个程序调用 execve() 系统函数，启动 test 可执行程序，并注入攻击字符串。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// exec.c
#include &quot;stdio.h&quot;
char code[] = 
  &quot;0123456789ABCDEF0123456789AB&quot;
  &quot;\xc5\x91\x04\x08&quot;
  &quot;\x00&quot;;
int main(void) 
{
  char *arg[3];
  arg[0] = &quot;./test&quot;;
  arg[1] = code;
  arg[2] = NULL;
  execve(arg[0], arg, NULL);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022144318401.png&quot; alt=&quot;image-20251022144318401&quot;&gt;&lt;/p&gt;
&lt;p&gt;因为 &lt;code&gt;&quot;\xc5\x91\x04\x08&quot;&lt;/code&gt; 这里是对应的程序返回地址，这里将它覆盖成了 &lt;code&gt;0x080491c5&lt;/code&gt; ，成功执行 hacker 函数。&lt;/p&gt;</content:encoded></item><item><title>Chapter III-5</title><link>https://nkns.cc/notes/csapp/chapter_iii_5</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_iii_5</guid><description>CSAPP NOTE CHAP III-5</description><pubDate>Mon, 06 Oct 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 3.5 复杂数据类型的分配和访问&lt;/h1&gt;
&lt;h2&gt;地址和指针&lt;/h2&gt;
&lt;h3&gt;汇编语言中的地址和指针&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;地址是对主存中的每个字节单元指定一个编号&lt;/li&gt;
&lt;li&gt;一个数值作为地址使用时&lt;strong&gt;通过寄存器进行间接寻址&lt;/strong&gt;，找到目标操作数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;C 语言中的地址和指针&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;学过 C 语言都知道，没什么好说的。&lt;/li&gt;
&lt;li&gt;指针变量，其内容作为间接寻址使用。需要将变量值送到寄存器中进行&lt;strong&gt;间接寻址&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;数组的分配和访问&lt;/h2&gt;
&lt;p&gt;数组的分配是编译程序时进行的。&lt;/p&gt;
&lt;h3&gt;数组的定义和存储&lt;/h3&gt;
&lt;p&gt;数组的存储类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;静态存储 &lt;code&gt;static&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;外部存储 &lt;code&gt;extern&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;自动存储 &lt;code&gt;auto&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;auto&lt;/code&gt; 型分配在&lt;strong&gt;堆栈段&lt;/strong&gt;中，其他类型分配在静态数据区（&lt;strong&gt;数据段&lt;/strong&gt;中）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251018210323150.png&quot; alt=&quot;image-20251018210323150&quot;&gt;&lt;/p&gt;
&lt;h3&gt;数组的访问&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    int     a[10];
    char    b[50] = &quot;1234567&quot;;
    for (int i = 0; i &amp;#x3C; 10; i++)
        a[i] = i * 3;
    for (int j = 0; j &amp;#x3C; 15; j++)
        putchar(b[j]);    
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;汇编代码选摘：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# 初始化 a
	11e3:	c7 45 ca 00 00 00 00 	movl   $0x0,-0x36(%ebp)
    11ea:	c7 45 ce 00 00 00 00 	movl   $0x0,-0x32(%ebp)
    11f1:	c7 45 d2 00 00 00 00 	movl   $0x0,-0x2e(%ebp)
    11f8:	c7 45 d6 00 00 00 00 	movl   $0x0,-0x2a(%ebp)
    11ff:	c7 45 da 00 00 00 00 	movl   $0x0,-0x26(%ebp)
    1206:	c7 45 de 00 00 00 00 	movl   $0x0,-0x22(%ebp)
    120d:	c7 45 e2 00 00 00 00 	movl   $0x0,-0x1e(%ebp)
    1214:	c7 45 e6 00 00 00 00 	movl   $0x0,-0x1a(%ebp)
    121b:	c7 45 ea 00 00 00 00 	movl   $0x0,-0x16(%ebp)
    1222:	c7 45 ee 00 00 00 00 	movl   $0x0,-0x12(%ebp)
# 初始化 b （直接把字符串粗暴转化）
# a.c:5:     char    b[50] = &quot;1234567&quot;;
	movabsq	$15540725856023089, %rax	#, tmp97
	movl	$0, %edx	#,
	movq	%rax, -64(%rbp)	# tmp97, b
	movq	%rdx, -56(%rbp)	#, b
	movq	$0, -48(%rbp)	#, b
	movq	$0, -40(%rbp)	#, b
	movq	$0, -32(%rbp)	#, b
	movq	$0, -24(%rbp)	#, b
	movw	$0, -16(%rbp)	#, b
# for 循环内，对 a 和 b 的间接寻址：
a:
	# 取出 i
    1238:	8b 55 90             	mov    -0x70(%ebp),%edx
    # 对 i 乘以 3
    123b:	89 d0                	mov    %edx,%eax
    123d:	01 c0                	add    %eax,%eax
    123f:	01 c2                	add    %eax,%edx
    # 取出 i
    1241:	8b 45 90             	mov    -0x70(%ebp),%eax
    # 间接寻址
    1244:	89 54 85 98          	mov    %edx,-0x68(%ebp,%eax,4)
b:
    # 取出字符串首地址
    125b:	8d 55 c2             	lea    -0x3e(%ebp),%edx
    # 取出索引值
    125e:	8b 45 94             	mov    -0x6c(%ebp),%eax
    # 计算偏移后地址
    1261:	01 d0                	add    %edx,%eax
    # 将 char 转化为 (unsigned)int
    1263:	0f b6 00             	movzbl (%eax),%eax
    # 冗余代码，因为 putchar 要求传入值为 int，最终又要变为(unsigned)int。编译器在上一步调整完之后没意识到这一点
    1266:	0f be c0             	movsbl %al,%eax
    # 冗余代码，编译器没优化时希望把偏移量凑满 8 位，所以对栈顶调整了数值。
    1269:	83 ec 0c             	sub    $0xc,%esp
    # eax 入栈
    126c:	50                   	push   %eax
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一维数组数组元素的访问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采用&lt;strong&gt;变址寻址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;数组名作为&lt;strong&gt;位移量&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;数组下标放到&lt;strong&gt;变址寄存器&lt;/strong&gt;中&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数组和指针&lt;/h3&gt;
&lt;p&gt;在指针变量目标数据类型和数组类型相同的前提下，指针变量可以指向数组或数组中任意元素。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    int a[10];
    int *ptr1 = &amp;#x26;a[0];
    int *ptr2 = &amp;#x26;a[1];
    int *ptr0 = a; 
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应汇编片段：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;    11bc:	8d 44 24 14          	lea    0x14(%esp),%eax	; 取出 a[0] 地址给 ptr1
    11c0:	89 44 24 08          	mov    %eax,0x8(%esp)
    11c4:	8d 44 24 14          	lea    0x14(%esp),%eax	; 取出 a[0] 地址
    11c8:	83 c0 04             	add    $0x4,%eax	; 变址成 a[1] 地址
    11cb:	89 44 24 0c          	mov    %eax,0xc(%esp)
    11cf:	8d 44 24 14          	lea    0x14(%esp),%eax	; 取出 a 地址
    11d3:	89 44 24 10          	mov    %eax,0x10(%esp)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应汇编寻址取值方式：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022122710171.png&quot; alt=&quot;image-20251022122710171&quot;&gt;&lt;/p&gt;
&lt;h3&gt;指针数组和多维数组&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;指针数组&lt;/strong&gt; 数组元素是指针变量的数组，称为指针数组。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include&amp;#x3C;stdio.h&gt;
int main() {
    char * sa[5] = {&quot;hello&quot;, &quot;abc&quot;, &quot;123&quot;, &quot;yes&quot;, &quot;no&quot;};
    for(int i = 0; i &amp;#x3C; 5; i++) {
        printf(&quot;%s\n&quot;, sa[i]);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;// 这一段用来将五个字符串地址存入指针数组
	11d5:	8d 83 34 e0 ff ff    	lea    -0x1fcc(%ebx),%eax
    11db:	89 45 e0             	mov    %eax,-0x20(%ebp)
    11de:	8d 83 3a e0 ff ff    	lea    -0x1fc6(%ebx),%eax
    11e4:	89 45 e4             	mov    %eax,-0x1c(%ebp)
    11e7:	8d 83 3e e0 ff ff    	lea    -0x1fc2(%ebx),%eax
    11ed:	89 45 e8             	mov    %eax,-0x18(%ebp)
    11f0:	8d 83 42 e0 ff ff    	lea    -0x1fbe(%ebx),%eax
    11f6:	89 45 ec             	mov    %eax,-0x14(%ebp)
    11f9:	8d 83 46 e0 ff ff    	lea    -0x1fba(%ebx),%eax
    11ff:	89 45 f0             	mov    %eax,-0x10(%ebp)
// 接下来是 for 循环
    1202:	c7 45 dc 00 00 00 00 	movl   $0x0,-0x24(%ebp)	; 定义 i = 0
    1209:	eb 17                	jmp    1222 &amp;#x3C;main+0x75&gt;	; 条件跳转
    120b:	8b 45 dc             	mov    -0x24(%ebp),%eax	; eax = i
    120e:	8b 44 85 e0          	mov    -0x20(%ebp,%eax,4),%eax
    1212:	83 ec 0c             	sub    $0xc,%esp	; esp -= 12, 用来入栈eax
    1215:	50                   	push   %eax
    1216:	e8 45 fe ff ff       	call   1060 &amp;#x3C;puts@plt&gt;
    121b:	83 c4 10             	add    $0x10,%esp
    121e:	83 45 dc 01          	addl   $0x1,-0x24(%ebp)
    1222:	83 7d dc 04          	cmpl   $0x4,-0x24(%ebp)
    1226:	7e e3                	jle    120b &amp;#x3C;main+0x5e&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022124336101.png&quot; alt=&quot;image-20251022124336101&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022124400811.png&quot; alt=&quot;image-20251022124400811&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多维数组&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    char a[3][5];
    char *p00 = &amp;#x26;(a[0][0]);
    char *p01 = &amp;#x26;(a[0][1]);
    char *p02 = &amp;#x26;(a[0][2]);
    char *p10 = &amp;#x26;(a[1][0]);
    char *p11 = &amp;#x26;(a[1][1]);
    char *p20 = &amp;#x26;(a[2][0]);
    char *p24 = &amp;#x26;(a[2][4]);    
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;    11bc:	8d 44 24 1d          	lea    0x1d(%esp),%eax	; eax = a
    11c0:	89 04 24             	mov    %eax,(%esp)
    11c3:	8d 44 24 1d          	lea    0x1d(%esp),%eax
    11c7:	83 c0 01             	add    $0x1,%eax	; eax = &amp;#x26;a[1]
    11ca:	89 44 24 04          	mov    %eax,0x4(%esp)
    11ce:	8d 44 24 1d          	lea    0x1d(%esp),%eax
    11d2:	83 c0 02             	add    $0x2,%eax	; eax = &amp;#x26;a[2]
    11d5:	89 44 24 08          	mov    %eax,0x8(%esp)
    11d9:	8d 44 24 1d          	lea    0x1d(%esp),%eax
    11dd:	83 c0 05             	add    $0x5,%eax	; eax = &amp;#x26;a[5]
    11e0:	89 44 24 0c          	mov    %eax,0xc(%esp)
    11e4:	8d 44 24 1d          	lea    0x1d(%esp),%eax
    11e8:	83 c0 06             	add    $0x6,%eax	; eax = &amp;#x26;a[6]
    11eb:	89 44 24 10          	mov    %eax,0x10(%esp)
    11ef:	8d 44 24 1d          	lea    0x1d(%esp),%eax
    11f3:	83 c0 0a             	add    $0xa,%eax	; eax = &amp;#x26;a[10]
    11f6:	89 44 24 14          	mov    %eax,0x14(%esp)
    11fa:	8d 44 24 1d          	lea    0x1d(%esp),%eax
    11fe:	83 c0 0e             	add    $0xe,%eax	; eax = &amp;#x26;a[15]
    1201:	89 44 24 18          	mov    %eax,0x18(%esp)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际上汇编就是把多维数组拆开来看的&lt;/p&gt;
&lt;h2&gt;结构体数据的分配和访问&lt;/h2&gt;
&lt;h3&gt;结构体的定义和存储&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;C语言的结构体将不同类型的数据依次存放在一段连续的存储区中。指向结构的指针，就是其第一个字节的地址。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;结构体的存储类型主要包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;静态存储 &lt;code&gt;static&lt;/code&gt; 型&lt;/li&gt;
&lt;li&gt;外部存储 &lt;code&gt;extern&lt;/code&gt; 型&lt;/li&gt;
&lt;li&gt;自动存储 &lt;code&gt;auto&lt;/code&gt; 型&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;auto 型存储分配在堆栈段中，其他存储类型分配在静态数据区（数据段）中。&lt;/p&gt;
&lt;p&gt;结构体成员首址，采用 &lt;strong&gt;变址寻址&lt;/strong&gt; 确定，即
$$
address_{member} = address_{struct} + value_{shift}
$$&lt;/p&gt;
&lt;h3&gt;结构体数据作为子程序参数和返回值&lt;/h3&gt;
&lt;p&gt;结构体变量按值传递空间和时间开销太大，而且对应的实参无法被调用。因此结构体参数往往按地址传递。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt; 
typedef struct 
{
    int     id;
    char    name[10];
} stu_info;
void reg_stu_v(stu_info s)
{
    s.id = 101;
}
void reg_stu_a(stu_info* s)
{
    s-&gt;id = 201;
}
int main()
{
    stu_info stu = {-1, &quot;jack&quot;};
    reg_stu_v(stu);
    printf(&quot;%d\n&quot;, stu.id);
    reg_stu_a(&amp;#x26;stu);
    printf(&quot;%d\n&quot;, stu.id);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应汇编代码片段：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;000011ad &amp;#x3C;reg_stu_v&gt;:
    11ad:	55                   	push   %ebp
    11ae:	89 e5                	mov    %esp,%ebp
    11b0:	e8 da 00 00 00       	call   128f &amp;#x3C;__x86.get_pc_thunk.ax&gt;
    11b5:	05 1f 2e 00 00       	add    $0x2e1f,%eax
    11ba:	c7 45 08 65 00 00 00 	movl   $0x65,0x8(%ebp)	; 给栈内存里的临时变量赋值
    11c1:	90                   	nop
    11c2:	5d                   	pop    %ebp
    11c3:	c3                   	ret    

000011c4 &amp;#x3C;reg_stu_a&gt;:
    11c4:	55                   	push   %ebp
    11c5:	89 e5                	mov    %esp,%ebp
    11c7:	e8 c3 00 00 00       	call   128f &amp;#x3C;__x86.get_pc_thunk.ax&gt;
    11cc:	05 08 2e 00 00       	add    $0x2e08,%eax
    11d1:	8b 45 08             	mov    0x8(%ebp),%eax	; 和上一个函数最大的区别，这里调用了传入的指针
    11d4:	c7 00 c9 00 00 00    	movl   $0xc9,(%eax)	; 给传入指针对应的实际变量
    11da:	90                   	nop
    11db:	5d                   	pop    %ebp
    11dc:	c3                   	ret    
main:
	1205:	c7 45 e4 ff ff ff ff 	movl   $0xffffffff,-0x1c(%ebp)
    120c:	c7 45 e8 6a 61 63 6b 	movl   $0x6b63616a,-0x18(%ebp)
    1213:	c7 45 ec 00 00 00 00 	movl   $0x0,-0x14(%ebp)
    121a:	66 c7 45 f0 00 00    	movw   $0x0,-0x10(%ebp)
    1220:	ff 75 f0             	push   -0x10(%ebp)	; 将成员变量压栈
    1223:	ff 75 ec             	push   -0x14(%ebp)
    1226:	ff 75 e8             	push   -0x18(%ebp)
    1229:	ff 75 e4             	push   -0x1c(%ebp)
    122c:	e8 7c ff ff ff       	call   11ad &amp;#x3C;reg_stu_v&gt;
    1231:	83 c4 10             	add    $0x10,%esp
    1234:	8b 45 e4             	mov    -0x1c(%ebp),%eax
    1237:	83 ec 08             	sub    $0x8,%esp
    123a:	50                   	push   %eax
    123b:	8d 83 34 e0 ff ff    	lea    -0x1fcc(%ebx),%eax	; 把结构体存入 eax
    1241:	50                   	push   %eax
    1242:	e8 09 fe ff ff       	call   1050 &amp;#x3C;printf@plt&gt;
    1247:	83 c4 10             	add    $0x10,%esp
    124a:	83 ec 0c             	sub    $0xc,%esp
    124d:	8d 45 e4             	lea    -0x1c(%ebp),%eax
    1250:	50                   	push   %eax
    1251:	e8 6e ff ff ff       	call   11c4 &amp;#x3C;reg_stu_a&gt;
    1256:	83 c4 10             	add    $0x10,%esp
    1259:	8b 45 e4             	mov    -0x1c(%ebp),%eax
    125c:	83 ec 08             	sub    $0x8,%esp
    125f:	50                   	push   %eax
    1260:	8d 83 34 e0 ff ff    	lea    -0x1fcc(%ebx),%eax
    1266:	50                   	push   %eax
    1267:	e8 e4 fd ff ff       	call   1050 &amp;#x3C;printf@plt&gt;
    126c:	83 c4 10             	add    $0x10,%esp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结构体结构：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022132353042.png&quot; alt=&quot;image-20251022132353042&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来讨论结构体数据作为返回值：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt; 
typedef struct {
    int     id;
    char    name[10];
} stu_info;
stu_info get_stu() {
    stu_info stu = {75, &quot;Peter&quot;};
    return stu;
}
int main() {
    stu_info stu = get_stu();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:7:     stu_info stu = {75, &quot;Peter&quot;};
	movl	$75, -48(%rbp)	#, stu.id
	movabsq	$491328398672, %rax	#, tmp88
	movq	%rax, -44(%rbp)	# tmp88, stu.name
	movw	$0, -36(%rbp)	#, stu.name
# a.c:8:     return stu;
	movq	-48(%rbp), %rax	# stu, tmp83
	movq	-40(%rbp), %rdx	# stu,
	movq	%rax, -32(%rbp)	# tmp83, D.2355
	movq	%rdx, -24(%rbp)	#, D.2355
	movq	-32(%rbp), %rax	# D.2355, tmp84  这里 rax 和 rdx 是返回值，为了方便返回的时候赋值
	movq	-24(%rbp), %rdx	# D.2355,
# a.c:11:     stu_info stu = get_stu();
	movl	$0, %eax	#,
	call	get_stu	#
	movq	%rax, -32(%rbp)	# tmp84, stu
	movq	%rdx, -24(%rbp)	#, stu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这里返回值为什么用两个寄存器？&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;返回值的标准：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;对于“简单”的、不超过64位（8字节）的返回值（例如 &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;char*&lt;/code&gt;），调用约定规定只使用 &lt;code&gt;%rax&lt;/code&gt; 寄存器。&lt;/li&gt;
&lt;li&gt;但是，你的 &lt;code&gt;stu_info&lt;/code&gt; 结构体&lt;strong&gt;大于8字节&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;int id;&lt;/code&gt; // 4 字节&lt;/li&gt;
&lt;li&gt;&lt;code&gt;char name[10];&lt;/code&gt; // 10 字节&lt;/li&gt;
&lt;li&gt;总大小 = 14 字节。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为了对齐，编译器可能会把它当作16字节来处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;针对小型结构体的优化规则：&lt;/strong&gt; x86-64 ABI 规定：
&lt;ul&gt;
&lt;li&gt;如果一个结构体（或联合体）&lt;strong&gt;总大小不超过16字节（128位）&lt;/strong&gt;，它可以&lt;strong&gt;直接通过寄存器&lt;/strong&gt;返回。&lt;/li&gt;
&lt;li&gt;它会被拆分成两个8字节（64位）的“块”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第一个8字节的块放入 &lt;code&gt;%rax&lt;/code&gt;。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二个8字节的块放入 &lt;code&gt;%rdx&lt;/code&gt;。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;联合体数据的分配和访问&lt;/h2&gt;
&lt;p&gt;C 语言中的联合体，各成员共享存储空间，按最大长度成员所需空间大小为目标。&lt;strong&gt;成员具有相同的起始地址&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt; 
union uarea
{
    char    c_data;
    short   s_data;
    int     i_data;
    long    l_data;
};
int main()
{
    union uarea u;
    char* a1 = &amp;#x26;(u.c_data);
    short* a2 = &amp;#x26;(u.s_data);
    int* a3 = &amp;#x26;(u.i_data);
    long* a4 = &amp;#x26;(u.l_data);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应汇编：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;    11bc:	8d 44 24 08          	lea    0x8(%esp),%eax
    11c0:	89 44 24 0c          	mov    %eax,0xc(%esp)
    11c4:	8d 44 24 08          	lea    0x8(%esp),%eax
    11c8:	89 44 24 10          	mov    %eax,0x10(%esp)
    11cc:	8d 44 24 08          	lea    0x8(%esp),%eax
    11d0:	89 44 24 14          	mov    %eax,0x14(%esp)
    11d4:	8d 44 24 08          	lea    0x8(%esp),%eax
    11d8:	89 44 24 18          	mov    %eax,0x18(%esp)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见无论把 union 如何解读，取的地址都是 &lt;code&gt;0x8(%esp)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;联合体中的变量相互覆盖，起始地址相同。&lt;/p&gt;
&lt;h2&gt;数据的对齐&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;若 CPU 同时可以访问读写 64 位主存，则一次可以读写 8 个字节&lt;/li&gt;
&lt;li&gt;一条指令中操作数的地址按 8 对齐，访问该操作数就只需要一次主存访问，效率会更高。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Chapter III-4</title><link>https://nkns.cc/notes/csapp/chapter_iii_4</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_iii_4</guid><description>CSAPP NOTE CHAP III-4</description><pubDate>Sun, 05 Oct 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 3.4 C语言的机器级表示&lt;/h1&gt;
&lt;h2&gt;数值类型和运算的机器级表示&lt;/h2&gt;
&lt;h3&gt;C语言中的整数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;有符号数：&lt;code&gt;int&lt;/code&gt; &lt;code&gt;short&lt;/code&gt; &lt;code&gt;long&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无符号数：&lt;code&gt;unsigned int&lt;/code&gt; &lt;code&gt;unsigned short&lt;/code&gt; &lt;code&gt;unsigned long&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;C 语言标准规定了各类型最小取值范围，int 型至少为 16 位，取值范围为 -32768 - 32767，虽然现代编译器都是 2147483647 这个标准&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在一个数值的后面加一个 &lt;code&gt;u&lt;/code&gt; 或者 &lt;code&gt;U&lt;/code&gt; 来表示无符号数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运算中同时有无符号和有符号整数，按无符号整数计算&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;例题 1&lt;/strong&gt; 假定以下关系表达式在 32 位用补码表示的机器上执行，结果是什么？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251015105810037.png&quot; alt=&quot;image-20251015105810037&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例题 2&lt;/strong&gt; 回答以下三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在有些32位系统上，C表达式-2147483648 &amp;#x3C; 2147483647的执行结果为false。Why？&lt;/p&gt;
&lt;p&gt;在 ISO C90 标准下 ，-2147483648 为 unsigned int 型，因此
“-2147483648 &amp;#x3C; 2147483647” 按无符号数比较，10……0B 比 01……1B 大，结果为 false。&lt;/p&gt;
&lt;p&gt;在 ISO C99 标准下，2147483648 为 long long 型，因此 “-2147483648 &amp;#x3C; 2147483647” 按带符号整数比较，10……0B 比 01……1B 小，结果为 true。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若定义变量 “int i=-2147483648;”，则 “i &amp;#x3C; 2147483647” 的执行结果为 true。Why？&lt;/p&gt;
&lt;p&gt;i &amp;#x3C; 2147483647 按 int 型数比较，结果为 true。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果将表达式写成“-2147483647-1 &amp;#x3C; 2147483647”，则结果会怎样呢？Why？&lt;/p&gt;
&lt;p&gt;-2147483647-1 &amp;#x3C; 2147483647 按 int 型比较，结果为 true。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;例题 3&lt;/strong&gt; 判断以下函数结果&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
void main()
{
       short   x;
       unsigned short  y;
       x= -1;            // x=65535
       y= -1;            // y=65535
       printf(&quot;%d   %d\n&quot;, x,  y);
}

// Output -1 65535
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看相关的汇编代码可知：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;    11b9:	66 c7 45 f4 ff ff    	movw   $0xffff,-0xc(%ebp)	# x = -1
    11bf:	66 c7 45 f6 ff ff    	movw   $0xffff,-0xa(%ebp)	# y = -1
    11c5:	0f b7 4d f6          	movzwl -0xa(%ebp),%ecx	# printf 的 %d 解读 y
    11c9:	0f bf 55 f4          	movswl -0xc(%ebp),%edx	# printf 的 %d 解读 x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;例题 4&lt;/strong&gt; 判断以下函数结果&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main() {
    short x;
    unsigned short y;
    x = -1;
    y = -1;
    if(x &gt; 0) printf(&quot;%d positive\n&quot;, x);
    if(y &gt; 0) printf(&quot;%d positive\n&quot;, y);
}
// Output 65535 positive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看相关的汇编代码可知：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;    11ba:	66 c7 45 f4 ff ff    	movw   $0xffff,-0xc(%ebp)
    11c0:	66 c7 45 f6 ff ff    	movw   $0xffff,-0xa(%ebp)
    11c6:	66 83 7d f4 00       	cmpw   $0x0,-0xc(%ebp)
    11cb:	7e 17                	jle    11e4 &amp;#x3C;main+0x47&gt;
    # ...
    11e4:	66 83 7d f6 00       	cmpw   $0x0,-0xa(%ebp)
    11e9:	74 17                	je     1202 &amp;#x3C;main+0x65&gt;	# 无符号数只要不是 0 就大于 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C 语言中的浮点数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;类型：&lt;code&gt;double&lt;/code&gt; &lt;code&gt;float&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;long double&lt;/code&gt; 随编译器和处理器不同而变化，IA-32 中是 80 位&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int&lt;/code&gt; 的有效位数是 32 位，而 &lt;code&gt;float&lt;/code&gt; 的有效位数是 23 位，转换的时候会有精度丢失，转换为 &lt;code&gt;double&lt;/code&gt; 则不会丢失&lt;/li&gt;
&lt;li&gt;浮点数转化为整数的时候可能会溢出或舍入，小数部分会从 0 方向被截断。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main() {
    float heads;
    while(1) {
        printf(&quot;Please enter a number: &quot;);
        scanf(&quot;%d&quot;, &amp;#x26;heads);
        printf(&quot;%f\r\n&quot;, heads);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251017204720051.png&quot; alt=&quot;image-20251017204720051&quot;&gt;&lt;/p&gt;
&lt;p&gt;原因：&lt;code&gt;float&lt;/code&gt; 类型的最小精度使得它只能表示到这里的 &lt;code&gt;0.000004&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;数据的宽度和存储&lt;/h3&gt;
&lt;h4&gt;C语言中数值数据类型的宽度&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;不同机器同一类型宽度可能会不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251017204926454.png&quot; alt=&quot;image-20251017204926454&quot;&gt;&lt;/p&gt;
&lt;p&gt;注：Compaq Alpha 是 64 位机&lt;/p&gt;
&lt;h4&gt;C语言中类型转换顺序（比较重要）&lt;/h4&gt;
&lt;p&gt;顺序从大到小：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;unsigned long long&lt;/code&gt; &amp;#x3C;- &lt;code&gt;long long&lt;/code&gt; &amp;#x3C;- &lt;code&gt;unsigned&lt;/code&gt; &amp;#x3C;- &lt;code&gt;int&lt;/code&gt; &amp;#x3C;- &lt;code&gt;(unsigned)char, short&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    unsigned int a = 1;
    unsigned short b = 1;
    char c = -1;
    int d;
    d = (a &gt; c) ? 1 : 0;
    // c 的码点为 neg(0000 0001) = 1111 1111, 符号扩展后升级为 int
    // 但是 a 是 unsigned int，于是整个比较按照无符号数比较，就变成了 1 &gt; 4294967295
    printf(&quot;%d\n&quot;, d);
    d = (b &gt; c) ? 1 : 0;
    // 所有小于 int 的整数类型(包括 char)都会被提升为 int 型，b 和 c被转换为了 int 型下的 1 和 -1
    printf(&quot;%d\n&quot;, d);
}
// Result: 0 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C 语言中的运算&lt;/h3&gt;
&lt;h4&gt;按位逻辑运算和逻辑运算&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;按位逻辑运算：&lt;code&gt;&amp;#x26;&lt;/code&gt; &lt;code&gt;|&lt;/code&gt; &lt;code&gt;~&lt;/code&gt; &lt;code&gt;^&lt;/code&gt; 对位串实现 &lt;strong&gt;掩码&lt;/strong&gt; 操作或其他处理&lt;/li&gt;
&lt;li&gt;逻辑运算：&lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt; &lt;code&gt;||&lt;/code&gt; &lt;code&gt;!&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;移位运算&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;&gt;&gt;&lt;/code&gt; &lt;code&gt;&amp;#x3C;&amp;#x3C;&lt;/code&gt; 用于提取部分信息、扩大或缩小数值的 2/4/8 倍&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;y &gt;&gt; 8;	//提取 y 的高位数据
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;位扩展运算和位截断运算&lt;/h4&gt;
&lt;p&gt;无。类型转换时自动进行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 1&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;short si = -32768;				// si = -32768; 0x8000
unsigned short usi = si;		// usi = 32768; 0x8000
int i = si;						// i = -32768; 0xffff8000
unsigned ui = usi;				// i = 32768; 0x00008000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;例子 2&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int i = 32768;			// i = 0x00008000
short si = (short)i;	// si = 0x8000
int j = si;				// j = 0xffff8000
// Result: j != si
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;整数算术运算&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;C 语言整数实现与汇编语言的差异，此处略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无符号数加法溢出判断&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251017220754923.png&quot; alt=&quot;image-20251017220754923&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	发生溢出时，一定满足：&lt;strong&gt;result &amp;#x3C; x &amp;#x26;&amp;#x26; result &amp;#x3C; y&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int uadd_ok(unsigned x, unsigned y) {
    unsigned sum = x + y;
    return su &gt;= x;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;有符号数加法溢出判断&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251015140818835.png&quot; alt=&quot;image-20251015140818835&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int tadd_ok(int x, int y) {
    int sum = x+y;
    int neg_over = x &amp;#x3C; 0 &amp;#x26;&amp;#x26; y &amp;#x3C; 0 &amp;#x26;&amp;#x26; sum &gt;= 0;
    int pos_over = x &gt;= 0 &amp;#x26;&amp;#x26; y &gt;= 0 &amp;#x26;&amp;#x26; sum &amp;#x3C; 0;
        return !neg_over &amp;#x26;&amp;#x26; !pos_over;
} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;例题&lt;/strong&gt; 以下程序判断相减有没有问题？&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int tsub_ok(int x, int y) {
       return tadd_ok(x, -y);
}
// y = 0x80000000 时出错
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;整数的乘运算&lt;/p&gt;
&lt;p&gt;在 C 语言中，参加运算的数&lt;strong&gt;类型必须一致&lt;/strong&gt;，如果不一致&lt;strong&gt;会转换为一致再计算&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;例题&lt;/strong&gt; 以下程序存在什么漏洞&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int copy_array(int *array, int count) 
{ 
  	int i;  
 	/* 在堆区申请一块内存 */
  	int *myarray = (int *) malloc(count*sizeof(int)); 
   	if (myarray == NULL) 
       	return -1;
  		for (i = 0; i &amp;#x3C; count; i++) 
       	myarray[i] = array[i]; 
   	return count; 
}
// count * sizeof(int) 可能会溢出，造成数组越界访问
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;整数的除法运算&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Code segement I
int a = 0x80000000;
int b = a / -1; 
printf(&quot;%d\n&quot;, b);
// Result: -2147483648

// Code segement II
int a = 0x80000000;
int b = -1;
int c = a / b; 
printf(&quot;%d\n&quot;, c);
// Result: &quot;Floating point exception&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;浮点数的运算&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;浮点数的除 0 运算&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    int a = 1;
    int b = 0;
    printf(&quot;Division by zero: %d\n&quot;, a/b);
    return 0;
}
// Result: 整数除 0 发生异常！
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​	然而浮点数有表示无穷大的码点：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    double a = 1.0;
    double b = -1.0;
    double c = 0.0;
    printf(&quot;Division by zero: %f  %f\n&quot;, a/c, b/c);
    return 0;
}
// Result: 无穷大
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;浮点数的比较运算&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​	以下列表判断是否&lt;strong&gt;永真&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| 表达式                  | 结果 |
| ----------------------- | ---- |
| x == (int)(float) x     | 否   |
| x == (int)(double) x    | 是   |
| f == (float)(double) f  | 是   |
| d == (float) d          | 否   |
| f == -(-f);             | 是   |
| 2/3 == 2/3.0            | 否   |
| d &amp;#x3C; 0.0 ⇒((d\2)  &amp;#x3C; 0.0) | 是   |
| d &gt; f ⇒-f  &gt; -d         | 是   |
| d \ d &gt;= 0.0            | 是   |
| x\x&gt;=0                  | 否   |
| (d+f)-d == f            | 否   |&lt;/p&gt;
&lt;h2&gt;选择语句的机器级表示&lt;/h2&gt;
&lt;h3&gt;if-else 语句的机器级表示&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    int flag;
    int x = 3;
    int y = -1;
    if (x &gt; 0 || y &gt; 0)
    	flag = 1;
    else 
    	flag = 0;
	printf(&quot;flag = %d \n&quot;, flag);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:5:     int x = 3;
	movl	$3, -8(%rbp)	#, x
# a.c:6:     int y = -1;
	movl	$-1, -4(%rbp)	#, y
# a.c:7:     if (x &gt; 0 || y &gt; 0)
	cmpl	$0, -8(%rbp)	#, x
	jg	.L2	#,
# a.c:7:     if (x &gt; 0 || y &gt; 0)
	cmpl	$0, -4(%rbp)	#, y
	jle	.L3	#,
.L2:
# a.c:8:     	flag = 1;
	movl	$1, -12(%rbp)	#, flag
	jmp	.L4	#
.L3:
# a.c:10:     	flag = 0;
	movl	$0, -12(%rbp)	#, flag
.L4:
# a.c:11: 	printf(&quot;flag = %d \n&quot;, flag);
	movl	-12(%rbp), %eax	# flag, tmp84
	movl	%eax, %esi	# tmp84,
	leaq	.LC0(%rip), %rax	#, tmp85
	movq	%rax, %rdi	# tmp85,
	movl	$0, %eax	#,
	call	printf@PLT	#
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;switch 的机器级表示&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;例子 1&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
	int  x = 3;
	int  y = -1;
	int  z;
	int  i = 1;
	char c;
	c = getchar();
	switch (c) {
        case &apos;+&apos;:
            case &apos;a&apos;:
                z = x + y;
                break;	
        case &apos;-&apos;:
            case &apos;s&apos;:
                z = x - y;
                break;
        default:
            z = 0;
	}
	printf(&quot; %d %c %d = %d \n&quot;, x, c, y, z);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:4: 	int  x = 3;
	movl	$3, -12(%rbp)	#, x
# a.c:5: 	int  y = -1;
	movl	$-1, -8(%rbp)	#, y
# a.c:7: 	int  i = 1;
	movl	$1, -4(%rbp)	#, i
# a.c:9: 	c = getchar();
	call	getchar@PLT	#
# a.c:9: 	c = getchar();
	movb	%al, -17(%rbp)	# _1, c
# a.c:10: 	switch (c) {
	movsbl	-17(%rbp), %eax	# c, _2
	cmpl	$115, %eax	#, _2
	je	.L2	#,
	cmpl	$115, %eax	#, _2
	jg	.L3	#,
	cmpl	$97, %eax	#, _2
	je	.L4	#,
	cmpl	$97, %eax	#, _2
	jg	.L3	#,
	cmpl	$43, %eax	#, _2
	je	.L4	#,
	cmpl	$45, %eax	#, _2
	je	.L2	#,
	jmp	.L3	#
.L4:
# a.c:13:                 z = x + y;
	movl	-12(%rbp), %edx	# x, tmp91
	movl	-8(%rbp), %eax	# y, tmp92
	addl	%edx, %eax	# tmp91, tmp90
	movl	%eax, -16(%rbp)	# tmp90, z
# a.c:14:                 break;	
	jmp	.L5	#
.L2:
# a.c:17:                 z = x - y;
	movl	-12(%rbp), %eax	# x, tmp96
	subl	-8(%rbp), %eax	# y, tmp95
	movl	%eax, -16(%rbp)	# tmp95, z
# a.c:18:                 break;
	jmp	.L5	#
.L3:
# a.c:20:             z = 0;
	movl	$0, -16(%rbp)	#, z
.L5:
# a.c:22: 	printf(&quot; %d %c %d = %d \n&quot;, x, c, y, z);
	movsbl	-17(%rbp), %edx	# c, _3
	movl	-16(%rbp), %esi	# z, tmp97
	movl	-8(%rbp), %ecx	# y, tmp98
	movl	-12(%rbp), %eax	# x, tmp99
	movl	%esi, %r8d	# tmp97,
	movl	%eax, %esi	# tmp99,
	leaq	.LC0(%rip), %rax	#, tmp100
	movq	%rax, %rdi	# tmp100,
	movl	$0, %eax	#,
	call	printf@PLT	#
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;例子 2&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    int result = 0;
    int a = 12;
    int c = 0;
    int b = 10;
    switch (a)
    {
        case 15:
            c = b &amp;#x26;0x0f;
    	case 10:
			result = c + 50;
			break;
        case 12:
        case 17:
            result = b + 50;
            break;
        case 14:
            result = b;
            break;
        default:
            result = a;        
    }
    printf(&quot;%d\n&quot;, result);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下是对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:4:     int result = 0;
	movl	$0, -16(%rbp)	#, result
# a.c:5:     int a = 12;
	movl	$12, -8(%rbp)	#, a
# a.c:6:     int c = 0;
	movl	$0, -12(%rbp)	#, c
# a.c:7:     int b = 10;
	movl	$10, -4(%rbp)	#, b
# a.c:8:     switch (a)
	movl	-8(%rbp), %eax	# a, tmp85
	subl	$10, %eax	#, tmp84
	cmpl	$7, %eax	#, tmp84
	ja	.L2	#,
	movl	%eax, %eax	# tmp84, tmp86
	leaq	0(,%rax,4), %rdx	#, tmp87
	leaq	.L4(%rip), %rax	#, tmp88
	movl	(%rdx,%rax), %eax	#, tmp89
	cltq
	leaq	.L4(%rip), %rdx	#, tmp92
	addq	%rdx, %rax	# tmp92, tmp91
	notrack jmp	*%rax	# tmp91
	.section	.rodata
	.align 4
	.align 4
.L4:
	.long	.L7-.L4
	.long	.L2-.L4
	.long	.L3-.L4
	.long	.L2-.L4
	.long	.L6-.L4
	.long	.L5-.L4
	.long	.L2-.L4
	.long	.L3-.L4
	.text
.L5:
# a.c:11:             c = b &amp;#x26;0x0f;
	movl	-4(%rbp), %eax	# b, tmp96
	andl	$15, %eax	#, tmp95
	movl	%eax, -12(%rbp)	# tmp95, c
.L7:
# a.c:13: 			result = c + 50;
	movl	-12(%rbp), %eax	# c, tmp100
	addl	$50, %eax	#, tmp99
	movl	%eax, -16(%rbp)	# tmp99, result
# a.c:14: 			break;
	jmp	.L8	#
.L3:
# a.c:17:             result = b + 50;
	movl	-4(%rbp), %eax	# b, tmp104
	addl	$50, %eax	#, tmp103
	movl	%eax, -16(%rbp)	# tmp103, result
# a.c:18:             break;
	jmp	.L8	#
.L6:
# a.c:20:             result = b;
	movl	-4(%rbp), %eax	# b, tmp105
	movl	%eax, -16(%rbp)	# tmp105, result
# a.c:21:             break;
	jmp	.L8	#
.L2:
# a.c:23:             result = a;        
	movl	-8(%rbp), %eax	# a, tmp106
	movl	%eax, -16(%rbp)	# tmp106, result
.L8:
# a.c:25:     printf(&quot;%d\n&quot;, result);
	movl	-16(%rbp), %eax	# result, tmp107
	movl	%eax, %esi	# tmp107,
	leaq	.LC0(%rip), %rax	#, tmp108
	movq	%rax, %rdi	# tmp108,
	movl	$0, %eax	#,
	call	printf@PLT	#
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上switch语句运行时借助了 &lt;strong&gt;跳表&lt;/strong&gt; 进行实现。&lt;/p&gt;
&lt;h3&gt;条件表达式的机器级表示&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
	int a;
	int x;
	scanf(&quot;%d&quot;, &amp;#x26;a);
	x = a &gt; 0 ? a : a + 100;
	printf(&quot;x = %d \n&quot;, x);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:7: 	x = a &gt; 0 ? a : a + 100;
	movl	-16(%rbp), %eax	# a, a.1_1
# a.c:7: 	x = a &gt; 0 ? a : a + 100;
	testl	%eax, %eax	# a.1_1
	jg	.L2	#,
# a.c:7: 	x = a &gt; 0 ? a : a + 100;
	movl	-16(%rbp), %eax	# a, a.2_2
# a.c:7: 	x = a &gt; 0 ? a : a + 100;
	addl	$100, %eax	#, iftmp.0_3
	jmp	.L3	#
.L2:
# a.c:7: 	x = a &gt; 0 ? a : a + 100;
	movl	-16(%rbp), %eax	# a, iftmp.0_3
.L3:
# a.c:7: 	x = a &gt; 0 ? a : a + 100;
	movl	%eax, -12(%rbp)	# iftmp.0_3, x
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;循环结构的机器级表示&lt;/h2&gt;
&lt;h3&gt;do-while 循环&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
	int i = 1;
int result = 0;
	do
	{
		result += i;
		i++;
	} 
	while (i &amp;#x3C;= 10);
	printf(&quot;result = %d \n&quot;, result);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;.L2:
# a.c:8: 		result += i;
	movl	-8(%rbp), %eax	# i, tmp84
	addl	%eax, -4(%rbp)	# tmp84, result
# a.c:9: 		i++;
	addl	$1, -8(%rbp)	#, i
# a.c:11: 	while (i &amp;#x3C;= 10);
	cmpl	$10, -8(%rbp)	#, i
	jle	.L2	#,
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;while 循环&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
	int i = 1;
	int n = 100;
	int result = 0;
	while (i &amp;#x3C;= n)
	{
		result += i;
		i++;
	}
	printf(&quot;result = %d \n&quot;, result);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:7: 	while (i &amp;#x3C;= n)
	jmp	.L2	#
.L3:
# a.c:9: 		result += i;
	movl	-12(%rbp), %eax	# i, tmp84
	addl	%eax, -8(%rbp)	# tmp84, result
# a.c:10: 		i++;
	addl	$1, -12(%rbp)	#, i
.L2:
# a.c:7: 	while (i &amp;#x3C;= n)
	movl	-12(%rbp), %eax	# i, tmp85
	cmpl	-4(%rbp), %eax	# n, tmp85
	jle	.L3	#,
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;for 循环&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;例子 1&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
	int n = 100;
	int result = 0;
	for (int i = 1; i &amp;#x3C;= n; i++) 
		result += i;
	printf(&quot;result = %d \n&quot;, result);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:6: 	for (int i = 1; i &amp;#x3C;= n; i++) 
	movl	$1, -8(%rbp)	#, i
# a.c:6: 	for (int i = 1; i &amp;#x3C;= n; i++) 
	jmp	.L2	#
.L3:
# a.c:7: 		result += i;
	movl	-8(%rbp), %eax	# i, tmp84
	addl	%eax, -12(%rbp)	# tmp84, result
# a.c:6: 	for (int i = 1; i &amp;#x3C;= n; i++) 
	addl	$1, -8(%rbp)	#, i
.L2:
# a.c:6: 	for (int i = 1; i &amp;#x3C;= n; i++) 
	movl	-8(%rbp), %eax	# i, tmp85
	cmpl	-4(%rbp), %eax	# n, tmp85
	jle	.L3	#,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;例子 2&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    char    buf1[20];
    char    buf2[20];
    int     i;
    scanf(&quot;%s&quot;, buf1);
    for (i = 0; i &amp;#x3C; 20; i++)
        buf2[i] = buf1[i];
	printf(&quot;%s\n&quot;, buf2);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:8:     for (i = 0; i &amp;#x3C; 20; i++)
	movl	$0, -68(%rbp)	#, i
# a.c:8:     for (i = 0; i &amp;#x3C; 20; i++)
	jmp	.L2	#
.L3:
# a.c:9:         buf2[i] = buf1[i];
	movl	-68(%rbp), %eax	# i, tmp88
	cltq
	movzbl	-64(%rbp,%rax), %edx	# buf1[i_2], _1
# a.c:9:         buf2[i] = buf1[i];
	movl	-68(%rbp), %eax	# i, tmp90
	cltq
	movb	%dl, -32(%rbp,%rax)	# _1, buf2[i_2]
# a.c:8:     for (i = 0; i &amp;#x3C; 20; i++)
	addl	$1, -68(%rbp)	#, i
.L2:
# a.c:8:     for (i = 0; i &amp;#x3C; 20; i++)
	cmpl	$19, -68(%rbp)	#, i
	jle	.L3	#,
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;事实上，汇编语言是比较好实现 &lt;code&gt;do-while&lt;/code&gt; 结构的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251016204728366.png&quot; alt=&quot;image-20251016204728366&quot;&gt;&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;while&lt;/code&gt; 其实是相对不那么好实现的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251016204801173.png&quot; alt=&quot;image-20251016204801173&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;for&lt;/code&gt; 循环的结构看起来就是在判断的前面、 &lt;code&gt;while&lt;/code&gt; 的函数段后面加了两行（例如 &lt;code&gt;i++&lt;/code&gt; ）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251016204819339.png&quot; alt=&quot;image-20251016204819339&quot;&gt;&lt;/p&gt;
&lt;h2&gt;编译优化&lt;/h2&gt;
&lt;p&gt;在 gcc 编译程序的时候，可以选择 &lt;code&gt;-O0&lt;/code&gt; &lt;code&gt;-O1&lt;/code&gt; &lt;code&gt;-O2&lt;/code&gt; &lt;code&gt;-O3&lt;/code&gt; 四个等级，对应等级是优化水平。O0 即完全不优化。&lt;/p&gt;
&lt;h2&gt;循环结构与递归的比较&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;递归求和：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int iter_sum(int n)
{
	int result;	
	if  (n &amp;#x3C;= 0)  
	    result = 0;   
	else
	    result = n + iter_sum(n - 1); 
	return  result;
}
int main()
{
    int sum = iter_sum(10);
    printf(&quot;%d\n&quot;, sum);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编程序：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;iter_sum:
# a.c:5: 	if  (n &amp;#x3C;= 0)  
	cmpl	$0, -20(%rbp)	#, n
	jg	.L2	#,
# a.c:6: 	    result = 0;   
	movl	$0, -4(%rbp)	#, result
	jmp	.L3	#
.L2:
# a.c:8: 	    result = n + iter_sum(n - 1); 
	movl	-20(%rbp), %eax	# n, tmp86
	subl	$1, %eax	#, _1
	movl	%eax, %edi	# _1,
	call	iter_sum	#
# a.c:8: 	    result = n + iter_sum(n - 1); 
	movl	-20(%rbp), %edx	# n, tmp90
	addl	%edx, %eax	# tmp90, tmp89
	movl	%eax, -4(%rbp)	# tmp89, result
.L3:
# a.c:9: 	return  result;
	movl	-4(%rbp), %eax	# result, _10
main:
# a.c:13:     int sum = iter_sum(10);
	movl	$10, %edi	#,
	call	iter_sum	#
	movl	%eax, -4(%rbp)	# tmp84, sum
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;非递归求和：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int main()
{
    int n = 10;
    int i;
    int result = 0;
    for (i=1; i &amp;#x3C;= n; i++)  
	    result += i; 
    printf(&quot;%d\n&quot;, result);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# a.c:7:     for (i=1; i &amp;#x3C;= n; i++)  
	movl	$1, -12(%rbp)	#, i
# a.c:7:     for (i=1; i &amp;#x3C;= n; i++)  
	jmp	.L2	#
.L3:
# a.c:8: 	    result += i; 
	movl	-12(%rbp), %eax	# i, tmp84
	addl	%eax, -8(%rbp)	# tmp84, result
# a.c:7:     for (i=1; i &amp;#x3C;= n; i++)  
	addl	$1, -12(%rbp)	#, i
.L2:
# a.c:7:     for (i=1; i &amp;#x3C;= n; i++)  
	movl	-12(%rbp), %eax	# i, tmp85
	cmpl	-4(%rbp), %eax	# n, tmp85
	jle	.L3	#,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见非递归程序没有用到 &lt;code&gt;call&lt;/code&gt; 命令，减少了调用、返回、保护现场三个步骤，并且没有使用栈帧。因此，为了提高程序的性能，应该尽量用非递归方式实现功能。&lt;/p&gt;
&lt;h2&gt;过程调用的机器级表示&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;过程调用的运行机理：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
int fadd(int x, int y)
{
	int u,v,w;
	u = x + 10;
	v = y + 25;
	w = u + v;
	return w;
}
int main()
{
   int  a = 100;    // 0x 64
   int  b = 200;    // 0x C8
   int  sum = 0;
   sum = fadd(a, b);
   printf(&quot;%d\n&quot;, sum);
   return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的一些汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;Dump of assembler code for function fadd:
3       {
   0x5655619d &amp;#x3C;+0&gt;:     push   %ebp				# 保护现场，这里只用保护ebp
   0x5655619e &amp;#x3C;+1&gt;:     mov    %esp,%ebp		# 使用ebp，保存进入子程序时，保护现场后堆栈段的基址。EBP = 0xffffd160
   0x565561a0 &amp;#x3C;+3&gt;:     sub    $0x10,%esp		# 为局部变量分配空间。三个局部变量，总长度为12个字节。为了对齐，分配了16个字节的空间。
   0x565561a3 &amp;#x3C;+6&gt;:     call   0x56556236 &amp;#x3C;__x86.get_pc_thunk.ax&gt;
   0x565561a8 &amp;#x3C;+11&gt;:    add    $0x2e30,%eax

4               int u,v,w;
5               u = x + 10;
   0x565561ad &amp;#x3C;+16&gt;:    mov    0x8(%ebp),%eax	# 源操作数使用了参数x
   0x565561b0 &amp;#x3C;+19&gt;:    add    $0xa,%eax
   0x565561b3 &amp;#x3C;+22&gt;:    mov    %eax,-0xc(%ebp)	# 目的操作数使用了局部变量x

6               v = y + 25;
   0x565561b6 &amp;#x3C;+25&gt;:    mov    0xc(%ebp),%eax
   0x565561b9 &amp;#x3C;+28&gt;:    add    $0x19,%eax
   0x565561bc &amp;#x3C;+31&gt;:    mov    %eax,-0x8(%ebp)

7               w = u + v;
   0x565561bf &amp;#x3C;+34&gt;:    mov    -0xc(%ebp),%edx
   0x565561c2 &amp;#x3C;+37&gt;:    mov    -0x8(%ebp),%eax
   0x565561c5 &amp;#x3C;+40&gt;:    add    %edx,%eax
   0x565561c7 &amp;#x3C;+42&gt;:    mov    %eax,-0x4(%ebp)

8               return w;
   0x565561ca &amp;#x3C;+45&gt;:    mov    -0x4(%ebp),%eax	# 将返回值送入EAX。通过EAX返回子程序的返回值。

9       }
   0x565561cd &amp;#x3C;+48&gt;:    leave					# leave指令，等价于
                                                # mov  %ebp, %esp
                                                # pop  %ebp
                                                # 即还原ESP、EBP

   0x565561ce &amp;#x3C;+49&gt;:    ret  					# ret，出栈到EIP
Dump of assembler code for function main:
11      {
   0x565561cf &amp;#x3C;+0&gt;:     lea    0x4(%esp),%ecx
   0x565561d3 &amp;#x3C;+4&gt;:     and    $0xfffffff0,%esp
   0x565561d6 &amp;#x3C;+7&gt;:     push   -0x4(%ecx)
   0x565561d9 &amp;#x3C;+10&gt;:    push   %ebp
   0x565561da &amp;#x3C;+11&gt;:    mov    %esp,%ebp
   0x565561dc &amp;#x3C;+13&gt;:    push   %ebx
   0x565561dd &amp;#x3C;+14&gt;:    push   %ecx
   0x565561de &amp;#x3C;+15&gt;:    sub    $0x10,%esp
   0x565561e1 &amp;#x3C;+18&gt;:    call   0x565560a0 &amp;#x3C;__x86.get_pc_thunk.bx&gt;
   0x565561e6 &amp;#x3C;+23&gt;:    add    $0x2df2,%ebx

12         int  a = 100;    // 0x 64
   0x565561ec &amp;#x3C;+29&gt;:    movl   $0x64,-0x14(%ebp)

13         int  b = 200;    // 0x C8
   0x565561f3 &amp;#x3C;+36&gt;:    movl   $0xc8,-0x10(%ebp)

14         int  sum = 0;
   0x565561fa &amp;#x3C;+43&gt;:    movl   $0x0,-0xc(%ebp)

15         sum = fadd(a, b);
   0x56556201 &amp;#x3C;+50&gt;:    push   -0x10(%ebp)
   0x56556204 &amp;#x3C;+53&gt;:    push   -0x14(%ebp)
   0x56556207 &amp;#x3C;+56&gt;:    call   0x5655619d &amp;#x3C;fadd&gt;
   0x5655620c &amp;#x3C;+61&gt;:    add    $0x8,%esp		# 出栈参数x、y
   0x5655620f &amp;#x3C;+64&gt;:    mov    %eax,-0xc(%ebp)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 main 第一行处执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) p/x $esp
$2 = 0xffffce20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) x/16 0xffffce20 - 48
0xffffcdf0:     0x00000000      0x00000000      0x01000000      0x0000000b
0xffffce00:     0xf7fc4560      0x00000000      0xf7d9b4be      0xf7fa9054
0xffffce10:     0xf7fbe4a0      0xf7fd6f20      0xf7d9b4be      0x565561e6 &amp;#x3C;- main入口
0xffffce20:     0xffffce60      0xf7fbe66c      0xf7fbeb20      0x00000001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着执行到 fadd：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fadd (x=100, y=200) at a.c:3
3       {
(gdb) p/x $eip
$2 = 0x5655619d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) p/x $esp
$3 = 0xffffce14
(gdb) x/16 0xffffce20 - 48
0xffffcdf0:     0x00000000      0x00000000      0x01000000      0x0000000b
0xffffce00:     0xf7fc4560      0x00000000      0xf7d9b4be      0xf7fa9054
0xffffce10:     0xf7fbe4a0      0x5655620c      0x00000064      0x000000c8
                                 ^ main函数返回点   ^ a             ^ b
0xffffce20:     0xffffce60      0x00000064      0x000000c8      0x00000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由此可见，栈帧和局部变量在栈内被很好地保护了。&lt;/p&gt;
&lt;p&gt;调用时栈和栈帧的变化如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251018005346141.png&quot; alt=&quot;image-20251018005346141&quot;&gt;&lt;/p&gt;
&lt;h3&gt;局部变量&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;子程序开始执行时，在堆栈中分配局部变量空间。&lt;/li&gt;
&lt;li&gt;局部变量在子程序栈帧中&lt;/li&gt;
&lt;li&gt;子程序退出前，会释放局部变量的空间。因此，局部变量的作用域和生存空间只在子程序中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;详情可以参考上面这一例汇编内的注释。&lt;/p&gt;
&lt;h3&gt;按值传递参数和按地址传递参数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;基本的形参和实参概念&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;按值传递：传递变量值；按地址传递：传递变量地址（指针）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;按地址传递的时候记得间接寻址，直接修改地址参数是啥用都没有的。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;例子 按值传递参数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
void swap(int x, int y)
{
    int t = x;
    x = y;
    y = t;    
}
int main()
{
    int a = 15;
    int b = 22;
    printf(&quot;a=%d b=%d\n&quot;, a, b);
    swap(a, b);
    printf(&quot;a=%d b=%d\n&quot;, a, b);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;汇编片段：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;main:
# a.c:13:     swap(a, b);
	movl	-4(%rbp), %edx	# b, tmp87
	movl	-8(%rbp), %eax	# a, tmp88
	movl	%edx, %esi	# tmp87,
	movl	%eax, %edi	# tmp88,
	call	swap	#
swap:
	movl	%edi, -20(%rbp)	# x, x
	movl	%esi, -24(%rbp)	# y, y
# a.c:4:     int t = x;
	movl	-20(%rbp), %eax	# x, tmp82
	movl	%eax, -4(%rbp)	# tmp82, t
# a.c:5:     x = y;
	movl	-24(%rbp), %eax	# y, tmp83
	movl	%eax, -20(%rbp)	# tmp83, x
# a.c:6:     y = t;    
	movl	-4(%rbp), %eax	# t, tmp84
	movl	%eax, -24(%rbp)	# tmp84, y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;例子 按地址传递参数&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
void swap(int* x, int* y)
{
    int t = *x;
    *x = *y;
    *y = t;    
}
int main()
{
    int a = 15;
    int b = 22;
    printf(&quot;a=%d b=%d\n&quot;, a, b);
    swap(&amp;#x26;a, &amp;#x26;b);
    printf(&quot;a=%d b=%d\n&quot;, a, b);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;汇编片段：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;main:
# a.c:13:     swap(&amp;#x26;a, &amp;#x26;b);
	leaq	-12(%rbp), %rdx	#, tmp89
	leaq	-16(%rbp), %rax	#, tmp90
	movq	%rdx, %rsi	# tmp89,
	movq	%rax, %rdi	# tmp90,
	call	swap	#
swap:
	movq	%rdi, -24(%rbp)	# x, x
	movq	%rsi, -32(%rbp)	# y, y
# a.c:4:     int t = *x;
	movq	-24(%rbp), %rax	# x, tmp83
	movl	(%rax), %eax	# *x_3(D), tmp84
	movl	%eax, -4(%rbp)	# tmp84, t
# a.c:5:     *x = *y;
	movq	-32(%rbp), %rax	# y, tmp85
	movl	(%rax), %edx	# *y_5(D), _1
# a.c:5:     *x = *y;
	movq	-24(%rbp), %rax	# x, tmp86
	movl	%edx, (%rax)	# _1, *x_3(D)
# a.c:6:     *y = t;    
	movq	-32(%rbp), %rax	# y, tmp87
	movl	-4(%rbp), %edx	# t, tmp88
	movl	%edx, (%rax)	# tmp88, *y_5(D)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述片段先将地址取出，再利用寄存器间接寻址最终修改了内存中的原数据。&lt;/p&gt;
&lt;h3&gt;递归过程调用&lt;/h3&gt;
&lt;p&gt;前文已经有一个例子了，这里再举一个。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;  
int f(int x) 
{
    if (x==1)  
        return 1;     
    return  x*f(x-1); 
}  
int main() 
{
    printf(&quot;%d\n&quot;,f(5));
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;f:
	movl	%edi, -4(%rbp)	# x, x
# a.c:4:     if (x==1)  
	cmpl	$1, -4(%rbp)	#, x
	jne	.L2	#,
# a.c:5:         return 1;     
	movl	$1, %eax	#, _3
	jmp	.L3	#
.L2:
# a.c:6:     return  x*f(x-1); 
	movl	-4(%rbp), %eax	# x, tmp86
	subl	$1, %eax	#, _1
	movl	%eax, %edi	# _1,
	call	f	#
# a.c:6:     return  x*f(x-1); 
	imull	-4(%rbp), %eax	# x, _3
.L3:
# a.c:7: }  
	leave	
	.cfi_def_cfa 7, 8
	ret	
main:
# a.c:10:     printf(&quot;%d\n&quot;,f(5));
	movl	$5, %edi	#,
	call	f	#
	movl	%eax, %esi	# _1,
	leaq	.LC0(%rip), %rax	#, tmp85
	movq	%rax, %rdi	# tmp85,
	movl	$0, %eax	#,
	call	printf@PLT	#
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;递归调用示意图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251018011551561.png&quot; alt=&quot;image-20251018011551561&quot;&gt;&lt;/p&gt;
&lt;p&gt;其实相关原理 C 语言已经都学过了，这里看懂汇编即可。&lt;/p&gt;</content:encoded></item><item><title>Chapter III-3</title><link>https://nkns.cc/notes/csapp/chapter_iii_3</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_iii_3</guid><description>CSAPP NOTE CHAP III-3</description><pubDate>Sat, 04 Oct 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 3.3 IA-32 常用指令类型及其操作&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;AT&amp;#x26;T&lt;/strong&gt; &lt;strong&gt;格式汇编指令的简单形式和后缀形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| 简单形式                           | 后缀形式               | 说明                                                         |
| ---------------------------------- | ---------------------- | ------------------------------------------------------------ |
| mov(lea、add、inc、sub、dec等类似) | movb  movw  movl       | 有寄存器时，使用简单形式。无寄存器时，使用后缀形式。（若无寄存器时使用简单形式，会添加默认后缀l） |
| movsx                              | movsbw  movsbl  movswl | 使用后缀形式                                                 |
| movzx                              | movzbw  movzbl  movzwl | 使用后缀形式                                                 |&lt;/p&gt;
&lt;h2&gt;传送指令&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;功能：将数据、地址、立即数送入寄存器或存贮器&lt;/li&gt;
&lt;li&gt;这类指令有：MOV、XCHG、LEA 等&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;传送指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格式：MOV OPS, OPD&lt;/li&gt;
&lt;li&gt;功能：(OPS) -&gt; OPD&lt;/li&gt;
&lt;li&gt;注意：&lt;strong&gt;不能是单元←→单元&lt;/strong&gt;，即&lt;strong&gt;不能交换两个内存单元的地址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;指令的具体形式可以为：movb、movw、movl 等&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov	%eax, %ebx	; Right
mov $1, %eax	; Right
mov $1, 0xaaaabbbb	; Right
mov 0xaaaabbbb, 0xccccdddd	; Wrong!
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据交换指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格式：XCHG OPS, OPD&lt;/li&gt;
&lt;li&gt;功能：(OPD) -&gt; OPS, (OPS) -&gt; OPD&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;xchg %eax, %ebx
# 若执行前：EAX = 0x5678， EBX = 0x1234
# 执行后：EAX = 0x1234，EBX = 0x5678H
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;p&gt;寄存器←→寄存器，寄存器←→存贮器。有一个必须为寄存器。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;扩展传送指令&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;符号拓展&lt;/strong&gt; 传送指令&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;格式：MOVSX OPS, OPD&lt;/li&gt;
&lt;li&gt;功能：江源操作数的符号向前扩展成与目的操作数相同的数据类型后，再送入目的地址对应的单元中。&lt;/li&gt;
&lt;li&gt;说明：
&lt;ul&gt;
&lt;li&gt;可以指明操作数类型，具体指令可以是 &lt;code&gt;movsbw&lt;/code&gt; &lt;code&gt;movsbl&lt;/code&gt; &lt;code&gt;movswl&lt;/code&gt; 等&lt;/li&gt;
&lt;li&gt;其实一共就四种类型 &lt;code&gt;b&lt;/code&gt; &lt;code&gt;w&lt;/code&gt; &lt;code&gt;l&lt;/code&gt; &lt;code&gt;q&lt;/code&gt;，大小从小到大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OPS 不能为立即数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;OPD 必须是寄存器&lt;/li&gt;
&lt;li&gt;源操作数的位数必须小于目的操作数的位数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;无符号拓展&lt;/strong&gt; 传送指令&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;格式： MOVZX OPS, OPD&lt;/li&gt;
&lt;li&gt;功能： 将源操作数的高位补 0 ，扩成与目的操作数相同的数据类型后，再送入目的操作数对应的单元中。&lt;/li&gt;
&lt;li&gt;说明注意点同上&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov	$0xe3, %bl
movsx	%bl, %ebx
# (EBX) = ?			0xFFFFFFE3
mov	$0xe3, %bl
movzx	%bl, %ebx
# (EBX) = ?			0x000000E3
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;byte0:	.byte 0xa8
mov	byte0, %bl
movsx	%bl, %ecx	# ERROR
movsx	byte0, %bl	# ERROR
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;地址传送指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;格式：LEA OPS, OPD&lt;/li&gt;
&lt;li&gt;功能：OPS 的偏移地址 -&gt; OPD&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;leal	buf, %eax	# 1
movl	$buf, %eax	# 2
# 以上两者等价
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;注意：&lt;strong&gt;OPD&lt;/strong&gt; 必须为寄存器&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;算术运算指令&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;算术指令：加、减、乘、除及符号扩展指令&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;共同特点：对&lt;code&gt;SF&lt;/code&gt; &lt;code&gt;OF&lt;/code&gt; &lt;code&gt;ZF&lt;/code&gt; &lt;code&gt;CF&lt;/code&gt; &lt;code&gt;AF&lt;/code&gt; 有影响&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运算原则：有符号数在机内均用补码表示，不单独处理符号&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;加法指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ADD OPS, OPD&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能：(OPS) + (OPD) -&gt; OPD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;INC OPD&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;功能：(OPD) + 1 -&gt; OPD&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意：OPD 不能是立即数&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;ADD	%eax, %ebx	# (%ebx) = (%eax) + (%ebx)
INC	%ecx	# (%ecx)++
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;减法指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SUB OPS, OPD&lt;/p&gt;
&lt;p&gt;(OPD) - (OPS) -&gt; OPD&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DEC OPD&lt;/p&gt;
&lt;p&gt;(OPD) - 1 -&gt; OPD&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NEG OPD&lt;/p&gt;
&lt;p&gt;(OPD)_反 + 1 -&gt; OPD	// 求补&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CMP OPD, OPS&lt;/p&gt;
&lt;p&gt;(OPD) - (OPS)	// &lt;strong&gt;不回送结果，只影响标志&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意：如果是进行加、减运算，对&lt;strong&gt;有符号数&lt;/strong&gt;，当 &lt;strong&gt;OF=0&lt;/strong&gt; 时，计算结果&lt;strong&gt;正确&lt;/strong&gt; ；对无符号数，当 &lt;strong&gt;CF=0&lt;/strong&gt; 时，结果&lt;strong&gt;正确&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# 例：求 (AX) 的绝对值。
	cmp	$0, %ax
	jge	exit	; AX &gt;= 0 时，跳到 exit
	neg %ax
	...
	exit:
	...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;乘法指令&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;单操作数乘法指令
&lt;ul&gt;
&lt;li&gt;有符号乘法：IMUL OPS;&lt;/li&gt;
&lt;li&gt;无符号乘法：MUL OPS；&lt;/li&gt;
&lt;li&gt;被乘数隐含在 EAX/AX/AL 中。是字乘法还是字节乘法，由 OPS 决定。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251113000916733.png&quot; alt=&quot;image-20251113000916733&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov	$0x50, %ax
mov $-0x10, %bx
imul %bx
# Result: DX = FFFFH, AX = FB00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目的操作数必须是AX（字乘法是AX，字节是AL）。&lt;/li&gt;
&lt;li&gt;OPS不能是立即数。&lt;/li&gt;
&lt;li&gt;对CF和OF有影响。&lt;/li&gt;
&lt;li&gt;若MUL运算后，（AH）或（DX）为0，则CF、OF均为0；否则CF、OF均为1。&lt;/li&gt;
&lt;li&gt;对IMUL来说，若乘积的高一半是底一半的符号扩展，则CF、OF=0；否则CF、OF=1。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;双操作数乘法指令&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;格式：IMUL OPS, OPD&lt;/li&gt;
&lt;li&gt;功能：(OPS) * (OPD) -&gt; OPD&lt;/li&gt;
&lt;li&gt;注意：
&lt;ul&gt;
&lt;li&gt;目的操作数必须是16/32位寄存器&lt;/li&gt;
&lt;li&gt;源操作数可以是立即数。&lt;/li&gt;
&lt;li&gt;目的操作数和源操作数必须类型一致&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;三操作数乘法指令&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;格式：IMUL n, OPS, OPD&lt;/li&gt;
&lt;li&gt;功能：(OPS) * n -&gt; OPD&lt;/li&gt;
&lt;li&gt;注意：
&lt;ul&gt;
&lt;li&gt;目的操作数必须是16/32位寄存器&lt;/li&gt;
&lt;li&gt;源操作数不能是立即数。&lt;/li&gt;
&lt;li&gt;目的操作数和源操作数必须类型一致&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;符号扩展指令&lt;/h3&gt;
&lt;p&gt;用于在除法前，将单精度数扩展到双精度数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(1) 字节转换成字&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;格式：CBW&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;功能：将AL中的符号扩展到AH中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov $-7, %al
cbw
# CBW执行前：(AL)= F9H
# 执行后：(AX)= FFF9H
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(2) 字转为双字&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;格式：CWD&lt;/li&gt;
&lt;li&gt;功能：将AX的符号扩展到DX中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(3) 字转为双字&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;格式：CWDE&lt;/li&gt;
&lt;li&gt;功能：将AX的符号扩展到EAX中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(4) 32位转为64位&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;格式：CDQ&lt;/li&gt;
&lt;li&gt;功能：将EAX的符号扩展到EDX中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;除法指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有符号除法：IDIV OPS&lt;/li&gt;
&lt;li&gt;无符号除法：DIV OPS&lt;/li&gt;
&lt;li&gt;功能：
&lt;ul&gt;
&lt;li&gt;字节除法：(AX) / (OPS) → AL (商)、AH (余数)&lt;/li&gt;
&lt;li&gt;字除法：(DX、AX) / (OPS) → AX (商)、DX (余)&lt;/li&gt;
&lt;li&gt;双字除法：(EDX、EAX) / (OPS) → EAX (商)、EDX (余)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;注意：
&lt;ul&gt;
&lt;li&gt;如果是无符号除法，被除数符号的扩展不能用CBW、CWD。只能 &lt;code&gt;MOV DX, 0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;OPS不能为立即数。&lt;/li&gt;
&lt;li&gt;除数为0时，产生溢出中断。&lt;/li&gt;
&lt;li&gt;有符号除法，余数与被除数符号相同。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# 示例1
mov  $-0x4001, %ax
cwd
mov  $4, %cx
idiv %cx
# 结果：(DX)= FFFFH(余数)，(AX)= F000H(商)。

# 示例2
mov  $-0x4001,  %ax
cwd
mov  $-0x4, %cx
idiv  %cx
# 结果：(DX)=FFFFH(余数)，(AX)=1000H(商)。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;按位运算指令&lt;/h2&gt;
&lt;h3&gt;1. 逻辑运算指令&lt;/h3&gt;
&lt;p&gt;包括：求反、逻辑乘、测试、逻辑加、按位加等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;(1) 求反&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;格式：NOT OPD;&lt;/li&gt;
&lt;li&gt;功能：将OPD的内容逐位取反→OPD。&lt;/li&gt;
&lt;li&gt;该指令不影响标志位。&lt;/li&gt;
&lt;li&gt;注意：与求补 (NEG OPD) 的区别。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(2) 逻辑乘&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;格式：AND OPD, OPS;&lt;/li&gt;
&lt;li&gt;功能：OPD ∧ OPS → OPD&lt;/li&gt;
&lt;li&gt;用途：屏蔽某些位 (e.g., &lt;code&gt;and $0xff, %dx&lt;/code&gt; 屏蔽高8位) 或提取值 (e.g., &lt;code&gt;and 0x0f, %al&lt;/code&gt; 将 &apos;5&apos; (35H) 变为 5)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(3) 测试指令&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;a）格式：TEST OPD, OPS
&lt;ul&gt;
&lt;li&gt;功能：(OPD) ∧ (OPS)，结果不回送，影响标志SF、ZF、PF。&lt;/li&gt;
&lt;li&gt;用途：检测与OPS中为1的位相对应的位是否为1。&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;test $0x80, %al&lt;/code&gt; 测试al最高位是否为0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;b）格式：BT OPD, OPS
&lt;ul&gt;
&lt;li&gt;功能：将OPD的指定位送到CF&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(4) 逻辑加&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;格式：OR OPD, OPS&lt;/li&gt;
&lt;li&gt;功能：(OPD) ∨ (OPS) → OPD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(5) 按位加 (异或)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;格式：XOR OPD, OPS&lt;/li&gt;
&lt;li&gt;功能：(OPD) ⨁ (OPS) → OPD&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;xor %ax, %ax&lt;/code&gt; 等价于 &lt;code&gt;MOV AX, 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 移位指令&lt;/h3&gt;
&lt;p&gt;包括：算术、逻辑、循环移位。 格式：&lt;code&gt;操作符 n, OPD&lt;/code&gt; 或 &lt;code&gt;操作符 %cl, OPD&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;a) 算术左移或逻辑左移&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SAL n, OPD, 或 SHL n, OPD&lt;/li&gt;
&lt;li&gt;功能：(OPD)向左移指定的次数，低位补0。&lt;/li&gt;
&lt;li&gt;每左移一次，相当于*2。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012552837.png&quot; alt=&quot;image-20251116012552837&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;b) 算术右移&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SAR n, OPD&lt;/li&gt;
&lt;li&gt;功能：(OPD)向右移指定位数，最高位(符号位)不变。&lt;/li&gt;
&lt;li&gt;实现有符号数除2^n运算。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012620243.png&quot; alt=&quot;image-20251116012620243&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;c) 逻辑右移&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SHR OPD, n&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;功能：(OPD)向右移指定位数，最高位补0。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现无符号数除2^n运算。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：算术移位适合于有符号数；逻辑移位适合于无符号数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012648519.png&quot; alt=&quot;image-20251116012648519&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;(2) 循环移位指令&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;a）循环左移 (ROL n, OPD): 最高位与最低位连成环。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012711908.png&quot; alt=&quot;image-20251116012711908&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;b）循环右移 (ROR OPD, n): 最低位与最高位连成环。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012726377.png&quot; alt=&quot;image-20251116012726377&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;c）带进位循环左移 (RCL OPD, n): (OPD)连同CF一起向左循环。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012735928.png&quot; alt=&quot;image-20251116012735928&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;d）带进位循环右移 (RCR n, OPD): (OPD)连同CF一起向右循环。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012743394.png&quot; alt=&quot;image-20251116012743394&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;移位指令小结：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012801483.png&quot; alt=&quot;image-20251116012801483&quot;&gt;&lt;/p&gt;
&lt;h2&gt;控制转移指令&lt;/h2&gt;
&lt;p&gt;特点：改变程序的执行顺序，即改变了指令指示器IP的内容。 分为条件转移和无条件转移。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251116012812584.png&quot; alt=&quot;image-20251116012812584&quot;&gt;&lt;/p&gt;
&lt;h3&gt;1. 标志寄存器&lt;/h3&gt;
&lt;p&gt;保存指令执行后CPU的状态信息及运算结果特征。 32位标志寄存器称为EFLAGS。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;(1) 符号标志SF (Sign Flag)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;运算结果的最高二进制位为1，则SF=1 (负)，否则SF=0 (正)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(2) 进位标志 CF (Carry Flag)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;运算时从最高位向前产生了进位（或借位），则CF=1；否则 CF=0。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(3) 零标志 ZF (Zero Flag)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;运算结果为0，则 ZF＝1，否则 ZF＝0。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(4) 溢出标志 OF (Overflow Flag)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;运算结果超出了有符号数的范围，则 OF=1。&lt;/li&gt;
&lt;li&gt;硬件判断：当出现“正+正=负、负+负=正、正-负=负、负-正=正”，产生溢出。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 条件转移&lt;/h3&gt;
&lt;p&gt;功能：由上一条指令所设的条件码来判别测试条件，满足条件则转移。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;(1) 简单条件转移&lt;/strong&gt; (根据单个标志位)
&lt;ul&gt;
&lt;li&gt;a) CF标志：JC (CF=1) / JNC (CF=0)&lt;/li&gt;
&lt;li&gt;b) ZF标志：JZ (ZF=1) / JNZ (ZF=0)&lt;/li&gt;
&lt;li&gt;c) SF标志：JS (SF=1) / JNS (SF=0)&lt;/li&gt;
&lt;li&gt;d) OF标志：JO (OF=1) / JNO (OF=0)&lt;/li&gt;
&lt;li&gt;e) PF标志：JP (偶) / JNP (奇)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(2) 无符号数的条件转移&lt;/strong&gt; (跟在CMP后，比较无符号数)
&lt;ul&gt;
&lt;li&gt;a) JA / JNBE (高于 / 不低于或等于) (测试条件：CF∨ZF=0)&lt;/li&gt;
&lt;li&gt;b) JAE / JNB (高于等于 / 不低于) (测试条件：CF=0)&lt;/li&gt;
&lt;li&gt;c) JB / JNAE (低于 / 不高于且不等于) (测试条件：CF=1)&lt;/li&gt;
&lt;li&gt;d) JBE / JNA (低于等于 / 不高于) (测试条件：CF∨ZF=1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(3) 有符号数条件转移&lt;/strong&gt; (跟在CMP后，比较有符号数)
&lt;ul&gt;
&lt;li&gt;a) JG / JNLE (大于 / 不小于且不等于) (测试条件：(SF⊕OF)∨ZF=0)&lt;/li&gt;
&lt;li&gt;b) JGE / JNL (大于等于 / 不小于) (测试条件：SF⊕OF=0)&lt;/li&gt;
&lt;li&gt;c) JL / JNGE (小于 / 不大于且不等于) (测试条件：SF⊕OF=1)&lt;/li&gt;
&lt;li&gt;d) JLE / JNG (小于等于 / 不大于) (测试条件：(SF⊕OF)∨ZF=1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：CMP比较指令本身无法分别有、无符号数，它比较的是否有符号，由后面的转移指令确定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3. 无条件转移指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;格式：JMP OPD&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;功能：无条件地转移到目的地址执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;a) 直接转移&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OPD为标号或地址值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jmp label_1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;b) 间接转移&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OPD为寄存器操作数或内存操作数。JMP以OPD的内容作为转移目的地址。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jmp *%eax&lt;/code&gt; (OPD为寄存器操作数)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jmp *var_1&lt;/code&gt; (OPD是内存操作数)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jmp *(%eax)&lt;/code&gt; (OPD是内存操作数, (%eax)的内容是地址)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;间接跳转可用于实现跳转表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;functab:  .long  lp1, lp2, lp3, …
...
jmp  functab(, %ebx, 4) ; EBX=0,跳lp1; EBX=1,跳lp2
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 转移传送指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;带条件的数据传送指令&lt;/li&gt;
&lt;li&gt;格式：cmov*** OPS，OPD&lt;/li&gt;
&lt;li&gt;功能：在条件成立时，传送数据。&lt;/li&gt;
&lt;li&gt;注意：OPS为寄存器或内存操作数，OPD为寄存器操作数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、分支&lt;/h2&gt;
&lt;p&gt;特点：计算机根据不同情况自动作出判断，有选择地执行相应处理程序。 对应C语言的 &lt;code&gt;if-else&lt;/code&gt; 和 &lt;code&gt;switch&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序分支一般用条件转移指令产生&lt;/li&gt;
&lt;li&gt;分支程序设计应注意：
&lt;ol&gt;
&lt;li&gt;选择合适的转移指令 (如JL和JB的区别)&lt;/li&gt;
&lt;li&gt;为每个分支安排出口 (避免分支执行后“掉入”另一个分支)&lt;/li&gt;
&lt;li&gt;按流程图编程&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;六、循环&lt;/h2&gt;
&lt;h3&gt;1. 循环程序的结构&lt;/h3&gt;
&lt;p&gt;由三个部分组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;设置循环的初始状态&lt;/strong&gt; (如设置循环次数计数值)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环体&lt;/strong&gt; (工作部分和修改部分)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环控制部分&lt;/strong&gt; (判断是否继续循环)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. 循环控制方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(1) 计数控制&lt;/strong&gt; (循环次数已知)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a) 正计数法：计数值从0加到n。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov	$0, %ecx
label_1:
...			; 循环体
inc	%ecx
cmp	n,  %ecx
jne	label_1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;b) 倒计数法：计数值从n减到0。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov	n, %ecx
label_1:
...			; 循环体
dec	%ecx
jnz	label_1
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注：&lt;code&gt;dec %ecx; jnz label_1&lt;/code&gt; 可以用 &lt;code&gt;loop label1&lt;/code&gt; 代替。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(2) 条件控制&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;特点：循环次数事先不知道。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例：求AX中1的个数，直到AX为0。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov	$0, %cl	; CL中存放1的个数
label_1:
and	%ax, %ax	; 产生条件
jz	exit
sal	$1, %ax	; 算术左移，b15 -&gt; CF
jnc	label_1
inc	%cl	; 若CF = 1, CL + 1 -&gt; CL
jmp	label_1
exit:
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 循环转移指令&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;a) LOOP 标号&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;功能：(ECX) −1 → ECX，若(ECX)不为0，则转标号处执行。&lt;/li&gt;
&lt;li&gt;基本等价于 &lt;code&gt;DEC ECX&lt;/code&gt; + &lt;code&gt;JNZ 标号&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;注意：LOOP指令对标志位无影响!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b) LOOPE / LOOPZ 标号&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;功能：(ECX) −1 → ECX，若(ECX)≠0 且 ZF=1，则转标号处执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;c) LOOPNE / LOOPNZ 标号&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;功能：(ECX) −1 → ECX，若(ECX)≠0 且 ZF=0，则转标号处执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;d) JCXZ 标号 / JECXZ 标号&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;功能：若(ECX)为0，则转标号处执行。(用于实现“先判断，后执行”的循环)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 循环程序设计&lt;/h3&gt;
&lt;p&gt;(1) 单重循环程序设计 (2) 最大循环次数未知 设计步骤：构思算法 -&gt; 伪代码 -&gt; 分配寄存器 -&gt; 编写汇编。&lt;/p&gt;
&lt;h2&gt;堆栈&lt;/h2&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;定义：&lt;strong&gt;主存中的一片数据存贮区&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用途：将主程序堆栈保护或处理终端&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;压栈&lt;/li&gt;
&lt;li&gt;出栈&lt;/li&gt;
&lt;li&gt;栈底：堆栈的固定端 &lt;strong&gt;不放数据&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;栈顶：最后放入的单元&lt;/li&gt;
&lt;li&gt;LIFO表：详见数据结构&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一端固定，一端活动&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;存取“先进后出”&lt;/li&gt;
&lt;li&gt;操作单元大于字单元&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESP栈顶指针&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESP由高地址向低地址移动&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;不要忘了栈底就一个字，不是双字&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;堆栈指令&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;进栈指令 PUSH OPS
&lt;ul&gt;
&lt;li&gt;功能：将OPS的字数据压入堆栈。&lt;/li&gt;
&lt;li&gt;进栈活动：
&lt;ul&gt;
&lt;li&gt;SP - 2 -&gt; SP&lt;/li&gt;
&lt;li&gt;OPS -&gt; (%SP)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出栈指令 POP OPD&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;格式：POP OPD (OPD：寄存器、段寄存器(除CS)、存储器)&lt;/li&gt;
&lt;li&gt;功能：将栈顶元素弹出到OPD。&lt;/li&gt;
&lt;li&gt;出栈活动：
&lt;ul&gt;
&lt;li&gt;(%SP) → OPD&lt;/li&gt;
&lt;li&gt;SP + 2 → SP (或+4, +8)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8个寄存器进出栈指令&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;PUSHA / POPA (16位寄存器)&lt;/li&gt;
&lt;li&gt;PUSHAD / POPAD (32位寄存器)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;子程序&lt;/h2&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;概念：功能相对独立的程序段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户自定义子程序&lt;/li&gt;
&lt;li&gt;系统子程序（如标准库）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;子程序与主程序的关系&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;调用与返回：主程序调用子程序，子程序执行结束后，又返回到主程序调用处的下一条指令。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用的本质是什么——转移到&lt;strong&gt;子程序的第一行&lt;/strong&gt;指令处，开始执行子程序的指令&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;返回的本质是什么——&lt;strong&gt;转移&lt;/strong&gt;到主程序中，调用指令的下一行指令处，开始执行主程序的指令&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;转移的本质是什么——修改 &lt;strong&gt;EIP&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如何实现 &lt;strong&gt;调用-返回&lt;/strong&gt; ——借助&lt;strong&gt;堆栈&lt;/strong&gt;主程序的指令地址&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;子程序的定义&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;.type  func_name,  @function
func_name:
	# 子程序体
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# 例：定义一个名为sort的子程序
# call.s
.type  sort,  @function
sort:
...
	ret
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;子程序的调用和返回&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;调用指令 &lt;code&gt;CALL&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;格式： CALL OPD&lt;/p&gt;
&lt;p&gt;功能： 返回&lt;/p&gt;
&lt;h3&gt;现场保护和现场恢复&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;调用子程序时，有可能破坏主程序中寄存器原来的内容，因此，必须保护、恢复现场。&lt;/li&gt;
&lt;li&gt;通过在子程序开头 &lt;code&gt;PUSH&lt;/code&gt; 寄存器，在 &lt;code&gt;RET&lt;/code&gt; 前 &lt;code&gt;POP&lt;/code&gt; 寄存器来实现。&lt;/li&gt;
&lt;li&gt;注意：
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;POP&lt;/code&gt; 的顺序必须与 &lt;code&gt;PUSH&lt;/code&gt; 的顺序相反。&lt;/li&gt;
&lt;li&gt;可以使用 &lt;code&gt;PUSHAD&lt;/code&gt; 和 &lt;code&gt;POPAD&lt;/code&gt; 保护/恢复所有寄存器。&lt;/li&gt;
&lt;li&gt;若使用寄存器EAX存放子程序的返回值，则不能对其进行保护和恢复。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参数传递&lt;/h3&gt;
&lt;p&gt;主程序为子程序提供入口参数，子程序返回结果给主程序。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;a) 寄存器法&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;利用寄存器传送，适合参数少的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;b) 约定单元法&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;参数在事先约定的存贮单元（全局变量）中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;c) 堆栈法&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;利用堆栈传递参数。&lt;/li&gt;
&lt;li&gt;参数个数不受限制，适用于子程序嵌套调用。&lt;/li&gt;
&lt;li&gt;C语言等高级语言主要采用此法。&lt;/li&gt;
&lt;li&gt;主程序 &lt;code&gt;PUSH&lt;/code&gt; 参数，然后 &lt;code&gt;CALL&lt;/code&gt;。子程序通过 &lt;code&gt;(%ebp)&lt;/code&gt; 访问参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;子程序举例&lt;/h3&gt;
&lt;p&gt;(示例：十进制输入转十六进制输出)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;.section    .data
    in_buf: .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    out_buf:    .byte 0, 0, 0, 0, 0, 0, 0, 0, &apos;\n&apos;
    lf:     .byte &apos;\n&apos;    
.section    .text
.global     _start
_start:
    call    decibin
    call    binihex
    mov     $1, %eax
    mov     $0, %ebx
    int     $0x80
.type       decibin, @function
decibin:
    mov     $3, %eax
    mov     $0, %ebx
    mov     $in_buf, %ecx
    mov     $10, %edx
    int     $0x80
    mov     %eax, %ecx
    mov     $0, %eax
    mov     $0, %esi
    dec     %ecx
next_1:
    movzxb  in_buf(%esi), %ebx
    sub     $&apos;0&apos;, %ebx
    cmp     $9, %ebx
    jg      err_1
    cmp     $0, %ebx
    jl      err_1
    mov     $10, %edx
    mul     %edx
    add     %ebx, %eax
    inc     %esi..
    dec     %ecx
    jnz     next_1
    jmp     exit_1  
err_1:
    mov     $0, %eax    
exit_1:
    ret
.type       binihex, @function
binihex:
    mov     $8, %ecx
    mov     $0, %edi
next_2:
    rol     $4, %eax
    mov     %al, %dl
    and     $0xf, %dl
    add     $&apos;0&apos;, %dl
    cmp     $0x3a, %dl
    jl      label_2
    add     $7, %dl
label_2:
    mov     %dl, out_buf(%edi)
    inc     %edi
    dec     %ecx
    jnz     next_2
    mov     $4, %eax
    mov     $1, %ebx
    mov     $out_buf, %ecx
    mov     $9, %edx
    int     $0x80   
    ret
&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Chapter III-2</title><link>https://nkns.cc/notes/csapp/chapter_iii_2</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_iii_2</guid><description>CSAPP NOTE CHAP III-2</description><pubDate>Fri, 03 Oct 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chapter 3.2 IA-32 指令系统概述&lt;/h1&gt;
&lt;h2&gt;数据类型及其格式&lt;/h2&gt;
&lt;p&gt;IA-32 指令系统中，操作数可以按存储方式分为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;立即数&lt;/li&gt;
&lt;li&gt;寄存器操作数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储器操作数&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注：IA-32 指令系统中，操作数被看作 0/1 序列&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AT&amp;#x26;T 格式和 Intel 格式的汇编指令有不小的区别：&lt;/p&gt;
&lt;p&gt;| Intel格式    | AT&amp;#x26;T格式             |
| ------------ | -------------------- |
| MOV EAX, 0   | &lt;strong&gt;movl  $0, %eax&lt;/strong&gt;   |
| ADD EAX, EBX | &lt;strong&gt;addl&lt;/strong&gt;  %ebx, %eax |
| INC ECX      | &lt;strong&gt;incl&lt;/strong&gt;  %ecx       |&lt;/p&gt;
&lt;p&gt;两者目的操作数和源操作数的顺序不同！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;AT&amp;#x26;T 指令长度后缀&lt;/h3&gt;
&lt;p&gt;| 后缀 | 操作数 | 例子           |
| ---- | ------ | -------------- |
| b    | 8位    | movb $0， %al, |
| w    | 16位   | movw  $0, buf  |
| l    | 32位   | movl $0, %eax  |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;若指令中有一个操作数是寄存器，则操作数长度已确定，长度后缀可省略（不推荐）&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;寄存器组织&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026123928449.png&quot; alt=&quot;image-20251026123928449&quot;&gt;&lt;/p&gt;
&lt;h3&gt;寻址方式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;主存储器&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;|          | 主存 | 辅存 |
| -------- | ---- | ---- |
| 容量     | 小   | 大   |
| 存取速度 | 快   | 慢   |&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基本单位是 bit&lt;/li&gt;
&lt;li&gt;为了区别各个存贮单元，给每个单元编号，称为&lt;strong&gt;地址&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;字节&lt;/strong&gt;单元的编号，也称为物理地址&lt;/li&gt;
&lt;li&gt;低字节放地位，高字节放高位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026124203772.png&quot; alt=&quot;image-20251026124203772&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026124408496.png&quot; alt=&quot;image-20251026124408496&quot;&gt;&lt;/p&gt;
&lt;h3&gt;存储器物理地址的形成&lt;/h3&gt;
&lt;p&gt;早期的 8086 微处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;20 位地址总线，寻址范围：$$ 2^{20} = 1M $$&lt;/li&gt;
&lt;li&gt;与地址相关的寄存器均为 16 位（SP, BP, SI, DI)，寻址范围：$$ 2^{16} = 64K$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;问题：如何通过 16 位寄存器访问 1 MB 的内存&lt;/p&gt;
&lt;p&gt;解决：将 1 M 字节主存分段，每段最多 64 K 字节&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;用 CS, DS, SS, ES 保存当前可用段的段首地址&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;计算物理地址时，应将段寄存器内容左移 4 位，然后再与偏移地址相加，得到待访问单元的物理地址。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026125908224.png&quot; alt=&quot;image-20251026125908224&quot;&gt;&lt;/p&gt;
&lt;p&gt;80x86 的寻址范围：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;80x86 地址总线 32 位，寻址范围 4 G&lt;/li&gt;
&lt;li&gt;80x86 通用寄存器 32 位，寻址范围也是 4 G&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以 80x86 的一个通用寄存器能够存放一个完整的 32 位地址&lt;/p&gt;
&lt;h3&gt;保护模式下的内存管理&lt;/h3&gt;
&lt;p&gt;要为每个进程提供一个独立的4G大小的虚拟地址空间。逻辑地址空间（多个进程的地址空间总和）大于物理地址空间。采用&lt;strong&gt;分页机制&lt;/strong&gt;来实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将物理地址映射到进程的逻辑地址空间上；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有映射了物理内存的逻辑地址空间才能访问；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每次映射的物理内存不大，且用完后可释放，再重新映射到新的逻辑地址空间上，因此能够让多个进程同时运行；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分页由系统基于页目录和页表，自动完成&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026130228471.png&quot; alt=&quot;image-20251026130228471&quot;&gt;&lt;/p&gt;
&lt;p&gt;特权级与&lt;strong&gt;分段机制&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;80x86有4个特权级：0,1,2,3（0级&lt;strong&gt;最高&lt;/strong&gt;，3级&lt;strong&gt;最低&lt;/strong&gt;），能够在虚拟内存的基础上实现进一步的内存保护机制&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026130514801.png&quot; alt=&quot;image-20251026130514801&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;80x86通过&lt;strong&gt;分段机制&lt;/strong&gt;来实现特权级的访问控制。&lt;/p&gt;
&lt;p&gt;用“&lt;strong&gt;段选择符&lt;/strong&gt;：EA”的形式表示逻辑地址，段选择符（16位）存放在段寄存器中，指向一个&lt;strong&gt;段描述符&lt;/strong&gt;（64位）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026130500434.png&quot; alt=&quot;image-20251026130500434&quot;&gt;&lt;/p&gt;
&lt;p&gt;用逻辑地址访问内存时，段选择符中的特权级需要高于段描述符中的&lt;strong&gt;特权级&lt;/strong&gt;。&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;由逻辑地址生成物理地址的过程：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026131624700.png&quot; alt=&quot;image-20251026131624700&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EA要小于“段界限 * 4K”。一般应用程序中，段基址为0，段界限为0FFFFFH。所以对线性地址的形成不产生影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;具体寻址方式（重要）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026131700566.png&quot; alt=&quot;image-20251026131700566&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;立即寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;操作数直接存放在指令中，紧跟在操作码之后，它作为指令的一部分存放在代码段里，这种操作数称为立即数。&lt;/p&gt;
&lt;p&gt;汇编格式：$value&lt;/p&gt;
&lt;p&gt;功能：指令的下一单元的内容为操作数 n，即：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026131902152.png&quot; alt=&quot;image-20251026131902152&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt; movl $0x3064, %eax&lt;/p&gt;
&lt;p&gt;目的操作数采用寄存器寻址，为寄存器 eax&lt;/p&gt;
&lt;p&gt;源操作数采用立即寻址，即：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026132347724.png&quot; alt=&quot;image-20251026132347724&quot;&gt;&lt;/p&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;立即数指令作为源操作数，&lt;strong&gt;不能作为目的操作数&lt;/strong&gt;！&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;立即数&lt;strong&gt;不能作为单操作数指令的操作数&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;立即数&lt;strong&gt;只有大小，没有类型，未分配内存单元&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;寄存器寻址&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​	在这种寻址方式中，指令所指明的寄存器中存放操作数&lt;/p&gt;
&lt;p&gt;​	汇编格式：%R&lt;/p&gt;
&lt;p&gt;​	功能：寄存器R的内容就是操作数。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026132547362.png&quot; alt=&quot;image-20251026132547362&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 1&lt;/strong&gt; &lt;code&gt;incl %eax&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​	操作数在 eax 中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026132620740.png&quot; alt=&quot;image-20251026132620740&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	执行前：EAX=12H&lt;/p&gt;
&lt;p&gt;​	执行：EAX+1 → EAX。&lt;/p&gt;
&lt;p&gt;​	执行后：EAX= 13H&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 2&lt;/strong&gt; &lt;code&gt;add %ebx, %eax&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​	EAX为目的操作数地址，EBX为源操作数地址。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251026132715349.png&quot; alt=&quot;image-20251026132715349&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	执行前：EAX=1234H，EBX=5620H&lt;/p&gt;
&lt;p&gt;​	执行： EAX+EBX→ EAX。&lt;/p&gt;
&lt;p&gt;​	执行后：EAX=6854H，EBX=5620H&lt;/p&gt;
&lt;p&gt;​	目的操作数、源操作数都是用寄存器寻址&lt;/p&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用寄存器操作比较快！如果可能的话尽量多用&lt;/li&gt;
&lt;li&gt;采用寄存器寻址方式，&lt;strong&gt;目的、源操作数类型必须一致&lt;/strong&gt;。如：movw %bx, %ah; ERROR&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;直接寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;直接寻址时，操作数的偏移地址就在指令操作码后面，而操作数则存放在内存中。&lt;/p&gt;
&lt;p&gt;汇编格式：EA（有效地址） 或 地址表达式&lt;/p&gt;
&lt;p&gt;功能：指令下一字单元的内容是操作数的偏移地址。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111210446867.png&quot; alt=&quot;image-20251111210446867&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	段基址为 0 时，EA 等于操作数的线性地址。&lt;/p&gt;
&lt;p&gt;​	操作数的线性地址到物理地址的转换由系统自动完成，因此在实际应用中，&lt;strong&gt;只需要着重考虑操作数的偏移地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 3&lt;/strong&gt; movl 0x2000, %eax&lt;/p&gt;
&lt;p&gt;​	执行前 (0x2000) = 1234H&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111210643821.png&quot; alt=&quot;image-20251111210643821&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	执行时：(2000H) -&gt; EAX&lt;/p&gt;
&lt;p&gt;​	执行后：EAX = 0x1234&lt;/p&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EA 格式表示的操作数没有类型&lt;/li&gt;
&lt;li&gt;地址表达式 格式中，若还有变量，则表示的操作数有类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;movw  0x20, %ax	; 源操作数无类型
movw  %ax, buf		; 目的操作数为直接寻址，其中buf为已定义的字类型的变量。则目的操作数有类型
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;寄存器间接寻址&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​	操作数的偏移地址在寄存器中，而操作数则在存贮器中。&lt;/p&gt;
&lt;p&gt;​	格式：(%R)&lt;/p&gt;
&lt;p&gt;​	功能：R 的内容为操作数的偏移地址 EA&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111221328566.png&quot; alt=&quot;image-20251111221328566&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	R可以是EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP之一。EBP和ESP默认所指的段为堆栈段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 4&lt;/strong&gt; mov (%esi), %eax&lt;/p&gt;
&lt;p&gt;​	假定执行前：EAX = 5, ESI = 1000H, (1000H) = 50A0H&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111223045715.png&quot; alt=&quot;image-20251111223045715&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	执行：(1000H) -&gt; EAX&lt;/p&gt;
&lt;p&gt;​	执行后：EAX = 50A0H，其余不变&lt;/p&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 R 是 &lt;code&gt;ebp&lt;/code&gt; &lt;code&gt;esp&lt;/code&gt; 则操作对象在堆栈段中，当 R 为其他寄存器，说明对象在数据段中。&lt;/li&gt;
&lt;li&gt;此种寻址无类型&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;变址寻址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;操作数的偏移地址 EA 是指令中指明的寄存器的内容与指令中给出的位移量之和。操作数在存贮器中。&lt;/p&gt;
&lt;p&gt;汇编格式：X(%R) 或 X(, %R, S)&lt;/p&gt;
&lt;p&gt;功能：R 的内容与 X 相加，和为操作数的偏移地址 EA&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111224824841.png&quot; alt=&quot;image-20251111224824841&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	寄存器的选择同寄存器间接寻址一样，可以用EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP之一。EBP和ESP所指的段默认为堆栈段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 5&lt;/strong&gt; movl count(%esi), %eax&lt;/p&gt;
&lt;p&gt;​	假定执行前：ESI=2000H，COUNT=3000H，EAX=0，（5000H）=1234H&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111225151603.png&quot; alt=&quot;image-20251111225151603&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	执行：(0x5000) -&gt; EAX&lt;/p&gt;
&lt;p&gt;​	执行后：EAX = 0x1234, 其余不变&lt;/p&gt;
&lt;p&gt;采用变址寻址可以方便地访问数组中的任一元素，使得编程更加方便。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 6&lt;/strong&gt; 一维数组 a，其存放如图所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111225557596.png&quot; alt=&quot;image-20251111225557596&quot;&gt;&lt;/p&gt;
&lt;p&gt;访问其元素的源码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;.section   .data
count	.byte  a0, a1,…, an
.section   .text
...
mov   offset count,  %ebx
; 若要将a8放到ah中，可用：
movb   8(%ebx),  %ah
; 若要将第 i 个元素放到AH中，可用：
movb   i(%ebx),  %ah
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;段属性问题&lt;/p&gt;
&lt;p&gt;X为常数或数值表达式时，操作对象的段由R决定&lt;/p&gt;
&lt;p&gt;X为变量或标号时，操作数对象所在的段就是变量或标号所在的段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;类型&lt;/p&gt;
&lt;p&gt;当X为&lt;strong&gt;常量&lt;/strong&gt;时，此种寻址&lt;strong&gt;无类型&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;当X为&lt;strong&gt;含有变量或者标号的表达式&lt;/strong&gt;时，则&lt;strong&gt;有类型&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如： &lt;code&gt;movw buf(%ebx), %cx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;若 BUF 是已定义的字类型变量， buf(%ebx) 类型为字类型。&lt;/p&gt;
&lt;p&gt;若 BUF 定义为字节类型，则 buf(%ebx) 与 %cx 类型不一致。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;strong&gt;基址加变址寻址&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​	操作数的偏移地址 EA 是指令中指定的基址寄存器内容、变址寄存器内容及位移量 X 三者之和。操作数存放在主存之中。&lt;/p&gt;
&lt;p&gt;​	格式：&lt;strong&gt;X(%BR, %IR, S)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​	功能：BR 的内容加上 IR * S，再加上 X，得到操作数的偏移地址。也就是：EA = BR + IR * S + X&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111230425958.png&quot; alt=&quot;image-20251111230425958&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	其中：BR 表示基址寄存器，IR 表示变址寄存器。BR 和 IR 可以是 EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP 之一。&lt;/p&gt;
&lt;p&gt;​	若 BR 为空，则简化为变址寻址。&lt;/p&gt;
&lt;p&gt;​	基址寄存器决定操作数所在的段。如果选用 EBP 或 ESP，则操作数在堆栈段中；否则操作数在数据段中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 7&lt;/strong&gt; &lt;code&gt;movw mask(%ebx, %esi, ), %ax&lt;/code&gt; mask 为一个符号常量&lt;/p&gt;
&lt;p&gt;​	源操作数采用基址加变址寻址，EA = EBX + ESI + mask&lt;/p&gt;
&lt;p&gt;​	执行前：EBX = 0x2000, ESI = 0x1000, MASK = 0x250, (0x3250) = 0x1234&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111230638210.png&quot; alt=&quot;image-20251111230638210&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	执行：(3250H) -&gt; AX&lt;/p&gt;
&lt;p&gt;​	执行后：AX = 0x1234, 其它不变&lt;/p&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;p&gt;​	段的默认情况：当 X 为常数时，BR 为 BP 默认段为 SS ,其它默认段为 DS。操作数无类型。&lt;/p&gt;
&lt;p&gt;​	当 X 为变量或标号表达式时，操作对象所在的段就是变量或标号所在的段。操作数有类型，且与变量或标号类型相同。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;~带有&lt;strong&gt;比例因子&lt;/strong&gt;的寻址方式~&lt;/p&gt;
&lt;p&gt;​	X(%BR, %IR, S)&lt;/p&gt;
&lt;p&gt;​	(1) 只在变址寄存器 IR 上可以加比例因子 S&lt;/p&gt;
&lt;p&gt;​	(2) 比例因子 S 可以为 &lt;strong&gt;1，2，4，8&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CPU提供的指令和寻址方式，需要能够将高级语言翻译成高效的机器代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt; &lt;code&gt;movw buf(%ebx, %esi, 2), %ax&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;则 EA = EBX + 2 * ESI + BUF 的偏移地址&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;寻址方式的有关问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111231356996.png&quot; alt=&quot;image-20251111231356996&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	某些指令的执行要涉及到缺省的操作数，其寻址方式也是隐含的。&lt;/p&gt;
&lt;p&gt;​	例： cbw&lt;/p&gt;
&lt;p&gt;​	隐含源和目的操作数。源操作数为 AL ,寄存器寻址；目的操作数为 AX ,寄存器寻址。&lt;/p&gt;
&lt;p&gt;​	双操作数指令中，源和目的操作数不能同为存储器寻址方式&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;movb (%esi), (%edi) ; ERROR
addl 10(%ebx, %edi, 4), count ;ERROR
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;8&quot;&gt;
&lt;li&gt;寻址方式举例&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例： 已知数据段定义和存储示意图如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;.section  .data
buf:	.byte  10, 20, 40, 80, 30
buf1:	.byte  0, 0, 0, 0, 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111233845768.png&quot; alt=&quot;image-20251111233845768&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	(1) 分别利用直接寻址，寄存器间接寻址，变址寻址和基址加变址寻址，将该段中 BUF+3 单元中的内容送到 AL 中。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;a) 直接寻址
mov  buf(3), %al
b) 寄存器间接寻址
lea  buf(3),  %ebx ;将	BUF+3相对于段首地	址的偏移地址EA=3	送入BX中
mov  (%ebx), %al
c) 寄存器变址寻址
leal  buf, %ebx
movb  3(%ebx), %al
d) 基址加变址寻址
leal  buf, %ebx
movl  $3, %esi
movb  (%ebx, %esi, ),  %al
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​	(2) 利用寄存器间接寻址，变址寻址和基址加变址寻址，将 BUF 为首址的连续字节单元的内容分别送到以 BUF1 为首址的连续字节单元中。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;a) 寄存器间接寻址
…
leal  buf, %esi
leal  buf1,  %edi
movl  $5,  %ecx
l1: 
movb  (%esi), %al
movb  %al, (%edi)
inc  %esi
inc  %edi
dec  %ecx
jnz  l1
…
b) 变址寻址
…
movl  $0,  %esi
movl  $5,  %ecx
l1: 
movb  buf(%esi), %al
movb  %al,  buf1(%esi)
inc  %esi
dec  %ecx
jnz  l1
…
c) 基址加变址寻址
…
leal  buf,  %ebx
leal  buf1, %ebp
movl  $0,  %esi
movl  $5,  %ecx
l1:  
movb  (%ebx, %esi, 1), %al
movb  %al,  (%ebp, %esi, 1)
inc  %esi
dec  %ecx
jnz  l1
…
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存储器间接寻址，变址寻址和及指甲变址寻址都能用来传送一片连续存储区的内容。&lt;/li&gt;
&lt;li&gt;上述 3 种方式中，b 的可读性最好&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常量和变量的定义&lt;/h3&gt;
&lt;p&gt;汇编程序的语句中具体的操作数，可以分为常量和变量两种：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111234238408.png&quot; alt=&quot;image-20251111234238408&quot;&gt;&lt;/p&gt;
&lt;p&gt;表达式：由常数、寄存器、标号、变量加上运算符构成的式子&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;常量：汇编时已有确定数值的量。&lt;/p&gt;
&lt;p&gt;用途：赋值、作立即数、位移量。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251111234823502.png&quot; alt=&quot;image-20251111234823502&quot;&gt;&lt;/p&gt;
&lt;p&gt;​	表达式：由常数、寄存器、标号、变量加上运算符构成的式子&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;等价伪指令 &lt;code&gt;.equ&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;等号伪指令 &lt;code&gt;=&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;使用：定义后直接引用符号名。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;# 太抽象了，举一个实际例子，这里先举.equ 的

# Before
MOV AH, 2       ; 功能号2
MOV DL, 65      ; 字符&apos;A&apos;
INT 21h         ; 调用DOS中断
# After
; --- 在程序开头定义常量 ---
DOS_PRINT_CHAR .equ 2       ; 定义“打印字符”功能的编号
CHAR_A         .equ 65      ; 定义&apos;A&apos;的ASCII码
DOS_INTERRUPT  .equ 21h     ; 定义DOS中断号

; --- 在代码中使用 ---
MOV AH, DOS_PRINT_CHAR      ; 功能：打印字符
MOV DL, CHAR_A              ; 内容：&apos;A&apos;
INT DOS_INTERRUPT           ; 执行

# 再来一个 = 的

; --- 在程序开头定义一个“开关” ---
; 0 = 发行版, 1 = 调试版
DEBUG_MODE = 1

; --- 根据开关的值，来设置真正的常量 ---
IF DEBUG_MODE == 1
    TIMEOUT_VALUE = 5       ; 调试版：超时设为 5
ELSE
    TIMEOUT_VALUE = 60      ; 发行版：超时设为 60
ENDIF

; --- 在你的代码中 ---
; 假设有一个等待超时的函数
CALL WaitForInput
CMP AX, TIMEOUT_VALUE     ; 检查是否超时
JE  DidTimeout
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;数值表达式&lt;/p&gt;
&lt;p&gt;常量与运算符组成的式子。数值表达式在汇编期间进行运算，结果为常量。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;算术运算&lt;/p&gt;
&lt;p&gt;&lt;code&gt;+&lt;/code&gt; &lt;code&gt;-&lt;/code&gt; &lt;code&gt;*&lt;/code&gt; &lt;code&gt;/&lt;/code&gt; &lt;code&gt;MOD&lt;/code&gt; &lt;code&gt;SHR&lt;/code&gt; &lt;code&gt;SHL&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;逻辑运算&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AND&lt;/code&gt; &lt;code&gt;OR&lt;/code&gt; &lt;code&gt;XOR&lt;/code&gt; &lt;code&gt;NOT&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关系运算&lt;/p&gt;
&lt;p&gt;&lt;code&gt;EQ&lt;/code&gt; &lt;code&gt;NE&lt;/code&gt; &lt;code&gt;LT&lt;/code&gt; &lt;code&gt;GT&lt;/code&gt; &lt;code&gt;LE&lt;/code&gt; &lt;code&gt;GE&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;关系不成立结果为 0, 成立为 &lt;strong&gt;-1 (0FFFFH)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;变量&lt;/p&gt;
&lt;p&gt;数据段或附加数据段中一个数据存贮单元的名字, 是这个存储单元的地址的符号表示。可&lt;strong&gt;代表一批存储单元的首址&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;~变量的属性~&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;段属性：定义变量所在段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;偏移地址：该变量所占存储单元到所在段的段首址的字节距离&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;类型&lt;/strong&gt;：类型是指存取该变量中的数据所需要的&lt;strong&gt;字节数&lt;/strong&gt;, 变量的类型由定义该变量时所使用的伪指令确定&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;~变量的定义~&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;格式：[变量名:] 变量定义伪指令 表达式[，…]&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;变量定义伪指令：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;| 伪指令              | 数据类型            |
| ------------------- | ------------------- |
| .ascii              | 文本字符串          |
| .asciiz/.string     | 以0结尾的文本字符串 |
| .byte               | 8位字节变量         |
| .word/.short/.hword | 16位字变量          |
| .long/.int          | 32位双字变量        |
| .quad               | 64位四字变量        |
| .octa               | 128位八字变量       |
| .float/.single      | 32位单精度浮点数    |
| .double             | 64位双精度浮点数    |&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;表达式：&lt;/p&gt;
&lt;p&gt;a 数值表达式&lt;/p&gt;
&lt;p&gt;b ASCII 字符串&lt;/p&gt;
&lt;p&gt;c 地址表达式&lt;/p&gt;
&lt;p&gt;上述 a ~ c 组成的系列，各表达式之间用逗号隔开&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;~示例~&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在上例中分别执行语句后AL的结果。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;movb  buf, %al	; %al = 0x01
movw  buf + 2, %ax	; %ax = 0x3103
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指令 &lt;code&gt;mov  buf,  %edx&lt;/code&gt; 是否正确？
正确，执行 &lt;code&gt;movl buf, %edx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;p&gt;伪指令 EQU 及 “=” &lt;strong&gt;不分配存贮单元&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用&lt;strong&gt;直接寻址方式&lt;/strong&gt;时，变量的类型&lt;strong&gt;必须与指令的要求相符&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;地址表达式&lt;/p&gt;
&lt;p&gt;由变量、标号、常量、寄存器（名加括号）及一些运算符（数值表达式的运算符和特殊运算符）所组成的有意义的的式子。&lt;/p&gt;
&lt;p&gt;地址表达式也具有&lt;strong&gt;段、EA、类型&lt;/strong&gt;等三个属性。&lt;/p&gt;
&lt;p&gt;简单的地址表达式：直接寻址、间接寻址、变址、基址加变址等。&lt;/p&gt;
&lt;p&gt;取偏移地址&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$地址表达式&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​	功能：分离出变量、标号的偏移地址&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt; &lt;code&gt;movl $buf, %esi&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​	将变量buf的偏移地址，传送到esi中&lt;/p&gt;
&lt;p&gt;​	&lt;strong&gt;$buf是一个常量&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当前即将分配的地址&lt;/p&gt;
&lt;p&gt;功能：表示汇编程序当前即将分配的地址&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​	格式：.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;.section  .data
buf:  .ascii  “12345”
con	= . - buf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​	“.“表示当前即将分配的地址，“. - buf”表示当前地址减去buf的偏移地址，结果即为变量 buf 的字节长度&lt;/p&gt;
&lt;p&gt;​	“. - buf” 在汇编时计算，结果为一个常数值&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;填充伪指令&lt;/p&gt;
&lt;p&gt;格式：&lt;code&gt;.fill repeat, size, value&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​	功能：使用初始值，重复 repeat 次填充 size 个字节&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt; &lt;code&gt;.fill 10, 2, 0x5656&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;​	分配 20 个字节，用 word 值 0x5656, 填充 10 次&lt;/p&gt;</content:encoded></item><item><title>Chapter III-1</title><link>https://nkns.cc/notes/csapp/chapter_iii_1</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_iii_1</guid><description>CSAPP NOTE CHAP III-1</description><pubDate>Thu, 02 Oct 2025 16:59:19 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Chapter III&lt;/strong&gt; 程序的转换及机器级表示&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;Chapter 3.1 程序转换概述&lt;/h1&gt;
&lt;p&gt;计算机硬件只能识别&lt;strong&gt;机器语言&lt;/strong&gt;。用&lt;strong&gt;汇编语言&lt;/strong&gt;或&lt;strong&gt;高级语言&lt;/strong&gt;编写的源代码，需要转换成机器指令才能运行。&lt;/p&gt;
&lt;p&gt;一般先用&lt;strong&gt;编译器&lt;/strong&gt;将高级语言源代码转为汇编语言目标代码，再用&lt;strong&gt;汇编程序&lt;/strong&gt;将汇编语言源代码转为机器语言目标代码。&lt;/p&gt;
&lt;h2&gt;机器指令及汇编指令&lt;/h2&gt;
&lt;p&gt;机器指令的一般形式为：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251024200519982.png&quot; alt=&quot;image-20251024200519982&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;操作码：数据传送、加减运算等&lt;/li&gt;
&lt;li&gt;地址码：操作数和运算结果的存放位置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt; 一条机器指令占 3 个字节（24位），其内容解析如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251024200622910.png&quot; alt=&quot;image-20251024200622910&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt; 这玩意缺点太明显了，略去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进方案&lt;/strong&gt; 将机器语言符号化&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251024200742733.png&quot; alt=&quot;image-20251024200742733&quot;&gt;&lt;/p&gt;
&lt;p&gt;这样就诞生了汇编语言。例如上面的机器指令可以被写为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;操作码		  地址码
B8			F7 00
MOV 		AX, 0F7H
// 对应的汇编指令
mov	$0xf7, %ax
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;指令集体系结构&lt;/h2&gt;
&lt;p&gt;ISA 是计算机系统最重要的抽象层，将物力上的计算机硬件抽象成一个逻辑上的虚拟计算机，也被称为&lt;strong&gt;机器语言级虚拟机&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;ISA 定义了机器语言级虚拟机的属性和功能特性，主要包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可执行的指令的集合&lt;/li&gt;
&lt;li&gt;可接受的操作数类型&lt;/li&gt;
&lt;li&gt;寄存器组的结构&lt;/li&gt;
&lt;li&gt;存储空间&lt;/li&gt;
&lt;li&gt;大端存放还是小端存放&lt;/li&gt;
&lt;li&gt;寻址方式&lt;/li&gt;
&lt;li&gt;指令执行的控制方式&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;生成机器代码的过程&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251024201355210.png&quot; alt=&quot;image-20251024201355210&quot;&gt;&lt;/p&gt;
&lt;p&gt;一个程序的完整生成过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;预处理：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;gcc -E -o hello.i hello.c
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;编译：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;gcc -S -o hello.s hello.i
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;汇编：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;gcc -c -o hello.o hello.s
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;链接：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;gcc -o hello hello.o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个更简单的生成方法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gcc -o hello hello.c
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;创建了一个 hello.c&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// hello.c
#include&amp;#x3C;stdio.h&gt;
int main() {
    printf(&quot;hello world!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251024201843553.png&quot; alt=&quot;image-20251024201843553&quot;&gt;&lt;/p&gt;
&lt;p&gt;之后按顺序执行这几个命令：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251024202048076.png&quot; alt=&quot;image-20251024202048076&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看到程序运行成功。&lt;/p&gt;
&lt;p&gt;其中几个文件的内容：（.i 文件内容太长，放在最后了）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// hello.o
// 这个文件是机器可以读懂的二进制文件而非文本文件。

// hello.s
	.file	&quot;hello.c&quot;
	.text
	.section	.rodata
.LC0:
	.string	&quot;hello world!&quot;
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	leaq	.LC0(%rip), %rax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	&quot;GCC: (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0&quot;
	.section	.note.GNU-stack,&quot;&quot;,@progbits
	.section	.note.gnu.property,&quot;a&quot;
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	&quot;GNU&quot;
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

// hello.i
# 0 &quot;hello.c&quot;
# 0 &quot;&amp;#x3C;built-in&gt;&quot;
# 0 &quot;&amp;#x3C;command-line&gt;&quot;
# 1 &quot;/usr/include/stdc-predef.h&quot; 1 3 4
# 0 &quot;&amp;#x3C;command-line&gt;&quot; 2
# 1 &quot;hello.c&quot;
# 1 &quot;/usr/include/stdio.h&quot; 1 3 4
# 27 &quot;/usr/include/stdio.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/libc-header-start.h&quot; 1 3 4
# 33 &quot;/usr/include/x86_64-linux-gnu/bits/libc-header-start.h&quot; 3 4
# 1 &quot;/usr/include/features.h&quot; 1 3 4
# 392 &quot;/usr/include/features.h&quot; 3 4
# 1 &quot;/usr/include/features-time64.h&quot; 1 3 4
# 20 &quot;/usr/include/features-time64.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/wordsize.h&quot; 1 3 4
# 21 &quot;/usr/include/features-time64.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/timesize.h&quot; 1 3 4
# 19 &quot;/usr/include/x86_64-linux-gnu/bits/timesize.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/wordsize.h&quot; 1 3 4
# 20 &quot;/usr/include/x86_64-linux-gnu/bits/timesize.h&quot; 2 3 4
# 22 &quot;/usr/include/features-time64.h&quot; 2 3 4
# 393 &quot;/usr/include/features.h&quot; 2 3 4
# 486 &quot;/usr/include/features.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/sys/cdefs.h&quot; 1 3 4
# 559 &quot;/usr/include/x86_64-linux-gnu/sys/cdefs.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/wordsize.h&quot; 1 3 4
# 560 &quot;/usr/include/x86_64-linux-gnu/sys/cdefs.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/long-double.h&quot; 1 3 4
# 561 &quot;/usr/include/x86_64-linux-gnu/sys/cdefs.h&quot; 2 3 4
# 487 &quot;/usr/include/features.h&quot; 2 3 4
# 510 &quot;/usr/include/features.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/gnu/stubs.h&quot; 1 3 4
# 10 &quot;/usr/include/x86_64-linux-gnu/gnu/stubs.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/gnu/stubs-64.h&quot; 1 3 4
# 11 &quot;/usr/include/x86_64-linux-gnu/gnu/stubs.h&quot; 2 3 4
# 511 &quot;/usr/include/features.h&quot; 2 3 4
# 34 &quot;/usr/include/x86_64-linux-gnu/bits/libc-header-start.h&quot; 2 3 4
# 28 &quot;/usr/include/stdio.h&quot; 2 3 4





# 1 &quot;/usr/lib/gcc/x86_64-linux-gnu/11/include/stddef.h&quot; 1 3 4
# 209 &quot;/usr/lib/gcc/x86_64-linux-gnu/11/include/stddef.h&quot; 3 4

# 209 &quot;/usr/lib/gcc/x86_64-linux-gnu/11/include/stddef.h&quot; 3 4
typedef long unsigned int size_t;
# 34 &quot;/usr/include/stdio.h&quot; 2 3 4


# 1 &quot;/usr/lib/gcc/x86_64-linux-gnu/11/include/stdarg.h&quot; 1 3 4
# 40 &quot;/usr/lib/gcc/x86_64-linux-gnu/11/include/stdarg.h&quot; 3 4
typedef __builtin_va_list __gnuc_va_list;
# 37 &quot;/usr/include/stdio.h&quot; 2 3 4

# 1 &quot;/usr/include/x86_64-linux-gnu/bits/types.h&quot; 1 3 4
# 27 &quot;/usr/include/x86_64-linux-gnu/bits/types.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/wordsize.h&quot; 1 3 4
# 28 &quot;/usr/include/x86_64-linux-gnu/bits/types.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/timesize.h&quot; 1 3 4
# 19 &quot;/usr/include/x86_64-linux-gnu/bits/timesize.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/wordsize.h&quot; 1 3 4
# 20 &quot;/usr/include/x86_64-linux-gnu/bits/timesize.h&quot; 2 3 4
# 29 &quot;/usr/include/x86_64-linux-gnu/bits/types.h&quot; 2 3 4


typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;


typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;

typedef signed long int __int64_t;
typedef unsigned long int __uint64_t;






typedef __int8_t __int_least8_t;
typedef __uint8_t __uint_least8_t;
typedef __int16_t __int_least16_t;
typedef __uint16_t __uint_least16_t;
typedef __int32_t __int_least32_t;
typedef __uint32_t __uint_least32_t;
typedef __int64_t __int_least64_t;
typedef __uint64_t __uint_least64_t;



typedef long int __quad_t;
typedef unsigned long int __u_quad_t;







typedef long int __intmax_t;
typedef unsigned long int __uintmax_t;
# 141 &quot;/usr/include/x86_64-linux-gnu/bits/types.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/typesizes.h&quot; 1 3 4
# 142 &quot;/usr/include/x86_64-linux-gnu/bits/types.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/time64.h&quot; 1 3 4
# 143 &quot;/usr/include/x86_64-linux-gnu/bits/types.h&quot; 2 3 4


typedef unsigned long int __dev_t;
typedef unsigned int __uid_t;
typedef unsigned int __gid_t;
typedef unsigned long int __ino_t;
typedef unsigned long int __ino64_t;
typedef unsigned int __mode_t;
typedef unsigned long int __nlink_t;
typedef long int __off_t;
typedef long int __off64_t;
typedef int __pid_t;
typedef struct { int __val[2]; } __fsid_t;
typedef long int __clock_t;
typedef unsigned long int __rlim_t;
typedef unsigned long int __rlim64_t;
typedef unsigned int __id_t;
typedef long int __time_t;
typedef unsigned int __useconds_t;
typedef long int __suseconds_t;
typedef long int __suseconds64_t;

typedef int __daddr_t;
typedef int __key_t;


typedef int __clockid_t;


typedef void * __timer_t;


typedef long int __blksize_t;




typedef long int __blkcnt_t;
typedef long int __blkcnt64_t;


typedef unsigned long int __fsblkcnt_t;
typedef unsigned long int __fsblkcnt64_t;


typedef unsigned long int __fsfilcnt_t;
typedef unsigned long int __fsfilcnt64_t;


typedef long int __fsword_t;

typedef long int __ssize_t;


typedef long int __syscall_slong_t;

typedef unsigned long int __syscall_ulong_t;



typedef __off64_t __loff_t;
typedef char *__caddr_t;


typedef long int __intptr_t;


typedef unsigned int __socklen_t;




typedef int __sig_atomic_t;
# 39 &quot;/usr/include/stdio.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h&quot; 1 3 4




# 1 &quot;/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h&quot; 1 3 4
# 13 &quot;/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h&quot; 3 4
typedef struct
{
  int __count;
  union
  {
    unsigned int __wch;
    char __wchb[4];
  } __value;
} __mbstate_t;
# 6 &quot;/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h&quot; 2 3 4




typedef struct _G_fpos_t
{
  __off_t __pos;
  __mbstate_t __state;
} __fpos_t;
# 40 &quot;/usr/include/stdio.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h&quot; 1 3 4
# 10 &quot;/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h&quot; 3 4
typedef struct _G_fpos64_t
{
  __off64_t __pos;
  __mbstate_t __state;
} __fpos64_t;
# 41 &quot;/usr/include/stdio.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/types/__FILE.h&quot; 1 3 4



struct _IO_FILE;
typedef struct _IO_FILE __FILE;
# 42 &quot;/usr/include/stdio.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/types/FILE.h&quot; 1 3 4



struct _IO_FILE;


typedef struct _IO_FILE FILE;
# 43 &quot;/usr/include/stdio.h&quot; 2 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h&quot; 1 3 4
# 35 &quot;/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h&quot; 3 4
struct _IO_FILE;
struct _IO_marker;
struct _IO_codecvt;
struct _IO_wide_data;




typedef void _IO_lock_t;





struct _IO_FILE
{
  int _flags;


  char *_IO_read_ptr;
  char *_IO_read_end;
  char *_IO_read_base;
  char *_IO_write_base;
  char *_IO_write_ptr;
  char *_IO_write_end;
  char *_IO_buf_base;
  char *_IO_buf_end;


  char *_IO_save_base;
  char *_IO_backup_base;
  char *_IO_save_end;

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _flags2;
  __off_t _old_offset;


  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  _IO_lock_t *_lock;







  __off64_t _offset;

  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  size_t __pad5;
  int _mode;

  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
# 44 &quot;/usr/include/stdio.h&quot; 2 3 4
# 52 &quot;/usr/include/stdio.h&quot; 3 4
typedef __gnuc_va_list va_list;
# 63 &quot;/usr/include/stdio.h&quot; 3 4
typedef __off_t off_t;
# 77 &quot;/usr/include/stdio.h&quot; 3 4
typedef __ssize_t ssize_t;






typedef __fpos_t fpos_t;
# 133 &quot;/usr/include/stdio.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/stdio_lim.h&quot; 1 3 4
# 134 &quot;/usr/include/stdio.h&quot; 2 3 4
# 143 &quot;/usr/include/stdio.h&quot; 3 4
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;






extern int remove (const char *__filename) __attribute__ ((__nothrow__ , __leaf__));

extern int rename (const char *__old, const char *__new) __attribute__ ((__nothrow__ , __leaf__));



extern int renameat (int __oldfd, const char *__old, int __newfd,
       const char *__new) __attribute__ ((__nothrow__ , __leaf__));
# 178 &quot;/usr/include/stdio.h&quot; 3 4
extern int fclose (FILE *__stream);
# 188 &quot;/usr/include/stdio.h&quot; 3 4
extern FILE *tmpfile (void)
  __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ;
# 205 &quot;/usr/include/stdio.h&quot; 3 4
extern char *tmpnam (char[20]) __attribute__ ((__nothrow__ , __leaf__)) ;




extern char *tmpnam_r (char __s[20]) __attribute__ ((__nothrow__ , __leaf__)) ;
# 222 &quot;/usr/include/stdio.h&quot; 3 4
extern char *tempnam (const char *__dir, const char *__pfx)
   __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (__builtin_free, 1)));






extern int fflush (FILE *__stream);
# 239 &quot;/usr/include/stdio.h&quot; 3 4
extern int fflush_unlocked (FILE *__stream);
# 258 &quot;/usr/include/stdio.h&quot; 3 4
extern FILE *fopen (const char *__restrict __filename,
      const char *__restrict __modes)
  __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ;




extern FILE *freopen (const char *__restrict __filename,
        const char *__restrict __modes,
        FILE *__restrict __stream) ;
# 293 &quot;/usr/include/stdio.h&quot; 3 4
extern FILE *fdopen (int __fd, const char *__modes) __attribute__ ((__nothrow__ , __leaf__))
  __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ;
# 308 &quot;/usr/include/stdio.h&quot; 3 4
extern FILE *fmemopen (void *__s, size_t __len, const char *__modes)
  __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ;




extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __attribute__ ((__nothrow__ , __leaf__))
  __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ;
# 328 &quot;/usr/include/stdio.h&quot; 3 4
extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__));



extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf,
      int __modes, size_t __n) __attribute__ ((__nothrow__ , __leaf__));




extern void setbuffer (FILE *__restrict __stream, char *__restrict __buf,
         size_t __size) __attribute__ ((__nothrow__ , __leaf__));


extern void setlinebuf (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));







extern int fprintf (FILE *__restrict __stream,
      const char *__restrict __format, ...);




extern int printf (const char *__restrict __format, ...);

extern int sprintf (char *__restrict __s,
      const char *__restrict __format, ...) __attribute__ ((__nothrow__));





extern int vfprintf (FILE *__restrict __s, const char *__restrict __format,
       __gnuc_va_list __arg);




extern int vprintf (const char *__restrict __format, __gnuc_va_list __arg);

extern int vsprintf (char *__restrict __s, const char *__restrict __format,
       __gnuc_va_list __arg) __attribute__ ((__nothrow__));



extern int snprintf (char *__restrict __s, size_t __maxlen,
       const char *__restrict __format, ...)
     __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 4)));

extern int vsnprintf (char *__restrict __s, size_t __maxlen,
        const char *__restrict __format, __gnuc_va_list __arg)
     __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 0)));
# 403 &quot;/usr/include/stdio.h&quot; 3 4
extern int vdprintf (int __fd, const char *__restrict __fmt,
       __gnuc_va_list __arg)
     __attribute__ ((__format__ (__printf__, 2, 0)));
extern int dprintf (int __fd, const char *__restrict __fmt, ...)
     __attribute__ ((__format__ (__printf__, 2, 3)));







extern int fscanf (FILE *__restrict __stream,
     const char *__restrict __format, ...) ;




extern int scanf (const char *__restrict __format, ...) ;

extern int sscanf (const char *__restrict __s,
     const char *__restrict __format, ...) __attribute__ ((__nothrow__ , __leaf__));





# 1 &quot;/usr/include/x86_64-linux-gnu/bits/floatn.h&quot; 1 3 4
# 119 &quot;/usr/include/x86_64-linux-gnu/bits/floatn.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/floatn-common.h&quot; 1 3 4
# 24 &quot;/usr/include/x86_64-linux-gnu/bits/floatn-common.h&quot; 3 4
# 1 &quot;/usr/include/x86_64-linux-gnu/bits/long-double.h&quot; 1 3 4
# 25 &quot;/usr/include/x86_64-linux-gnu/bits/floatn-common.h&quot; 2 3 4
# 120 &quot;/usr/include/x86_64-linux-gnu/bits/floatn.h&quot; 2 3 4
# 431 &quot;/usr/include/stdio.h&quot; 2 3 4



extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...) __asm__ (&quot;&quot; &quot;__isoc99_fscanf&quot;)

                               ;
extern int scanf (const char *__restrict __format, ...) __asm__ (&quot;&quot; &quot;__isoc99_scanf&quot;)
                              ;
extern int sscanf (const char *__restrict __s, const char *__restrict __format, ...) __asm__ (&quot;&quot; &quot;__isoc99_sscanf&quot;) __attribute__ ((__nothrow__ , __leaf__))

                      ;
# 459 &quot;/usr/include/stdio.h&quot; 3 4
extern int vfscanf (FILE *__restrict __s, const char *__restrict __format,
      __gnuc_va_list __arg)
     __attribute__ ((__format__ (__scanf__, 2, 0))) ;





extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg)
     __attribute__ ((__format__ (__scanf__, 1, 0))) ;


extern int vsscanf (const char *__restrict __s,
      const char *__restrict __format, __gnuc_va_list __arg)
     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0)));





extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ (&quot;&quot; &quot;__isoc99_vfscanf&quot;)



     __attribute__ ((__format__ (__scanf__, 2, 0))) ;
extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __asm__ (&quot;&quot; &quot;__isoc99_vscanf&quot;)

     __attribute__ ((__format__ (__scanf__, 1, 0))) ;
extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ (&quot;&quot; &quot;__isoc99_vsscanf&quot;) __attribute__ ((__nothrow__ , __leaf__))



     __attribute__ ((__format__ (__scanf__, 2, 0)));
# 513 &quot;/usr/include/stdio.h&quot; 3 4
extern int fgetc (FILE *__stream);
extern int getc (FILE *__stream);





extern int getchar (void);






extern int getc_unlocked (FILE *__stream);
extern int getchar_unlocked (void);
# 538 &quot;/usr/include/stdio.h&quot; 3 4
extern int fgetc_unlocked (FILE *__stream);
# 549 &quot;/usr/include/stdio.h&quot; 3 4
extern int fputc (int __c, FILE *__stream);
extern int putc (int __c, FILE *__stream);





extern int putchar (int __c);
# 565 &quot;/usr/include/stdio.h&quot; 3 4
extern int fputc_unlocked (int __c, FILE *__stream);







extern int putc_unlocked (int __c, FILE *__stream);
extern int putchar_unlocked (int __c);






extern int getw (FILE *__stream);


extern int putw (int __w, FILE *__stream);







extern char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream)
     __attribute__ ((__access__ (__write_only__, 1, 2)));
# 632 &quot;/usr/include/stdio.h&quot; 3 4
extern __ssize_t __getdelim (char **__restrict __lineptr,
                             size_t *__restrict __n, int __delimiter,
                             FILE *__restrict __stream) ;
extern __ssize_t getdelim (char **__restrict __lineptr,
                           size_t *__restrict __n, int __delimiter,
                           FILE *__restrict __stream) ;







extern __ssize_t getline (char **__restrict __lineptr,
                          size_t *__restrict __n,
                          FILE *__restrict __stream) ;







extern int fputs (const char *__restrict __s, FILE *__restrict __stream);





extern int puts (const char *__s);






extern int ungetc (int __c, FILE *__stream);






extern size_t fread (void *__restrict __ptr, size_t __size,
       size_t __n, FILE *__restrict __stream) ;




extern size_t fwrite (const void *__restrict __ptr, size_t __size,
        size_t __n, FILE *__restrict __s);
# 702 &quot;/usr/include/stdio.h&quot; 3 4
extern size_t fread_unlocked (void *__restrict __ptr, size_t __size,
         size_t __n, FILE *__restrict __stream) ;
extern size_t fwrite_unlocked (const void *__restrict __ptr, size_t __size,
          size_t __n, FILE *__restrict __stream);







extern int fseek (FILE *__stream, long int __off, int __whence);




extern long int ftell (FILE *__stream) ;




extern void rewind (FILE *__stream);
# 736 &quot;/usr/include/stdio.h&quot; 3 4
extern int fseeko (FILE *__stream, __off_t __off, int __whence);




extern __off_t ftello (FILE *__stream) ;
# 760 &quot;/usr/include/stdio.h&quot; 3 4
extern int fgetpos (FILE *__restrict __stream, fpos_t *__restrict __pos);




extern int fsetpos (FILE *__stream, const fpos_t *__pos);
# 786 &quot;/usr/include/stdio.h&quot; 3 4
extern void clearerr (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int feof (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern int ferror (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;



extern void clearerr_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int feof_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern int ferror_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;







extern void perror (const char *__s);




extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;




extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
# 823 &quot;/usr/include/stdio.h&quot; 3 4
extern int pclose (FILE *__stream);





extern FILE *popen (const char *__command, const char *__modes)
  __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (pclose, 1))) ;






extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__))
  __attribute__ ((__access__ (__write_only__, 1)));
# 867 &quot;/usr/include/stdio.h&quot; 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));



extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;


extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 885 &quot;/usr/include/stdio.h&quot; 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 902 &quot;/usr/include/stdio.h&quot; 3 4

# 2 &quot;hello.c&quot; 2

# 2 &quot;hello.c&quot;
int main() {
 printf(&quot;hello world!&quot;);
}

&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Combinatorial Counting Basics</title><link>https://nkns.cc/notes/dm/002-combinatorial-counting-basics</link><guid isPermaLink="true">https://nkns.cc/notes/dm/002-combinatorial-counting-basics</guid><description>Records About Counting Rules, Pigeonhole Principle, Permutations, and Combinations</description><pubDate>Thu, 02 Oct 2025 14:23:53 GMT</pubDate><content:encoded>&lt;h1&gt;Part 2 组合计数基础&lt;/h1&gt;
&lt;h2&gt;计数基础&lt;/h2&gt;
&lt;h3&gt;乘法规则 (Multiplication Rule)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：假定一个过程可以分解为两个任务。如果完成第一个任务有 $n_1$ 种方式，在第一个任务完成之后有 $n_2$ 种方式完成第二个任务，那么完成这个过程就有 $n_1 \cdot n_2$ 种方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：长度为七的位串有多少种？
&lt;strong&gt;解答&lt;/strong&gt;：由于每个比特位可以是 0 或 1，所以答案是 $2^7 = 128$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;集合论描述&lt;/strong&gt;：如果 $A_1, A_2, \dots, A_m$ 是有限集，那么这些集合的笛卡尔积中的元素数量是每个集合元素数量的乘积。
$$ |A_1 \times A_2 \times \dots \times A_m| = |A_1| \cdot |A_2| \cdot \dots \cdot |A_m| $$&lt;/p&gt;
&lt;h3&gt;加法规则 (Addition Rule)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：如果完成一项任务有 $n_1$ 种方式或 $n_2$ 种方式，并且 $n_1$ 和 $n_2$ 的方式之间没有重叠，那么完成任务的方式总共有 $n_1 + n_2$ 种。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：数学系选一名代表，有 37 名教师和 83 名学生，没有人既是教师又是学生。
&lt;strong&gt;解答&lt;/strong&gt;：$37 + 83 = 120$ 种。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;集合论描述&lt;/strong&gt;：当 A 和 B 是不相交集合时，$$ |A \cup B| = |A| + |B| $$。&lt;/p&gt;
&lt;h3&gt;减法规则 (Subtraction Rule) / 容斥原理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：如果完成某项任务有 $n_1$ 种方式或 $n_2$ 种方式，那么完成任务的总方式数量为 $n_1 + n_2$ 减去以两类方式中执行这个任务相同的方式。&lt;/p&gt;
&lt;p&gt;$$
|A \cup B| = |A| + |B| - |A \cap B|
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：长度为八的二进制串有多少个要么以 1 开始，要么以 00 结束？
&lt;strong&gt;解答&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;以 1 开头：$2^7 = 128$&lt;/li&gt;
&lt;li&gt;以 00 结尾：$2^6 = 64$&lt;/li&gt;
&lt;li&gt;既以 1 开头又以 00 结尾：$2^5 = 32$&lt;/li&gt;
&lt;li&gt;总数：$128 + 64 - 32 = 160$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;除法规则 (Division Rule)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：如果某项任务可以通过 $n$ 种方式完成，且对于每一种方式 $w$，正好有 $d$ 种方式对应于方式 $w$，那么完成这项任务的方式总数为 $n/d$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：围绕圆桌有多少种不同的方式排列四个人？（旋转视为相同）
&lt;strong&gt;解答&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直线排列有 $4! = 24$ 种。&lt;/li&gt;
&lt;li&gt;每个圆桌排列对应 4 种直线排列（旋转）。&lt;/li&gt;
&lt;li&gt;结果：$24 / 4 = 6$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;鸽巢原理 (Pigeonhole Principle)&lt;/h2&gt;
&lt;h3&gt;基础鸽巢原理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：如果 $k$ 是一个正整数，并且将 $k+1$ 个物体放入 $k$ 个盒子中，那么至少有一个盒子包含 2 个或更多的物体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推论&lt;/strong&gt;：从一个具有 $k+1$ 个元素的集合到一个具有 $k$ 个元素的集合的函数 $f$ 不是一对一函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：在任意 367 人的群体中，必定至少有两人有相同的生日（一年 366 天）。&lt;/p&gt;
&lt;h3&gt;广义鸽巢原理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：如果将 $N$ 个物体放入 $k$ 个盒子中，则至少有一个盒子包含至少 $\lceil N/k \rceil$ 个物体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：从 52 张牌中至少选多少张才能保证有 3 张同花色？
&lt;strong&gt;解答&lt;/strong&gt;：
$k=4$ (花色)，要求 $\lceil N/4 \rceil \ge 3$。
最小的 $N$ 为 $2 \times 4 + 1 = 9$。&lt;/p&gt;
&lt;h2&gt;排列 (Permutations)&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;一组不同对象的排列是这些对象的有序排列。具有 $n$ 个元素的集合的 $r$-排列的数量记为 $P(n,r)$。&lt;/p&gt;
&lt;h3&gt;公式&lt;/h3&gt;
&lt;p&gt;$$
P(n, r) = n(n-1)(n-2)\dots(n-r+1) = \frac{n!}{(n-r)!}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：推销员访问 8 个城市，起点固定，其余 7 个任意顺序。
&lt;strong&gt;解答&lt;/strong&gt;：$P(7,7) = 7! = 5040$。&lt;/p&gt;
&lt;h2&gt;组合 (Combinations)&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;集合元素的一个 $r$-组合是从集合中无序选择 $r$ 个元素。记作 $C(n,r)$ 或 $\binom{n}{r}$。&lt;/p&gt;
&lt;h3&gt;公式与定理&lt;/h3&gt;
&lt;p&gt;$$
C(n, r) = \frac{n!}{r!(n-r)!}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推论&lt;/strong&gt;：$C(n, r) = C(n, n-r)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;组合证明 (Combinatorial Proof)&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;双计数证明&lt;/strong&gt;：证明恒等式两边以不同方式计算相同对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双射证明&lt;/strong&gt;：展示两边计数的对象集合间存在双射。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二项式系数与恒等式&lt;/h2&gt;
&lt;h3&gt;二项式定理 (Binomial Theorem)&lt;/h3&gt;
&lt;p&gt;设 $x$ 和 $y$ 为变量，$n$ 为非负整数。
$$
(x+y)^n = \sum_{j=0}^{n} \binom{n}{j} x^{n-j} y^j
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：求 $(2x - 3y)^{25}$ 中 $x^{12}y^{13}$ 的系数。
&lt;strong&gt;解答&lt;/strong&gt;：令 $j=13$，系数为 $\binom{25}{13} 2^{12} (-3)^{13}$。&lt;/p&gt;
&lt;h3&gt;重要恒等式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;总子集数&lt;/strong&gt;：$\sum_{k=0}^{n} \binom{n}{k} = 2^n$（令 $x=1, y=1$）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;帕斯卡恒等式 (Pascal&apos;s Identity)&lt;/strong&gt;：
$$
\binom{n+1}{k} = \binom{n}{k-1} + \binom{n}{k}
$$
这是帕斯卡三角形及其递推关系的理论基础。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;广义排列和组合&lt;/h2&gt;
&lt;h3&gt;带重复的排列&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：允许重复的情况下，从 $n$ 个对象的集合中选取 $r$ 个对象的排列数为 $n^r$。&lt;/p&gt;
&lt;h3&gt;带重复的组合&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：当允许元素重复时，从一个包含 $n$ 个元素的集合中选择 $r$ 个元素的组合数是
$$ C(n+r-1, r) = C(n+r-1, n-1) $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明思路&lt;/strong&gt;：隔板法（Stars and Bars）。$r$ 个物体（星号）和 $n-1$ 个隔板（竖线）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：方程 $x_1 + x_2 + x_3 = 11$ 的非负整数解的个数。
&lt;strong&gt;解答&lt;/strong&gt;：$n=3, r=11$。$C(3+11-1, 11) = C(13, 11) = C(13, 2) = 78$。&lt;/p&gt;
&lt;h3&gt;具有不可区别物体的排列&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：设类型 1 的相同物体有 $n_1$ 个，...，类型 $k$ 的相同物体有 $n_k$ 个，总数 $n = n_1 + \dots + n_k$。则不同排列数为：
$$
\frac{n!}{n_1! n_2! \dots n_k!}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：重排单词 &quot;SUCCESS&quot;。
&lt;strong&gt;解答&lt;/strong&gt;：总数 7，S 有 3 个，C 有 2 个，U 有 1 个，E 有 1 个。
$$
\frac{7!}{3!2!1!1!} = 420
$$&lt;/p&gt;
&lt;h3&gt;把物体放入盒子&lt;/h3&gt;
&lt;p&gt;| 物体     | 盒子     | 公式/方法                                                    |
| :------- | :------- | :----------------------------------------------------------- |
| 可区别   | 可区别   | $n!/(n_1!n_2!\dots n_k!)$ (指定每个盒子数量) 或 $k^n$ (任意放) |
| 不可区别 | 可区别   | $C(n+r-1, n-1)$ (隔板法)                                     |
| 可区别   | 不可区别 | 无简单公式 (第二类斯特林数相关)                              |
| 不可区别 | 不可区别 | 整数拆分 $p_k(n)$                                            |&lt;/p&gt;
&lt;h2&gt;母函数 (Generating Functions)&lt;/h2&gt;
&lt;h3&gt;普通母函数 (Ordinary Generating Functions)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：对于序列 $a_0, a_1, a_2, \dots$，函数 $G(x) = \sum_{k=0}^{\infty} a_k x^k$ 称为该序列的母函数。
&lt;strong&gt;应用&lt;/strong&gt;：主要用于解决&lt;strong&gt;组合&lt;/strong&gt;问题（选取物品，顺序无关）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：两个骰子掷出 $n$ 点的方法数。
&lt;strong&gt;解答&lt;/strong&gt;：
$$
(x^1 + x^2 + \dots + x^6)(x^1 + x^2 + \dots + x^6)
$$
展开式中 $x^n$ 的系数即为方法数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;整数拆分&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将整数 $N$ 无序拆分成 ${a_1, a_2, \dots, a_n}$ 的和。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不允许重复&lt;/strong&gt;：$G(x) = (1+x^{a_1})(1+x^{a_2})\dots$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;允许重复&lt;/strong&gt;：$G(x) = (1+x^{a_1}+x^{2a_1}+\dots)(1+x^{a_2}+x^{2a_2}+\dots)\dots = \frac{1}{(1-x^{a_1})(1-x^{a_2})\dots}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：整数拆分成不同整数的和的拆分数（不允许重复）等于拆分成奇数的拆分数（允许重复）。&lt;/p&gt;
&lt;h3&gt;指数型母函数 (Exponential Generating Functions)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：对于序列 $a_0, a_1, a_2, \dots$，函数 $G_e(x) = \sum_{k=0}^{\infty} a_k \frac{x^k}{k!}$ 称为该序列的指数型母函数。
&lt;strong&gt;应用&lt;/strong&gt;：主要用于解决&lt;strong&gt;排列&lt;/strong&gt;问题（选取物品，顺序有关）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本构建块&lt;/strong&gt;：
从多重集中取 $r$ 个排列，若某元素限制出现次数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;出现 0 次或 1 次：$(1 + \frac{x}{1!})$&lt;/li&gt;
&lt;li&gt;出现任意次：$(1 + \frac{x}{1!} + \frac{x^2}{2!} + \dots) = e^x$&lt;/li&gt;
&lt;li&gt;出现偶数次：$\frac{e^x + e^{-x}}{2}$&lt;/li&gt;
&lt;li&gt;出现奇数次：$\frac{e^x - e^{-x}}{2}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：由 1, 3, 5, 7, 9 组成的 $n$ 位数，其中 3, 7 出现偶数次，其他不限。
&lt;strong&gt;解答&lt;/strong&gt;：
$$
G_e(x) = \left(\frac{e^x + e^{-x}}{2}\right)^2 (e^x)^3 = \frac{1}{4}(e^{2x} + 2 + e^{-2x})e^{3x} = \frac{1}{4}(e^{5x} + 2e^{3x} + e^x)
$$
展开后 $x^n/n!$ 的系数即为答案：
$$
a_n = \frac{1}{4}(5^n + 2 \cdot 3^n + 1)
$$&lt;/p&gt;</content:encoded></item><item><title>Advanced Counting Techniques</title><link>https://nkns.cc/notes/dm/003-advanced-counting-techniques</link><guid isPermaLink="true">https://nkns.cc/notes/dm/003-advanced-counting-techniques</guid><description>Records About Recurrence Relations, Generating Functions, and Inclusion-Exclusion Principle</description><pubDate>Thu, 02 Oct 2025 14:23:53 GMT</pubDate><content:encoded>&lt;h1&gt;Part 3 高级计数&lt;/h1&gt;
&lt;h2&gt;递推关系的应用&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;数列 ${a_n}$ 的 &lt;strong&gt;递推关系 (Recurrence Relation)&lt;/strong&gt; 是一个方程，它将 $a_n$ 表示为该数列之前的一个或多个项的函数，即 $a_0, a_1, \dots, a_{n-1}$，其中 $n$ 为所有满足 $n \ge n_0$ 的整数，$n_0$ 是一个非负整数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解&lt;/strong&gt;：如果一个数列的各项满足递推关系，则称该数列为递推关系的解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;初始条件&lt;/strong&gt;：数列的初始条件指定了递推关系生效前的各项（例如 $a_0, a_1$ 等）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;经典例子&lt;/h3&gt;
&lt;h4&gt;斐波那契数列 (Fibonacci Sequence)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：一对年轻的兔子（一公一母）被放置在一个岛上。兔子在满两个月前不会繁殖。两个月大后，每对兔子每个月都会产下一对兔子。假设兔子永远不会死亡，求在经过 $n$ 个月后岛上兔子的对数的递归关系。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;递推关系&lt;/strong&gt;：
$$
f_n = f_{n-1} + f_{n-2}, \quad n \ge 3
$$
&lt;strong&gt;初始条件&lt;/strong&gt;：$f_1 = 1, f_2 = 1$。&lt;/p&gt;
&lt;h4&gt;汉诺塔 (Tower of Hanoi)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：将 $n$ 个圆盘从柱子 1 移动到柱子 2，每次只能移动一个圆盘，且大圆盘不能放在小圆盘上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;递推关系&lt;/strong&gt;：
设 $H_n$ 为移动 $n$ 个圆盘所需的最小移动次数。
$$
H_n = 2H_{n-1} + 1
$$
&lt;strong&gt;初始条件&lt;/strong&gt;：$H_1 = 1$。
&lt;strong&gt;显式公式&lt;/strong&gt;：$H_n = 2^n - 1$。&lt;/p&gt;
&lt;h4&gt;位串计数&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：找出长度为 $n$ 的不包含两个连续 0 的位串的数量 $a_n$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;递推关系&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;结尾为 1：前 $n-1$ 位必须合法，数量为 $a_{n-1}$。&lt;/li&gt;
&lt;li&gt;结尾为 0：前一位必须是 1（因为不能有连续 0），再前 $n-2$ 位必须合法，数量为 $a_{n-2}$。
$$
a_n = a_{n-1} + a_{n-2}, \quad n \ge 3
$$
&lt;strong&gt;初始条件&lt;/strong&gt;：$a_1 = 2$ (0, 1), $a_2 = 3$ (01, 10, 11)。
&lt;strong&gt;注意&lt;/strong&gt;：$a_n = f_{n+2}$，其中 $f_n$ 是斐波那契数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;卡塔兰数 (Catalan Numbers)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：$C_n$ 表示对 $n+1$ 个数 $x_0 \cdot x_1 \cdots x_n$ 进行括号化的方法数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;递推关系&lt;/strong&gt;：
$$
C_n = \sum_{k=0}^{n-1} C_k C_{n-k-1}
$$
&lt;strong&gt;初始条件&lt;/strong&gt;：$C_0 = 1, C_1 = 1$。&lt;/p&gt;
&lt;h2&gt;线性递推关系的求解&lt;/h2&gt;
&lt;h3&gt;线性齐次递推关系&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：形式为
$$
a_n = c_1 a_{n-1} + c_2 a_{n-2} + \dots + c_k a_{n-k}
$$
其中 $c_1, c_2, \dots, c_k$ 为常数且 $c_k \ne 0$。&lt;/p&gt;
&lt;h4&gt;求解方法（特征方程法）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;写出特征方程&lt;/strong&gt;：
$$
r^k - c_1 r^{k-1} - c_2 r^{k-2} - \dots - c_k = 0
$$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;求解特征根&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;情况 1：$k$ 个不同的实根 $r_1, r_2, \dots, r_k$&lt;/strong&gt;
通解为：
$$
a_n = \alpha_1 r_1^n + \alpha_2 r_2^n + \dots + \alpha_k r_k^n
$$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;情况 2：有重根&lt;/strong&gt;
如果 $r_1$ 是 $m$ 重根，则它对应的项为：
$$
(\alpha_{1,0} + \alpha_{1,1}n + \dots + \alpha_{1,m-1}n^{m-1}) r_1^n
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代入初始条件&lt;/strong&gt;：解线性方程组求出常数 $\alpha_i$。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：$a_n = a_{n-1} + 2a_{n-2}$，初始条件 $a_0=2, a_1=7$。
特征方程：$r^2 - r - 2 = 0 \Rightarrow (r-2)(r+1)=0 \Rightarrow r_1=2, r_2=-1$。
通解：$a_n = \alpha_1 2^n + \alpha_2 (-1)^n$。
代入初始条件解得 $\alpha_1=3, \alpha_2=-1$。
所以 $a_n = 3 \cdot 2^n - (-1)^n$。&lt;/p&gt;
&lt;h3&gt;线性非齐次递推关系&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：形式为
$$
a_n = c_1 a_{n-1} + c_2 a_{n-2} + \dots + c_k a_{n-k} + F(n)
$$
其中 $F(n)$ 是仅依赖于 $n$ 的函数。&lt;/p&gt;
&lt;h4&gt;求解方法&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：通解由两部分组成：
$$
a_n = a_n^{(h)} + a_n^{(p)}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$a_n^{(h)}$ 是对应的&lt;strong&gt;齐次递推关系&lt;/strong&gt;的通解。&lt;/li&gt;
&lt;li&gt;$a_n^{(p)}$ 是非齐次递推关系的一个&lt;strong&gt;特解&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;常见 $F(n)$ 的特解形式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 $F(n)$ 是 $n$ 的 $t$ 次多项式，且 1 不是特征根，则设特解为 $n$ 的 $t$ 次多项式。&lt;/li&gt;
&lt;li&gt;若 $F(n) = C \cdot s^n$，且 $s$ 不是特征根，则设特解为 $A \cdot s^n$。&lt;/li&gt;
&lt;li&gt;若 $s$ 是 $m$ 重特征根，则特解形式需乘以 $n^m$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：$a_n = 3a_{n-1} + 2n, a_1=3$。
齐次部分 $a_n^{(h)} = \alpha 3^n$。
设特解 $a_n^{(p)} = cn + d$（因为 $F(n)=2n$ 是一次多项式且 1 不是特征根）。
代入原方程解得 $c=-1, d=-3/2$。
通解 $a_n = \alpha 3^n - n - 3/2$。
代入 $a_1=3$ 解得 $\alpha = 11/6$。&lt;/p&gt;
&lt;h2&gt;分治算法与递归关系&lt;/h2&gt;
&lt;h3&gt;分治递归关系&lt;/h3&gt;
&lt;p&gt;设一个递归算法将规模为 $n$ 的问题划分为 $a$ 个子问题，每个子问题规模为 $n/b$，合并步骤需 $g(n)$ 次操作。
$$
f(n) = a f(n/b) + g(n)
$$&lt;/p&gt;
&lt;h3&gt;主定理 (Master Theorem)&lt;/h3&gt;
&lt;p&gt;对于 $f(n) = a f(n/b) + c n^d$，其中 $a \ge 1, b &gt; 1, c &gt; 0, d \ge 0$：&lt;/p&gt;
&lt;p&gt;$$
f(n) \text{ is } \begin{cases}
O(n^d) &amp;#x26; \text{if } a &amp;#x3C; b^d \
O(n^d \log n) &amp;#x26; \text{if } a = b^d \
O(n^{\log_b a}) &amp;#x26; \text{if } a &gt; b^d
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;二分查找&lt;/strong&gt;：$f(n) = f(n/2) + 2$ ($a=1, b=2, d=0$) $\Rightarrow a = b^d \Rightarrow O(\log n)$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;归并排序&lt;/strong&gt;：$M(n) = 2M(n/2) + n$ ($a=2, b=2, d=1$) $\Rightarrow a = b^d \Rightarrow O(n \log n)$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快速整数乘法&lt;/strong&gt;：$f(n) = 3f(n/2) + Cn$ ($a=3, b=2, d=1$) $\Rightarrow a &gt; b^d \Rightarrow O(n^{\log_2 3}) \approx O(n^{1.585})$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;容斥原理 (Inclusion-Exclusion Principle)&lt;/h2&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;对于有限集 $A_1, A_2, \dots, A_n$：
$$
\left| \bigcup_{i=1}^n A_i \right| = \sum_{1 \le i \le n} |A_i| - \sum_{1 \le i &amp;#x3C; j \le n} |A_i \cap A_j| + \sum_{1 \le i &amp;#x3C; j &amp;#x3C; k \le n} |A_i \cap A_j \cap A_k| - \dots + (-1)^{n-1} \left| \bigcap_{i=1}^n A_i \right|
$$&lt;/p&gt;
&lt;h3&gt;应用&lt;/h3&gt;
&lt;h4&gt;Onto (满射) 函数的数量&lt;/h4&gt;
&lt;p&gt;从 $m$ 个元素的集合到 $n$ 个元素的集合的满射函数数量 ($m \ge n$)：
$$
n^m - C(n, 1)(n-1)^m + C(n, 2)(n-2)^m - \dots + (-1)^{n-1} C(n, n-1) \cdot 1^m
$$&lt;/p&gt;
&lt;h4&gt;错位排列 (Derangements)&lt;/h4&gt;
&lt;p&gt;$n$ 个元素的错位排列数 $D_n$（没有任何元素在原始位置的排列）：
$$
D_n = n! \left[ 1 - \frac{1}{1!} + \frac{1}{2!} - \frac{1}{3!} + \dots + (-1)^n \frac{1}{n!} \right]
$$
当 $n \to \infty$ 时，$D_n/n! \to 1/e$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：帽子寄存问题。$n$ 个人取回帽子，没人拿到自己帽子的概率约为 $1/e \approx 0.368$。&lt;/p&gt;</content:encoded></item><item><title>Mathematical Logic</title><link>https://nkns.cc/notes/dm/004-mathematical-logic</link><guid isPermaLink="true">https://nkns.cc/notes/dm/004-mathematical-logic</guid><description>Records About Mathematical Logic, Propositional Logic, Normal Forms</description><pubDate>Thu, 02 Oct 2025 14:23:53 GMT</pubDate><content:encoded>&lt;h1&gt;Part 4 数理逻辑&lt;/h1&gt;
&lt;h2&gt;命题 (Propositions)&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;命题&lt;/strong&gt;是具有确切真值的&lt;strong&gt;陈述句&lt;/strong&gt;。该命题可以取一个“值”，称为真值。
真值只有“真”和“假”两种，分别用 &lt;strong&gt;T (1)&lt;/strong&gt; 和 &lt;strong&gt;F (0)&lt;/strong&gt; 表示。&lt;/p&gt;
&lt;h3&gt;命题的分类&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;原子命题 (简单命题)&lt;/strong&gt;：不能再分解为更为简单命题的命题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复合命题&lt;/strong&gt;：可以分解为更为简单命题的命题。通过关联词（如“或者”、“并且”、“如果...则...”）复合而成。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;是命题&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;太阳是圆的 (T)&lt;/li&gt;
&lt;li&gt;$1+1=10$ (T/F，取决于进制，但在特定语境下有确切真值)&lt;/li&gt;
&lt;li&gt;3能被2整除 (F)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非命题&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;滚出去！ (祈使句)&lt;/li&gt;
&lt;li&gt;你要出去吗？ (疑问句)&lt;/li&gt;
&lt;li&gt;$x+y &gt; 0$ (真值取决于变量，除非指定域)&lt;/li&gt;
&lt;li&gt;本语句是假的 (悖论)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;命题联结词 (Connectives)&lt;/h2&gt;
&lt;h3&gt;1. 否定 (Negation)&lt;/h3&gt;
&lt;p&gt;设 $P$ 是任一命题，复合命题“非 $P$”称为 $P$ 的否定式，记作 $\neg P$。&lt;/p&gt;
&lt;p&gt;$$
\neg P \text{ 为真} \iff P \text{ 为假}
$$&lt;/p&gt;
&lt;h3&gt;2. 合取 (Conjunction)&lt;/h3&gt;
&lt;p&gt;设 $P, Q$ 是任两个命题，复合命题“$P$ 并且 $Q$”称为 $P$ 与 $Q$ 的合取式，记作 $P \wedge Q$。&lt;/p&gt;
&lt;p&gt;$$
P \wedge Q \text{ 为真} \iff P, Q \text{ 同为真}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自然语言对应&lt;/strong&gt;：“既...又...”，“不仅...而且...”，“虽然...但是...”。&lt;/p&gt;
&lt;h3&gt;3. 析取 (Disjunction)&lt;/h3&gt;
&lt;p&gt;设 $P, Q$ 是任两个命题，复合命题“$P$ 或者 $Q$”称为 $P$ 与 $Q$ 的析取式，记作 $P \vee Q$。&lt;/p&gt;
&lt;p&gt;$$
P \vee Q \text{ 为真} \iff P, Q \text{ 中至少有一个为真}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：这是“可兼或”（Inclusive OR）。&lt;/p&gt;
&lt;h3&gt;4. 蕴涵 (Implication)&lt;/h3&gt;
&lt;p&gt;设 $P, Q$ 是任两个命题，复合命题“如果 $P$，则 $Q$”称为 $P$ 与 $Q$ 的蕴涵式，记作 $P \rightarrow Q$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$P$ 称为前件，$Q$ 称为后件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
P \rightarrow Q \text{ 为假} \iff P \text{ 为真且 } Q \text{ 为假}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;善意推定&lt;/strong&gt;：当前件 $P$ 为假时，不管 $Q$ 真假如何，则 $P \rightarrow Q$ 都为真。
&lt;strong&gt;自然语言对应&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“只要 $P$ 就 $Q$” $\Rightarrow P \rightarrow Q$&lt;/li&gt;
&lt;li&gt;“只有 $Q$ 才 $P$” $\Rightarrow P \rightarrow Q$&lt;/li&gt;
&lt;li&gt;“$P$ 仅当 $Q$” $\Rightarrow P \rightarrow Q$&lt;/li&gt;
&lt;li&gt;“除非 $Q$ 否则 $\neg P$” $\Rightarrow \neg Q \rightarrow \neg P$ (即 $P \rightarrow Q$)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. 等价 (Equivalence)&lt;/h3&gt;
&lt;p&gt;设 $P, Q$ 是任两个命题，复合命题“$P$ 当且仅当 $Q$”称为 $P$ 与 $Q$ 的等价式，记作 $P \leftrightarrow Q$。&lt;/p&gt;
&lt;p&gt;$$
P \leftrightarrow Q \text{ 为真} \iff P, Q \text{ 同为真假}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自然语言对应&lt;/strong&gt;：“充分必要条件”。&lt;/p&gt;
&lt;h3&gt;优先级约定&lt;/h3&gt;
&lt;p&gt;$$
\neg \rightarrow \wedge \rightarrow \vee \rightarrow \rightarrow \rightarrow \leftrightarrow
$$
&lt;em&gt;(注：同级符号按从左到右顺序，括号优先级最高)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;命题公式 (Propositional Formula)&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;命题公式是仅由有限步使用规则构成的符号串：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;命题变元本身是一个公式。&lt;/li&gt;
&lt;li&gt;如 $G$ 是公式，则 $(\neg G)$ 也是公式。&lt;/li&gt;
&lt;li&gt;如 $G, H$ 是公式，则 $(G \wedge H), (G \vee H), (G \rightarrow H), (G \leftrightarrow H)$ 也是公式。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解释与真值表&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解释 (Interpretation)&lt;/strong&gt;：指派给公式中所有命题变元的一组真值。对于 $n$ 个变元，有 $2^n$ 个不同的解释。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;真值表 (Truth Table)&lt;/strong&gt;：将公式在所有可能解释下的真值情况列成的表。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;公式的分类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;永真公式 (重言式, Tautology)&lt;/strong&gt;：在所有解释下都为“真”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永假公式 (矛盾式, Contradiction)&lt;/strong&gt;：在所有解释下都为“假”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可满足公式 (Satisfiable)&lt;/strong&gt;：至少存在一个解释使其为“真”（不是永假式）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关系&lt;/strong&gt;：
$$ \text{永真式的否定} \Leftrightarrow \text{矛盾式} $$
$$ \text{矛盾式的否定} \Leftrightarrow \text{永真式} $$&lt;/p&gt;
&lt;h2&gt;逻辑等价与蕴涵&lt;/h2&gt;
&lt;h3&gt;逻辑等价 (Logical Equivalence)&lt;/h3&gt;
&lt;p&gt;如果公式 $G, H$ 在任意解释下真值相同，则称 $G, H$ 是等价的，记作 $G = H$ (或 $G \Leftrightarrow H$)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：
$$ G = H \iff (G \leftrightarrow H) \text{ 是永真公式} $$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：“$=$” 是一种关系，描述两个公式的关系；“$\leftrightarrow$” 是一种逻辑联结词，运算结果仍是公式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;基本等价公式 (24个)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;结合律&lt;/strong&gt;：
$$ G \vee (H \vee S) = (G \vee H) \vee S $$
$$ G \wedge (H \wedge S) = (G \wedge H) \wedge S $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交换律&lt;/strong&gt;：
$$ G \vee H = H \vee G $$
$$ G \wedge H = H \wedge G $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幂等律&lt;/strong&gt;：
$$ G \vee G = G $$
$$ G \wedge G = G $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;吸收律&lt;/strong&gt;：
$$ G \vee (G \wedge H) = G $$
$$ G \wedge (G \vee H) = G $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分配律&lt;/strong&gt;：
$$ G \vee (H \wedge S) = (G \vee H) \wedge (G \vee S) $$
$$ G \wedge (H \vee S) = (G \wedge H) \vee (G \wedge S) $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一律&lt;/strong&gt;：
$$ G \vee 0 = G, \quad G \wedge 1 = G $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零律&lt;/strong&gt;：
$$ G \vee 1 = 1, \quad G \wedge 0 = 0 $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排中律&lt;/strong&gt;：
$$ G \vee \neg G = 1 $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;矛盾律&lt;/strong&gt;：
$$ G \wedge \neg G = 0 $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双重否定律&lt;/strong&gt;：
$$ \neg(\neg G) = G $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;德·摩根定律 (De Morgan&apos;s Laws)&lt;/strong&gt;：
$$ \neg(G \vee H) = \neg G \wedge \neg H $$
$$ \neg(G \wedge H) = \neg G \vee \neg H $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;蕴涵式&lt;/strong&gt;：
$$ G \rightarrow H = \neg G \vee H $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;等价式&lt;/strong&gt;：
$$ G \leftrightarrow H = (G \rightarrow H) \wedge (H \rightarrow G) $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;假言易位&lt;/strong&gt;：
$$ G \rightarrow H = \neg H \rightarrow \neg G $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;归谬论&lt;/strong&gt;：
$$ (G \rightarrow H) \wedge (G \rightarrow \neg H) = \neg G $$&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;代入定理与替换定理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代入定理&lt;/strong&gt;：若 $G$ 是永真式，用任意公式 $H_i$ 替换 $G$ 中出现的原子变元 $P_i$，所得公式仍为永真式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;替换定理&lt;/strong&gt;：若 $G_1$ 是 $G$ 的子公式，且 $G_1 = H_1$，则用 $H_1$ 替换 $G$ 中的 $G_1$ 得到的新公式 $H$ 与 $G$ 等价（即 $G=H$）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;命题逻辑的应用实例&lt;/h2&gt;
&lt;h3&gt;1. 逻辑电路化简&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;例&lt;/strong&gt;：化简电路 $( (P \wedge Q \wedge R) \vee (P \wedge Q \wedge S) ) \wedge ( (P \wedge R) \vee (P \wedge S) )$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解&lt;/strong&gt;：
$$
\begin{aligned}
\text{原式} &amp;#x26;= ( (P \wedge Q) \wedge (R \vee S) ) \wedge ( P \wedge (R \vee S) ) &amp;#x26; (\text{提取公因式}) \
&amp;#x26;= P \wedge Q \wedge (R \vee S) &amp;#x26; (\text{吸收律 } A \wedge B \wedge A = A \wedge B)
\end{aligned}
$$&lt;/p&gt;
&lt;h3&gt;2. 逻辑谜题：骑士与无赖&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;背景&lt;/strong&gt;：骑士只说真话，无赖只说假话。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景 1&lt;/strong&gt;：
A 说：“我们两个都是无赖”。问 A, B 身份。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 A 是骑士 $\Rightarrow$ 话为真 $\Rightarrow$ A, B 都是无赖 $\Rightarrow$ 矛盾（A 既是骑士又是无赖）。&lt;/li&gt;
&lt;li&gt;若 A 是无赖 $\Rightarrow$ 话为假 $\Rightarrow$ “两人都是无赖”为假 $\Rightarrow$ 至少有一人是骑士。因 A 是无赖，故 &lt;strong&gt;B 是骑士&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;场景 2&lt;/strong&gt;：
A 说：“B 在撒谎”。C 说：“B 在撒谎”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;结论：A 和 C 说的话相同，身份并未直接互斥，但 B 和 C 必然身份相反（一个说谎一个没说）。此题需更多信息，但可推断 &lt;strong&gt;B 和 C 身份不同&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 多数表决电路 (飞机复核系统)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：三台计算机 $C_1, C_2, C_3$ 复核飞行计划，采用“少数服从多数”原则。求判断结果 $S$ 的公式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真值表分析&lt;/strong&gt;：
只要有 2 台或 3 台为 1 (真)，则 $S=1$。
即 $(1,1,0), (1,0,1), (0,1,1), (1,1,1)$ 四种情况。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;公式&lt;/strong&gt;：
$$
S = (C_1 \wedge C_2 \wedge \neg C_3) \vee (C_1 \wedge \neg C_2 \wedge C_3) \vee (\neg C_1 \wedge C_2 \wedge C_3) \vee (C_1 \wedge C_2 \wedge C_3)
$$
&lt;strong&gt;化简&lt;/strong&gt;：
$$
S = (C_1 \wedge C_2) \vee (C_1 \wedge C_3) \vee (C_2 \wedge C_3)
$$&lt;/p&gt;
&lt;h2&gt;范式 (Normal Forms)&lt;/h2&gt;
&lt;h3&gt;1. 基本定义&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文字 (Literal)&lt;/strong&gt;：命题变元或其否定（如 $P, \neg P$）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;析取式 (子句)&lt;/strong&gt;：有限个文字的析取（如 $P \vee \neg Q$）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合取式 (短语)&lt;/strong&gt;：有限个文字的合取（如 $P \wedge Q \wedge \neg R$）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 析取范式与合取范式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;析取范式 (DNF)&lt;/strong&gt;：有限个&lt;strong&gt;短语&lt;/strong&gt;的析取。
$$ A_1 \vee A_2 \vee \dots \vee A_n \quad (\text{其中 } A_i \text{ 是合取式}) $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合取范式 (CNF)&lt;/strong&gt;：有限个&lt;strong&gt;子句&lt;/strong&gt;的合取。
$$ B_1 \wedge B_2 \wedge \dots \wedge B_n \quad (\text{其中 } B_i \text{ 是析取式}) $$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：任何命题公式都存在与之等价的析取范式和合取范式。&lt;/p&gt;
&lt;h3&gt;3. 极小项与极大项&lt;/h3&gt;
&lt;p&gt;对于 $n$ 个命题变元：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;极小项 (Minterm, $m_i$)&lt;/strong&gt;：包含全部 $n$ 个变元的&lt;strong&gt;合取式&lt;/strong&gt;，每个变元以原形或否定形式出现一次。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性质&lt;/strong&gt;：每个极小项只有一种赋值使其为真（对应真值表的一行）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编码&lt;/strong&gt;：变元为 1，否定为 0。例如 $P \wedge \neg Q \wedge R \Rightarrow 101 \Rightarrow m_5$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;极大项 (Maxterm, $M_i$)&lt;/strong&gt;：包含全部 $n$ 个变元的&lt;strong&gt;析取式&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性质&lt;/strong&gt;：每个极大项只有一种赋值使其为假。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编码&lt;/strong&gt;：变元为 0，否定为 1。例如 $\neg P \vee Q \vee \neg R \Rightarrow 101 \Rightarrow M_5$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关系&lt;/strong&gt;：
$$ \neg m_i = M_i $$&lt;/p&gt;
&lt;h3&gt;4. 主范式 (Principal Normal Forms)&lt;/h3&gt;
&lt;p&gt;为了解决范式不唯一的问题，引入主范式。&lt;/p&gt;
&lt;h4&gt;主析取范式 (PDNF)&lt;/h4&gt;
&lt;p&gt;由&lt;strong&gt;极小项&lt;/strong&gt;的析取构成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;求法&lt;/strong&gt;：选出真值表中结果为 &lt;strong&gt;T (1)&lt;/strong&gt; 的行，将对应的极小项析取。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;：判断两公式是否等价（主析取范式唯一）；判断是否为永假式（主析取范式为空）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;主合取范式 (PCNF)&lt;/h4&gt;
&lt;p&gt;由&lt;strong&gt;极大项&lt;/strong&gt;的合取构成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;求法&lt;/strong&gt;：选出真值表中结果为 &lt;strong&gt;F (0)&lt;/strong&gt; 的行，将对应的极大项合取。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;：判断是否为永真式（主合取范式为空）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;范式转换算法&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;由 PDNF 求 PCNF&lt;/strong&gt;：
若公式 $G$ 的主析取范式包含极小项下标集合为 $I$，全集为 $U = {0, 1, \dots, 2^n-1}$。
则 $G$ 的主合取范式包含的极大项下标集合为 $U - I$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例&lt;/strong&gt;：
设 $G(P, Q, R)$ 的主析取范式为 $m_0 \vee m_1 \vee m_3 \vee m_4 \vee m_6 \vee m_7$。
则缺少的下标为 ${2, 5}$。
故主合取范式为 $M_2 \wedge M_5$。&lt;/p&gt;
&lt;h3&gt;5. 范式的应用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;判断公式类型&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;永真式&lt;/strong&gt; $\iff$ 主析取范式包含所有 $2^n$ 个极小项 $\iff$ 主合取范式为空。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永假式&lt;/strong&gt; $\iff$ 主析取范式为空 $\iff$ 主合取范式包含所有 $2^n$ 个极大项。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可满足式&lt;/strong&gt; $\iff$ 主析取范式不为空。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;判断等价&lt;/strong&gt;：两个公式等价当且仅当它们具有相同的主范式。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Predicate Logic &amp; Logic Inference</title><link>https://nkns.cc/notes/dm/005-predicate-logic--logic-inference</link><guid isPermaLink="true">https://nkns.cc/notes/dm/005-predicate-logic--logic-inference</guid><description>Records About Predicate Logic and Inference Rules</description><pubDate>Thu, 02 Oct 2025 14:23:53 GMT</pubDate><content:encoded>&lt;h1&gt;Part 5 谓词逻辑与推理理论&lt;/h1&gt;
&lt;h2&gt;谓词逻辑基础&lt;/h2&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;个体词 (Individual)&lt;/strong&gt;：表示研究对象（主语、宾语）。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;常量&lt;/strong&gt;：$a, b, c$（如“苏格拉底”）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变量&lt;/strong&gt;：$x, y, z$（泛指个体）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个体域 (Domain)&lt;/strong&gt;：个体变量的取值范围（常用 $D$ 表示）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;谓词 (Predicate)&lt;/strong&gt;：刻画个体的性质或关系的词。
&lt;ul&gt;
&lt;li&gt;$P(x)$：$x$ 是人。&lt;/li&gt;
&lt;li&gt;$Q(x, y)$：$x$ 喜欢 $y$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$n$ 元谓词&lt;/strong&gt;：描述 $n$ 个体之间的关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;量词 (Quantifiers)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全称量词 (Universal Quantifier, $\forall$)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;$\forall x P(x)$：对个体域中所有的 $x$，$P(x)$ 为真。&lt;/li&gt;
&lt;li&gt;对应自然语言：“所有”、“每一个”、“任意”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存在量词 (Existential Quantifier, $\exists$)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;$\exists x P(x)$：在个体域中存在至少一个 $x$，使得 $P(x)$ 为真。&lt;/li&gt;
&lt;li&gt;对应自然语言：“存在”、“有一些”、“至少一个”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;翻译规则（重要）&lt;/h3&gt;
&lt;p&gt;统一个体域为&lt;strong&gt;全总个体域&lt;/strong&gt;时，需引入特性谓词限定范围：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;全称量词 + 蕴涵&lt;/strong&gt;：$\forall x (M(x) \rightarrow P(x))$
&lt;ul&gt;
&lt;li&gt;“所有的人都是要死的” $\Rightarrow$ 对任意 $x$，如果 $x$ 是人，则 $x$ 会死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误写法&lt;/strong&gt;：$\forall x (M(x) \wedge P(x))$ （意味着宇宙万物都是人且都会死）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存在量词 + 合取&lt;/strong&gt;：$\exists x (M(x) \wedge P(x))$
&lt;ul&gt;
&lt;li&gt;“有些人登上了月球” $\Rightarrow$ 存在 $x$，既是人，又登上了月球。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误写法&lt;/strong&gt;：$\exists x (M(x) \rightarrow P(x))$ （若存在非人的东西，该式自动为真，无法准确表达含义）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;量词的性质与等值式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;量词转换律&lt;/strong&gt;：
$$ \neg (\forall x) P(x) \Leftrightarrow (\exists x) \neg P(x) $$
$$ \neg (\exists x) P(x) \Leftrightarrow (\forall x) \neg P(x) $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;量词分配律&lt;/strong&gt;：
$$ (\forall x)(P(x) \wedge Q(x)) \Leftrightarrow (\forall x)P(x) \wedge (\forall x)Q(x) $$
$$ (\exists x)(P(x) \vee Q(x)) \Leftrightarrow (\exists x)P(x) \vee (\exists x)Q(x) $$
&lt;em&gt;(注意：$\forall$ 对 $\vee$，$\exists$ 对 $\wedge$ 无分配律)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;辖域收缩与扩张&lt;/strong&gt;（$Q$ 中不含 $x$）：
$$ (\forall x)(P(x) \vee Q) \Leftrightarrow (\forall x)P(x) \vee Q $$
$$ (\exists x)(P(x) \wedge Q) \Leftrightarrow (\exists x)P(x) \wedge Q $$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;前束范式 (Prenex Normal Form)&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;所有量词都位于公式最前端，且辖域延伸至公式末端。
形式：$(Q_1 x_1) (Q_2 x_2) \dots (Q_n x_n) M$
其中 $Q_i \in {\forall, \exists}$，$M$ 为不含量词的公式。&lt;/p&gt;
&lt;h3&gt;转换步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;消去 $\rightarrow, \leftrightarrow$。&lt;/li&gt;
&lt;li&gt;利用摩根律将 $\neg$ 内移至原子谓词前。&lt;/li&gt;
&lt;li&gt;利用改名规则避免变元冲突。&lt;/li&gt;
&lt;li&gt;利用量词移动规则将量词提到最前。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：求 $\neg ((\forall x) P(x) \rightarrow (\exists y) Q(y))$ 的前束范式。
$$
\begin{aligned}
\neg (\neg (\forall x) P(x) \vee (\exists y) Q(y)) \quad &amp;#x26; (\text{消去} \rightarrow) \
(\forall x) P(x) \wedge \neg (\exists y) Q(y) \quad &amp;#x26; (\text{摩根律}) \
(\forall x) P(x) \wedge (\forall y) \neg Q(y) \quad &amp;#x26; (\text{量词转换}) \
(\forall x) (\forall y) (P(x) \wedge \neg Q(y)) \quad &amp;#x26; (\text{量词前移})
\end{aligned}
$$&lt;/p&gt;
&lt;h2&gt;推理理论 (Inference Theory)&lt;/h2&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;推理&lt;/strong&gt;：从一组前提 $G_1, G_2, \dots, G_n$ 推出结论 $H$ 的思维过程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有效推理&lt;/strong&gt;：当且仅当 $G_1 \wedge G_2 \wedge \dots \wedge G_n \rightarrow H$ 为&lt;strong&gt;永真式&lt;/strong&gt;。
记作：${G_1, G_2, \dots, G_n} \Rightarrow H$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常用推理规则&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;假言推理 (Modus Ponens)&lt;/strong&gt;: $P, P \rightarrow Q \Rightarrow Q$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拒取式 (Modus Tollens)&lt;/strong&gt;: $\neg Q, P \rightarrow Q \Rightarrow \neg P$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;假言三段论&lt;/strong&gt;: $P \rightarrow Q, Q \rightarrow R \Rightarrow P \rightarrow R$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;析取三段论&lt;/strong&gt;: $P \vee Q, \neg P \Rightarrow Q$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;化简律&lt;/strong&gt;: $P \wedge Q \Rightarrow P$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;附加律&lt;/strong&gt;: $P \Rightarrow P \vee Q$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合取引入&lt;/strong&gt;: $P, Q \Rightarrow P \wedge Q$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二难推论&lt;/strong&gt;: $P \vee Q, P \rightarrow R, Q \rightarrow R \Rightarrow R$&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;谓词逻辑推理规则&lt;/h3&gt;
&lt;p&gt;除了上述命题逻辑规则外，增加四个量词规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;全称特指 (US)&lt;/strong&gt;: $(\forall x) P(x) \Rightarrow P(c)$ （$c$ 为任意个体或特定常量）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全称推广 (UG)&lt;/strong&gt;: $P(x) \Rightarrow (\forall x) P(x)$ （$x$ 必须是任意的，不能受限）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存在特指 (ES)&lt;/strong&gt;: $(\exists x) P(x) \Rightarrow P(c)$ （$c$ 必须是&lt;strong&gt;新&lt;/strong&gt;的常量，之前未出现过）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存在推广 (EG)&lt;/strong&gt;: $P(c) \Rightarrow (\exists x) P(x)$。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;推理证明方法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;直接证明法&lt;/strong&gt;：由前提利用推理规则推导至结论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;间接证明法 (反证法)&lt;/strong&gt;：假设结论的否定 $\neg H$ 成立，将其加入前提集合，推导出矛盾（如 $R \wedge \neg R$）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;附加前提证明法 (CP规则)&lt;/strong&gt;：若结论是 $P \rightarrow Q$ 形式，可将 $P$ 作为附加前提，只需证明 $Q$ 成立。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;例题&lt;/strong&gt;：
前提：$\forall x (P(x) \rightarrow Q(x)), \forall x (Q(x) \rightarrow R(x))$
结论：$\forall x (P(x) \rightarrow R(x))$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\forall x (P(x) \rightarrow Q(x))$ (前提)&lt;/li&gt;
&lt;li&gt;$P(y) \rightarrow Q(y)$ (US, 1)&lt;/li&gt;
&lt;li&gt;$\forall x (Q(x) \rightarrow R(x))$ (前提)&lt;/li&gt;
&lt;li&gt;$Q(y) \rightarrow R(y)$ (US, 3)&lt;/li&gt;
&lt;li&gt;$P(y) \rightarrow R(y)$ (假言三段论, 2, 4)&lt;/li&gt;
&lt;li&gt;$\forall x (P(x) \rightarrow R(x))$ (UG, 5)&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Logic Inference &amp; Proof Techniques</title><link>https://nkns.cc/notes/dm/006-logic-inference--proof-techniques</link><guid isPermaLink="true">https://nkns.cc/notes/dm/006-logic-inference--proof-techniques</guid><description>Records About Propositional &amp; Predicate Logic Inference, Rules, and Proof Methods</description><pubDate>Thu, 02 Oct 2025 14:23:53 GMT</pubDate><content:encoded>&lt;h1&gt;Part 6 推理与证明技术&lt;/h1&gt;
&lt;h2&gt;推理的基本概念&lt;/h2&gt;
&lt;h3&gt;定义与有效性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;推理&lt;/strong&gt;：从一组&lt;strong&gt;前提&lt;/strong&gt; (Premises) $G_1, G_2, \dots, G_n$ 推出&lt;strong&gt;结论&lt;/strong&gt; (Conclusion) $H$ 的思维过程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逻辑蕴涵&lt;/strong&gt;：如果对于任意解释 $I$，当 $I$ 满足所有前提时，必然满足结论 $H$，则称 $H$ 是前提的逻辑结果。
&lt;ul&gt;
&lt;li&gt;记作：$G_1, G_2, \dots, G_n \Rightarrow H$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有效推理判定定理&lt;/strong&gt;：
$$ G_1, G_2, \dots, G_n \Rightarrow H \iff (G_1 \wedge G_2 \wedge \dots \wedge G_n) \rightarrow H \text{ 为永真式} $$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;$\rightarrow$ 与 $\Rightarrow$ 的区别&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;$\rightarrow$ (蕴涵)&lt;/strong&gt;：逻辑联结词，运算结果是一个&lt;strong&gt;公式&lt;/strong&gt;，有真假之分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$\Rightarrow$ (推出)&lt;/strong&gt;：元语言符号，描述两个公式（或一组公式与一个公式）之间的&lt;strong&gt;推导关系&lt;/strong&gt;，表示推理是&lt;strong&gt;有效&lt;/strong&gt;的。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;命题逻辑推理理论&lt;/h2&gt;
&lt;h3&gt;常用推理定律 (Inference Rules)&lt;/h3&gt;
&lt;p&gt;熟记这些规则是构造证明的基础。&lt;/p&gt;
&lt;p&gt;| 规则名称                                       | 形式                                                         | 备注                 |
| :--------------------------------------------- | :----------------------------------------------------------- | :------------------- |
| &lt;strong&gt;I1 简化规则&lt;/strong&gt; (Simplification)               | $G \wedge H \Rightarrow G$                                   | 从合取式中析取分量   |
| &lt;strong&gt;I2 添加规则&lt;/strong&gt; (Addition)                     | $G \Rightarrow G \vee H$                                     | 引入新的析取项       |
| &lt;strong&gt;I3/I12 假言推理&lt;/strong&gt; (Modus Ponens)             | $G, G \rightarrow H \Rightarrow H$                           | &lt;strong&gt;最常用&lt;/strong&gt;，肯定前件 |
| &lt;strong&gt;I4/I13 拒取式&lt;/strong&gt; (Modus Tollens)              | $\neg H, G \rightarrow H \Rightarrow \neg G$                 | 否定后件             |
| &lt;strong&gt;I5/I14 假言三段论&lt;/strong&gt; (Hypothetical Syllogism) | $G \rightarrow H, H \rightarrow I \Rightarrow G \rightarrow I$ | 链式推导             |
| &lt;strong&gt;I6/I10 选言三段论&lt;/strong&gt; (Disjunctive Syllogism)  | $\neg G, G \vee H \Rightarrow H$                             | 排除法               |
| &lt;strong&gt;I9 合取引入&lt;/strong&gt; (Conjunction)                  | $G, H \Rightarrow G \wedge H$                                | 组合前提             |
| &lt;strong&gt;I15 二难推论&lt;/strong&gt; (Dilemma)                     | $G \vee H, G \rightarrow I, H \rightarrow I \Rightarrow I$   | 分情况讨论           |&lt;/p&gt;
&lt;h3&gt;推理实例&lt;/h3&gt;
&lt;h4&gt;案例：凶手推理&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;前提&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;凶手是王某或陈某 ($P \vee Q$)&lt;/li&gt;
&lt;li&gt;若王某是凶手，案发时他必外出 ($P \rightarrow R$)&lt;/li&gt;
&lt;li&gt;王某案发时未外出 ($\neg R$)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：陈某是凶手 ($Q$)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$P \rightarrow R$ (前提)&lt;/li&gt;
&lt;li&gt;$\neg R$ (前提)&lt;/li&gt;
&lt;li&gt;$\neg P$ (拒取式, 1, 2)&lt;/li&gt;
&lt;li&gt;$P \vee Q$ (前提)&lt;/li&gt;
&lt;li&gt;$Q$ (选言三段论, 3, 4) $\blacksquare$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;谓词逻辑推理理论&lt;/h2&gt;
&lt;p&gt;在命题逻辑规则基础上，增加对量词的处理规则。&lt;/p&gt;
&lt;h3&gt;量词推理规则&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全称特指 (US, Universal Specification)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(\forall x)P(x) \Rightarrow P(c)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意&lt;/strong&gt;：$c$ 可以是任意个体常量或特定对象。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;含义&lt;/em&gt;：既然对所有都成立，那么对具体的某一个也成立。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全称推广 (UG, Universal Generalization)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$P(x) \Rightarrow (\forall x)P(x)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制&lt;/strong&gt;：$x$ 必须是&lt;strong&gt;任意&lt;/strong&gt;选取的，不能由存在量词特指而来，也不能在前提中受限。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;存在特指 (ES, Existential Specification)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(\exists x)P(x) \Rightarrow P(c)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制&lt;/strong&gt;：$c$ 必须是&lt;strong&gt;新&lt;/strong&gt;的常量（之前未在推理中出现过），表示“存在这么一个特定的个体”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;存在推广 (EG, Existential Generalization)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$P(c) \Rightarrow (\exists x)P(x)$&lt;/li&gt;
&lt;li&gt;&lt;em&gt;含义&lt;/em&gt;：既然找到了一个 $c$ 满足 $P$，那么存在 $x$ 满足 $P$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;推理示例：苏格拉底三段论&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;前提&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\forall x (Man(x) \rightarrow Mortal(x))$ (所有人都要死)&lt;/li&gt;
&lt;li&gt;$Man(Socrates)$ (苏格拉底是人)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：$Mortal(Socrates)$ (苏格拉底要死)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\forall x (Man(x) \rightarrow Mortal(x))$ (前提)&lt;/li&gt;
&lt;li&gt;$Man(Socrates) \rightarrow Mortal(Socrates)$ (US, 1) —— &lt;em&gt;将 x 特指为苏格拉底&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;$Man(Socrates)$ (前提)&lt;/li&gt;
&lt;li&gt;$Mortal(Socrates)$ (假言推理, 2, 3) $\blacksquare$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;证明方法&lt;/h2&gt;
&lt;h3&gt;直接证明法 (Direct Proof)&lt;/h3&gt;
&lt;p&gt;由前提出发，利用推理规则和等价变换，一步步推导至结论。&lt;/p&gt;
&lt;h3&gt;间接证明法 / 反证法 (Indirect Proof)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;思路&lt;/strong&gt;：将结论的否定 $\neg H$ 加入前提集合，如果推导出矛盾（如 $R \wedge \neg R$ 或 $0$），则原结论 $H$ 成立。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;依据&lt;/strong&gt;：$(G_1 \wedge \dots \wedge G_n \wedge \neg H) \rightarrow 0 \iff (G_1 \wedge \dots \wedge G_n) \rightarrow H$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：证明 $P \rightarrow Q, \neg Q \Rightarrow \neg P$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;假设 $\neg(\neg P)$ 即 $P$ (附加前提)&lt;/li&gt;
&lt;li&gt;$P \rightarrow Q$ (前提)&lt;/li&gt;
&lt;li&gt;$Q$ (假言推理, 1, 2)&lt;/li&gt;
&lt;li&gt;$\neg Q$ (前提)&lt;/li&gt;
&lt;li&gt;$Q \wedge \neg Q$ (矛盾) $\Rightarrow$ 假设错误，$\neg P$ 成立。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;附加前提证明法 (CP 规则)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：结论是蕴涵式 $P \rightarrow Q$。
&lt;strong&gt;思路&lt;/strong&gt;：将前件 $P$ 作为&lt;strong&gt;附加前提&lt;/strong&gt;放入前提集合中，只需证明后件 $Q$ 成立即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;依据&lt;/strong&gt;：$(G_1 \wedge \dots \wedge G_n) \rightarrow (P \rightarrow Q) \iff (G_1 \wedge \dots \wedge G_n \wedge P) \rightarrow Q$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：证明 $(P \rightarrow Q) \wedge (Q \rightarrow R) \Rightarrow P \rightarrow R$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$P$ (附加前提，结论的前件)&lt;/li&gt;
&lt;li&gt;$P \rightarrow Q$ (前提)&lt;/li&gt;
&lt;li&gt;$Q$ (假言推理, 1, 2)&lt;/li&gt;
&lt;li&gt;$Q \rightarrow R$ (前提)&lt;/li&gt;
&lt;li&gt;$R$ (假言推理, 3, 4)&lt;/li&gt;
&lt;li&gt;得证 $P \rightarrow R$ (CP 规则) $\blacksquare$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;易错点总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;量词规则的限制&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;使用 &lt;strong&gt;ES (存在特指)&lt;/strong&gt; 时，必须使用从未出现过的符号（例如 $P(c)$，不能用已知的 $a$）。&lt;/li&gt;
&lt;li&gt;使用 &lt;strong&gt;UG (全称推广)&lt;/strong&gt; 时，变量必须是任意的，不能依赖于特定的假设（如 ES 得到的变量）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优先级&lt;/strong&gt;：在混合命题和谓词的推理中，先处理量词（去掉量词），在命题逻辑层面进行推理，最后再加回量词（如果需要）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有效性与真实性&lt;/strong&gt;：推理有效仅保证形式正确（Truth-preserving），如果前提本身为假，结论的真实性无法保证（但推理过程依然是有效的）。&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Overture of Discrete Mathmatics</title><link>https://nkns.cc/notes/dm/discrete-mathmatics</link><guid isPermaLink="true">https://nkns.cc/notes/dm/discrete-mathmatics</guid><description>Ode to the base</description><pubDate>Thu, 02 Oct 2025 14:23:51 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;这是一份考试前临时赶工出来的离散数学笔记。&lt;/p&gt;
&lt;p&gt;前言什么的等我复习完再说吧！！&lt;/p&gt;</content:encoded></item><item><title>Chapter II</title><link>https://nkns.cc/notes/csapp/chapter_ii</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_ii</guid><description>CSAPP NOTE CHAP II</description><pubDate>Wed, 01 Oct 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 2 数据的机器级表示和处理&lt;/h1&gt;
&lt;h2&gt;编码和数制&lt;/h2&gt;
&lt;h3&gt;计算机系统层次中的编码任务&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022222836429.png&quot; alt=&quot;image-20251022222836429&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在不同的层次中实现各自的编码&lt;/li&gt;
&lt;li&gt;在不同的层次间实现编码的映射&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;例如&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C 语言中有 &lt;code&gt;short&lt;/code&gt; &lt;code&gt;int&lt;/code&gt; 等类型&lt;/li&gt;
&lt;li&gt;汇编语言 (ISA 层) 中有 &lt;code&gt;BYTE&lt;/code&gt; &lt;code&gt;WORD&lt;/code&gt; &lt;code&gt;DWORD&lt;/code&gt; 等类型。每种类型只考虑编码长短。&lt;/li&gt;
&lt;li&gt;可以采用补码、原码等编码，将高级语言中的有符号整数，映射为 ISA 层中指定长度的码点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;外部信息与内部数据的转换&lt;/h3&gt;
&lt;p&gt;从不同的角度和层次来看，数据有不同的形态。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251023120559036.png&quot; alt=&quot;image-20251023120559036&quot;&gt;&lt;/p&gt;
&lt;h3&gt;码点和码点空间&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;数据存储的单位和宽度&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bit 0 或 1&lt;/li&gt;
&lt;li&gt;byte 8 位一个字节，寻址单位&lt;/li&gt;
&lt;li&gt;word 2 个 byte&lt;/li&gt;
&lt;li&gt;dword 2 个 word&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储容量单位&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;KB：2^10 字节&lt;/li&gt;
&lt;li&gt;MB：2^20 字节&lt;/li&gt;
&lt;li&gt;GB：2^30 字节&lt;/li&gt;
&lt;li&gt;TB：2^40 字节&lt;/li&gt;
&lt;li&gt;PB：2^50 字节&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;码点空间&lt;/p&gt;
&lt;p&gt;针对一个位数为 n 的存储单元，它的每一种状态共同组合成了它的码点空间&lt;/p&gt;
&lt;p&gt;n bit 的存储，提供 $$ 2^n $$ 种不同的状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个 16 位的存储单元，它的码点空间为：&lt;/p&gt;
&lt;p&gt;| 二进制              | 十六进制 | 十进制 |
| ------------------- | -------- | ------ |
| 0000 0000 0000 0000 | 0000H    | 0      |
| 0000 0000 0000 0001 | 0001H    | 1      |
| ……                  | ……       |        |
| 1010 1011 1012 1013 | ABCDH    | 43981  |
| ……                  |          |        |
| 1111 1111 1111 1111 | FFFFH    | 65536  |&lt;/p&gt;
&lt;p&gt;从ISA的层次来理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;存储单元的“码点”的编码，只是为了区分存储单元的不同状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储单元的“码点”的编码，不具有语义上的含义。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储单元的“码点”，需要从软件层次出发，可以和上层软件编程中的语义数值进行一一对应。例如一个“码点”，进行对应后，可以表示一个整数、一个正数、一个负数、一个浮点数、或一个字符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对存储单元的语义的指定，是依靠在指令中，使用不同的操作符实现的。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IMUL AX; 有符号乘法，将AX看作有符号数&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;MUL AX； 无符号乘法，将AX看作无符号数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;编码方案&lt;/h3&gt;
&lt;p&gt;将语义数据和“码点”进行映射，即对数据进行编码。有许多种不同编码方案。这里不列举。&lt;/p&gt;
&lt;h2&gt;数值数据的机内表示&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251023165454595.png&quot; alt=&quot;image-20251023165454595&quot;&gt;&lt;/p&gt;
&lt;h3&gt;有符号整数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一个包含正负整数的集合，例如 {-128, -127, ..., 0, ...,127}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一般用补码表示&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;整数的补码：它本身的二进制数。&lt;/p&gt;
&lt;p&gt;例如：45 用 8 位补码表示： &lt;code&gt;0010 1101&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;负数的补码：&lt;/p&gt;
&lt;p&gt;负数 X 的补码 = 2n - |X|，其中 n 位字长。（最高位为 1 表示负数）&lt;/p&gt;
&lt;p&gt;如：n = 8 时，-45 的补码为：&lt;code&gt;1101 0011&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;简单方法：X 的补码 = |X| 的二进制数取反 + 1&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;补码的表示范围&lt;/p&gt;
&lt;p&gt;字长为 n 的补码表示范围：&lt;/p&gt;
&lt;p&gt;-2^(n-1) ~ +(2^(n-1) - 1)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是因为补码结构类似：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0000 0000 (0) -&gt; 7FFF FFFF (2147483647) -&gt; 8000 0000 (-2147483648) -&gt; FFFF FFFF (-1)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;补码的运算特点&lt;/p&gt;
&lt;p&gt;[m + n]_ 补 = [m]_ 补 + [n]_ 补&lt;/p&gt;
&lt;p&gt;相反数的补码为 -n 的补码的补码&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;无符号整数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一个只包含非负整数的集合&lt;/li&gt;
&lt;li&gt;用原码表示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;字长为 n 的原码表示范围：0 ~ 2^(n) - 1&lt;/p&gt;
&lt;h3&gt;浮点数&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251023170450031.png&quot; alt=&quot;image-20251023170450031&quot;&gt;&lt;/p&gt;
&lt;p&gt;这是浮点数的基本表示，下面进行详细讲解。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251023170458656.png&quot; alt=&quot;image-20251023170458656&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;符号位：0 表示正数，1 表示负数&lt;/li&gt;
&lt;li&gt;指数：一个字节用来表示指数&lt;/li&gt;
&lt;li&gt;指数是采用的“移码”来表示的：n - 127&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;| 指数码点   | 指数值                                                       |
| ---------- | ------------------------------------------------------------ |
| 0000  0000 | 特殊值，尾数为0时表示0；否则表示一个非规格化的小数（ 2-126(0.f)） |
| 0000  0001 | -126                                                         |
| n          | n-  127                                                      |
| 1111  1110 | +127                                                         |
| 1111  1111 | 特殊值，尾数为0时表示¥；否则表示NaN                          |&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;尾数：规格化数据中，尾数值为 1.XXXX&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​	例：&lt;/p&gt;
&lt;p&gt;​		float d1 = -0.75；&lt;/p&gt;
&lt;p&gt;​	(-0.75) = -0.11B = - 1.1B * 2^-1&lt;/p&gt;
&lt;p&gt;​	符号位：1&lt;/p&gt;
&lt;p&gt;​	指数：n – 127 = -1， n = 126 = 0111 1110B&lt;/p&gt;
&lt;p&gt;​	尾数：1B&lt;/p&gt;
&lt;p&gt;​	d1的二进制表示：1 0111 1110 1000 0000 0000 0000 0000 000B&lt;/p&gt;
&lt;p&gt;​	对应的4字节内容：BF F4 00 00 00 00 00 00H&lt;/p&gt;
&lt;h3&gt;十进制数（不考，看个乐子）&lt;/h3&gt;
&lt;p&gt;用二进制数来表示十进制数，80x86 提供直接处理 BCD 码的指令。&lt;/p&gt;
&lt;p&gt;如：		98 = 1001 1000BCD&lt;/p&gt;
&lt;p&gt;压缩 BCD码	9781 = 1001 0111 1000 0001&lt;/p&gt;
&lt;p&gt;非压缩BCD码	9781 = 00001001 00000111 00001000 00000001&lt;/p&gt;
&lt;h2&gt;字符数据的机内表示&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;字符编码：字符集到“码点空间”的映射&lt;/li&gt;
&lt;li&gt;西文字母采用 ASCII 码（老生常谈了属于是）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251023171024701.png&quot; alt=&quot;image-20251023171024701&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;汉字字符一般用 Unicode 或者 GBK&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;字符串的存放&lt;/h3&gt;
&lt;p&gt;以下是以字符串 &quot;1234ABCD&quot; 为例的存放结构&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251023171122380.png&quot; alt=&quot;image-20251023171122380&quot;&gt;&lt;/p&gt;</content:encoded></item><item><title>Post-001</title><link>https://nkns.cc/notes/blog/post-001</link><guid isPermaLink="true">https://nkns.cc/notes/blog/post-001</guid><description>建站的每一步</description><pubDate>Tue, 30 Sep 2025 16:59:19 GMT</pubDate><content:encoded>&lt;p&gt;这段时间对博客进行了重构，将博客从原先的 &lt;code&gt;Hexo-Butterfly&lt;/code&gt; 迁移到了 &lt;code&gt;astro-pure-lab&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;准备在这个 post 里面记录一下截止到 9 月 30 号建站的历程。&lt;/p&gt;</content:encoded></item><item><title>Chapter I</title><link>https://nkns.cc/notes/csapp/chapter_i</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/chapter_i</guid><description>CSAPP NOTE CHAP 1</description><pubDate>Tue, 30 Sep 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;Chap 1 计算机系统概述&lt;/h1&gt;
&lt;h2&gt;计算机的发展历程&lt;/h2&gt;
&lt;p&gt;了解即可，略去。&lt;/p&gt;
&lt;h2&gt;计算机系统的基本组成和功能&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;计算机是一种能&lt;strong&gt;自动&lt;/strong&gt;对&lt;strong&gt;数字&lt;/strong&gt;化信息进行算术和逻辑运算的高速处理装置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;基本功能&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据处理
&lt;ul&gt;
&lt;li&gt;加、减、乘、除&lt;/li&gt;
&lt;li&gt;与、或、非&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据存储
&lt;ul&gt;
&lt;li&gt;程序&lt;/li&gt;
&lt;li&gt;数据&lt;/li&gt;
&lt;li&gt;存储部件：&lt;strong&gt;非易失存储器&lt;/strong&gt;和&lt;strong&gt;快速存储器&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据传送&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;硬件组成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;冯诺依曼结构&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;硬件组成&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022173140312.png&quot; alt=&quot;image-20251022173140312&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里还有简化版：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022173159705.png&quot; alt=&quot;image-20251022173159705&quot;&gt;&lt;/p&gt;
&lt;p&gt;另外谈到 &lt;strong&gt;CPU&lt;/strong&gt; 和 &lt;strong&gt;主存&lt;/strong&gt;，这两个是非常重要的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022173241873.png&quot; alt=&quot;image-20251022173241873&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022173245048.png&quot; alt=&quot;image-20251022173245048&quot;&gt;&lt;/p&gt;
&lt;h3&gt;CPU 结构&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022173335723.png&quot; alt=&quot;image-20251022173335723&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;总线接口部件&lt;/strong&gt;：是 CPU 与整个计算机系统之间的高速接口
&lt;ul&gt;
&lt;li&gt;功能：接受所有的总线操作请求，并按优先权进行选择，最大限度的利用本身的资源为这些请求服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行部件&lt;/strong&gt;：寄存器组、标志寄存器、算逻部件、控制部件等组成
&lt;ul&gt;
&lt;li&gt;功能：从译码指令队列中取出指令并且执行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;寄存器组&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022173417376.png&quot; alt=&quot;image-20251022173417376&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据寄存器组&lt;/strong&gt;：&lt;code&gt;eax&lt;/code&gt; &lt;code&gt;ebx&lt;/code&gt; &lt;code&gt;ecx&lt;/code&gt; &lt;code&gt;edx&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用来保存操作数、元素按结果或作指示器、变址寄存器等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;指示器变址寄存器组&lt;/strong&gt;：&lt;code&gt;esi&lt;/code&gt; &lt;code&gt;edi&lt;/code&gt; &lt;code&gt;esp&lt;/code&gt; &lt;code&gt;ebp&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作用：用来存放操作数的偏移地址、用作指示器或变址寄存器&lt;/li&gt;
&lt;li&gt;ESI 源变址寄存器 字符串指令源操作数的指示器&lt;/li&gt;
&lt;li&gt;EDI 目的变址寄存器 字符串指令目的操作数的指示器&lt;/li&gt;
&lt;li&gt;ESP 堆栈指示器（栈顶、低地址）&lt;/li&gt;
&lt;li&gt;EBP 基址寄存器（栈底、高地址）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;标志寄存器&lt;/strong&gt; 保存一条指令执行后，CPU所处状态的信息&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022184503972.png&quot; alt=&quot;image-20251022184503972&quot;&gt;&lt;/p&gt;
&lt;p&gt;其中分为 &lt;strong&gt;条件标志&lt;/strong&gt; 和 &lt;strong&gt;控制标志&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;条件标志（常用！）&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;OF 溢出 Overflow&lt;/li&gt;
&lt;li&gt;SF 符号 Signed&lt;/li&gt;
&lt;li&gt;ZF 零标志 Zero&lt;/li&gt;
&lt;li&gt;CF 进位标志 Carry&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;控制标志（这门课不用）
&lt;ul&gt;
&lt;li&gt;DF 方向标志&lt;/li&gt;
&lt;li&gt;IF 中断允许标志&lt;/li&gt;
&lt;li&gt;TF 陷阱标志（单步执行）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;指令预取部件和指令译码部件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;指令预取部件：通过总线接口部件，把将要执行的指令从主存中取出，送入指令排队机构中排队。&lt;/li&gt;
&lt;li&gt;指令译码部件：从指令预取部件中读出指令并译码，在送入译码指令队列排队供执行部件使用。&lt;/li&gt;
&lt;li&gt;指令指示器EIP：总是保存下一条将要被 CPU 执行的指令的&lt;strong&gt;偏移地址&lt;/strong&gt;，其值为&lt;strong&gt;该指令到所在段首址的字节距离&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分段部件和分页部件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用分段部件和分页部件实现虚拟存储空间映射到物理存储空间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;程序员使用二维地址 “段地址：段内偏移地址” （即先根据代码、数据、堆栈分段，调用的时候再在对应段内使用偏移地址寻找）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分段部件把二维地址变成一维线性地址，然后分页部件再把这些地址转换到存储器的实际物理地址&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码段寄存器 CS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;堆栈段寄存器 SS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据段寄存器 DS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;附加段寄存器 ES&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;附加段寄存器 FS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;附加段寄存器 GS&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;软件开发和执行过程概述&lt;/h2&gt;
&lt;h3&gt;从源程序到执行程序&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022185924235.png&quot; alt=&quot;image-20251022185924235&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;源程序：文本文件，用户编写。
&lt;ul&gt;
&lt;li&gt;*.c/.cpp/.asm&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;目标程序：二进制文件，机器可识别，但不能执行
&lt;ul&gt;
&lt;li&gt;*.o( *.obj)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;可执行程序：二进制文件，机器可执行
&lt;ul&gt;
&lt;li&gt;*/ *.exe&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;编译器：将源程序转为目标程序&lt;/li&gt;
&lt;li&gt;链接器：将一个或多个目标程序链接生成可执行程序&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;可执行程序的启动和执行&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022193435775.png&quot; alt=&quot;image-20251022193435775&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从硬盘上将可执行程序的文件加载到内存中&lt;/li&gt;
&lt;li&gt;将 EIP 指向程序第一条指令&lt;/li&gt;
&lt;li&gt;CPU 开始执行程序&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;程序中每条指令的执行&lt;/h3&gt;
&lt;p&gt;机器指令的一般形式为 &lt;strong&gt;操作码	地址码&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;机器指令完成开头说的某一个基本操作。&lt;/p&gt;
&lt;p&gt;每条指令的执行过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从内存中读取该指令&lt;/li&gt;
&lt;li&gt;对指令进行译码&lt;/li&gt;
&lt;li&gt;若为内存操作数，从内存中取操作数&lt;/li&gt;
&lt;li&gt;对操作数进行运算&lt;/li&gt;
&lt;li&gt;保存运算结果。若为内存操作数，保存结果到内存中&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;一个汇编语言程序的例子&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-assembly&quot;&gt;.386
;堆栈段 
STACK SEGMENT USE16 STACK	;段名和组合类型
      DB 200 DUP(0)	;  堆栈的大小为200个字节
STACK ENDS
;数据段
DATA	SEGMENT USE16	;段为16位段
SUM  DW  ? ;SUM为字变量，初值不定
DATA	ENDS
;代码段
CODE	SEGMENT USE16
	ASSUME  CS:CODE, SS:STACK, DS:DATA, ES:DATA
START: MOV  AX, DATA
MOV  DS, AX;数据段首址送DS
MOV  CX, 50;循环计数器置初值
MOV  AX, 0	;累加器置初值
MOV  BX, 1	;1→BX
NEXT:ADD  AX, BX;(AX)+(BX)→AX
INC  BX
INC  BX	;(BX)+2→BX
DEC  CX	;(CX)-1→CX
JNE   NEXT	;(CX)≠0转NEXT
MOV  SUM, AX	;(CX)=0累加结果→SUM
MOV  AH, 4CH
INT  21H	;返回DOS
CODE ENDS
END  START ;源程序结束语句。程序运行时，启动地址为START
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在该例子中，一共定义了三个段：&lt;strong&gt;堆栈段&lt;/strong&gt;、&lt;strong&gt;数据段&lt;/strong&gt;和&lt;strong&gt;代码段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;汇编语句的一般格式：[NAME] 操作符 [操作数/ADDRESS] [COMMENT]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;操作符可分为 3 类：指令、伪指令、宏&lt;/li&gt;
&lt;li&gt;操作数也分为 3 类：数值操作数、寄存器操作数、内存操作数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;汇编过程及 Turbo debugger 的使用略过，因为这门课程使用的平台基本上是 Linux + gcc + gdb&lt;/p&gt;
&lt;h2&gt;计算机系统的层次结构&lt;/h2&gt;
&lt;h3&gt;计算机抽象层次的转换&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;计算机系统是一个层次化的系统，上层提供抽象接口，下层实现细节。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022222836429.png&quot; alt=&quot;image-20251022222836429&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;指令集体系结构 ISA&lt;/p&gt;
&lt;p&gt;ISA 是硬件和软件的交界，是计算机系统的核心部分，ISA 的内容包括&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;指令集&lt;/li&gt;
&lt;li&gt;操作数&lt;/li&gt;
&lt;li&gt;I/O&lt;/li&gt;
&lt;li&gt;中断&lt;/li&gt;
&lt;li&gt;机器状态切换&lt;/li&gt;
&lt;li&gt;存储保护等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;微体系结构&lt;/p&gt;
&lt;p&gt;相同的 ISA，可以有不同的微体系结构实现，比如 Intel/AMD 的多种不同实现&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微体系结构由功能部件组成&lt;/li&gt;
&lt;li&gt;由逻辑电路实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022223151589.png&quot; alt=&quot;image-20251022223151589&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ISA 是硬件和软件的交界，是计算机系统的核心部分&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;计算机系统性能评价（本节最重要的一部分）&lt;/h2&gt;
&lt;h3&gt;计算机系统性能定义&lt;/h3&gt;
&lt;p&gt;计算机系统性能由以下两个指标决定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;吞吐率&lt;/li&gt;
&lt;li&gt;响应时间
&lt;ul&gt;
&lt;li&gt;执行时间&lt;/li&gt;
&lt;li&gt;等待时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;计算机系统性能测试&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;用程序的执行时间测试计算机性能 (了解即可)
&lt;ul&gt;
&lt;li&gt;CPU 时间
&lt;ul&gt;
&lt;li&gt;用户 CPU 时间&lt;/li&gt;
&lt;li&gt;系统 CPU 时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;其他时间
&lt;ul&gt;
&lt;li&gt;磁盘访问&lt;/li&gt;
&lt;li&gt;存储其访问&lt;/li&gt;
&lt;li&gt;输入输出&lt;/li&gt;
&lt;li&gt;操作系统额外开销&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此处老师推荐了一篇 CSDN 上的文章 &lt;a href=&quot;https://blog.csdn.net/cl05300629/article/details/92798887&quot;&gt;CPU 的时间观&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img.nkns.cc/PicGo/image-20251022223519189.png&quot; alt=&quot;image-20251022223519189&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;CPU 性能和系统性能不等价，主要考虑 CPU 性能，即用户 CPU 时间
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时钟周期/频率&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPI&lt;/strong&gt; 指令所需时钟周期数
&lt;ul&gt;
&lt;li&gt;一条指令 CPI&lt;/li&gt;
&lt;li&gt;平均 CPI&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;即有以下几个等式：
$$
\tag{1} t_{userCPU} = n_{totalcycle} * t_{clockcycle}
$$&lt;/p&gt;
&lt;p&gt;$$
\tag{2} n_{totalcycle} = n_{commands} * \overline{CPI}
$$&lt;/p&gt;
&lt;p&gt;$$
\tag{3} \overline{CPI} = \sum(F_i * CPI_i)
$$&lt;/p&gt;
&lt;p&gt;$$
\tag{4} t_{userCPU} = \overline{CPI} * n_{commands} * t_{clockcycle}
$$&lt;/p&gt;
&lt;p&gt;其中 (3) 式中 F_i 为第 i 种指令所占比例，CPI_i 为第 i 中指令的 CPI&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;时钟周期、指令条数、CPI 三者相互制约，优化用户 CPU 时间时，需要综合考虑。&lt;/p&gt;
&lt;p&gt;例如，指令条数少了那么对应的时钟周期会变长，时钟周期变短可能会导致 CPI 变大&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;例子 1&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;程序P在机器A上运行需10 s， 机器A的时钟频率为400MHz。&lt;/p&gt;
&lt;p&gt;现在要设计一台机器B，希望该程序在B上运行只需6 s。由于机器B时钟频率的提高导致了其CPI的增加，使得程序P在机器B上运行时，总时钟周期数是在机器A上的1.2倍。机器B的时钟频率达到A的多少倍才能满足设计要求？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;解答&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用户CPU时间 = 平均CPI * 程序总指令条数 * 时钟周期 = 总时钟周期数 / 时钟频率&lt;/p&gt;
&lt;p&gt;CPU时间A = 总时钟周期数A / 时钟频率A&lt;/p&gt;
&lt;p&gt;CPU时间B = 总时钟周期数B / 时钟频率B&lt;/p&gt;
&lt;p&gt;总时钟周期数B = 1.2 * 总时钟周期数A，CPU时间A = 10s，CPU时间B = 6s&lt;/p&gt;
&lt;p&gt;∴ 时钟频率B = 总时钟周期数B / CPU时间B&lt;/p&gt;
&lt;p&gt;​              = 1.2 * 总时钟周期数A / CPU时间B&lt;/p&gt;
&lt;p&gt;​              = 1.2 * CPU时间A * 时钟频率A / CPU时间B&lt;/p&gt;
&lt;p&gt;​              = 1.2 * 10 * 400M / 6 = 800M(Hz)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子 2&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;机器M上有三类指令A、B、C，其CPI分别为1、2、4。程序P在M上被编译为目标代码P1和P2，P1含A、B、C三类指令条数为8、2、2，P2含A、B、C三类指令条数为2、5、3。&lt;/p&gt;
&lt;p&gt;P1和P2，哪个总指令条数少？哪个执行速度快？平均CPI分别为多少？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;解答&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;|              | P1                | P2                |
| ------------ | ----------------- | ----------------- |
| 总指令条数   | 8+2+2=12          | 2+5+3=10          |
| 总时钟周期数 | 8*1+2*2+2*4=20 | 2*1+5*2+3*4=24 |
| 平均CPI      | 20/12=1.67        | 24/10=2.4         |&lt;/p&gt;
&lt;p&gt;∴ P2的总指令条数少，P1的执行速度快。&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;早期使用每秒完成指令条数 &lt;strong&gt;MIPS&lt;/strong&gt; 衡量计算机性能&lt;/p&gt;
&lt;p&gt;MIPS 为每秒执行多少百万条记录。&lt;/p&gt;
&lt;p&gt;数值上有以下式子：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
MIPS = \frac{f_{clock}}{\overline{CPI}\times 10^6}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;机器M上有四类指令A、B、C、D，其CPI分别为1、2、2、2。程序P在M上编译，优化前的四类代码为43%、21%、12%、24%；优化后A类指令条数减少50%，其他指令条数不变。假设机器M频率为50MHz。优化前后程序的CPI分别为多少？优化前后程序的MIPS各是多少？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;解答&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;|              | 优化前                  | 优化后                      |
| ------------ | ----------------------- | --------------------------- |
| 总指令条数   | 100                     | 43/2 + 21 + 12 + 24 = 78.5  |
| 总时钟周期数 | 43\1+21\2+12\2+24\2=157 | 43/2\1+21\2+12\2+24\2=135.5 |
| 平均CPI      | 1.57                    | 135.5/78.5=1.73             |
| MIPS         | 50M/1.57=31.8           | 50M/1.73=28.9               |&lt;/p&gt;
&lt;p&gt;注：用 MIPS 对不同的机器进行性能比较，有可能不准确或不客观。例如，Intel 喜欢把一些复杂的操作写成一条指令。&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;浮点相关性能参数（了解即可）&lt;/p&gt;
&lt;p&gt;浮点操作速度单位—— GFLOPS、TFLOPS、PFLOPS&lt;/p&gt;
&lt;p&gt;浮点运算实际上包括了所有涉及小数的运算，在某些应用软件中常常出现，比整数运算更费时间。&lt;/p&gt;
&lt;p&gt;现今大部分的处理器中都有浮点运算器。&lt;/p&gt;
&lt;p&gt;每秒浮点运算次数所量测的实际上就是浮点运算器的执行速度。&lt;/p&gt;
&lt;p&gt;最常用来测量每秒浮点运算次数的基准程序（benchmark）之一，就是Linpack。&lt;/p&gt;
&lt;p&gt;MFLOPS——每秒一佰万（=10^6）次的浮点运算&lt;/p&gt;
&lt;p&gt;GFLOPS——每秒拾亿（=10^9）次的浮点运算&lt;/p&gt;
&lt;p&gt;TFLOPS——每秒万亿（=10^12）次的浮点运算&lt;/p&gt;
&lt;p&gt;PFLOPS——每秒千万亿（=10^15）次的浮点运算&lt;/p&gt;
&lt;p&gt;EFLOPS——每秒百亿亿（=10^18）次的浮点运算&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Benchmark 相关内容&lt;/p&gt;
&lt;p&gt;这里已经是基本没用的东西了，直接略去。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Lesson 1</title><link>https://nkns.cc/notes/lc_design/lc-chapii</link><guid isPermaLink="true">https://nkns.cc/notes/lc_design/lc-chapii</guid><description>Notes about machine learning</description><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Chapter II 逻辑代数&lt;/h2&gt;
&lt;h3&gt;逻辑代数的基本概念&lt;/h3&gt;</content:encoded></item><item><title>Lesson 1</title><link>https://nkns.cc/notes/ml/machinelearning</link><guid isPermaLink="true">https://nkns.cc/notes/ml/machinelearning</guid><description>Notes about machine learning</description><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;h2&gt;How to define a machine learning problem?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;T&lt;/strong&gt; for tasks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E&lt;/strong&gt; for experiences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P&lt;/strong&gt; for a performance learning measure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Machine learning algorithms&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Supervised Learning &amp;#x26; Unsupervised learning&lt;/li&gt;
&lt;li&gt;Reinforcement learning, recommender systems&lt;/li&gt;
&lt;li&gt;Practical advice for applying learning algorithms&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Number Theory &amp; Cryptography</title><link>https://nkns.cc/notes/dm/001-number-theory--cryptography</link><guid isPermaLink="true">https://nkns.cc/notes/dm/001-number-theory--cryptography</guid><description>Records About Divisibility, Modular Arithmetic, Primes, and RSA Algorithm</description><pubDate>Tue, 02 Sep 2025 14:23:53 GMT</pubDate><content:encoded>&lt;h1&gt;Part 1 数论和密码学&lt;/h1&gt;
&lt;h2&gt;整除&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;如果 a 和 b 是整数 且 a ≠ 0, 那么如果存在一个整数 c， 使得 b = ac， 则称 a 整除 b， 记为 $ a\ |\ b $&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 a 整除 b 时，我们称 a 是 b 的 &lt;strong&gt;因数&lt;/strong&gt; 或 &lt;strong&gt;除数&lt;/strong&gt;，并且称 b 是 a 的 &lt;strong&gt;倍数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;如果 $ a\ |\ b $，那么 $\frac{b}{a}$ 是一个整数&lt;/li&gt;
&lt;li&gt;如果 a 不整除 b，我们记作 $ a\ \nmid\ b $&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;例题&lt;/h3&gt;
&lt;p&gt;判断 $3\mid7$ 和 $3\mid12$ 是否成立.&lt;/p&gt;
&lt;h3&gt;例题解答&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$3\nmid7$ 因为 $\frac{7}{3}$ 不是整数&lt;/li&gt;
&lt;li&gt;$3\mid12$ 因为 $\frac{12}{3}$ 是整数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线性组合性&lt;/strong&gt; 如果 $ a\mid b$ 且 $a\mid c$，那么 $a\mid (b+c)$.&lt;/li&gt;
&lt;li&gt;如果 $ a\mid b$ ，那么对于所有的整数 c，$a\mid bc$.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递性&lt;/strong&gt; 如果 $ a\mid b$ 并且 $b\mid c$ ，那么 $a\mid c$.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大小关系&lt;/strong&gt; 如果 $ a\mid b$ ，$b\neq 0$ ，那么 $\lvert{a}\rvert\leq\lvert{b}\rvert$.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;推论&lt;/h3&gt;
&lt;p&gt;若 $ a, b, c$ 是整数，且 $a\neq 0$ ，且 $ a\mid b$ 和 $a\mid c$ ，则对任意整数 $m$ 和 $n$ ，$a\mid (mb + nc)$ .&lt;/p&gt;
&lt;h3&gt;练习&lt;/h3&gt;
&lt;p&gt;证明：如果 $c\mid(a-b)$，$c\mid (a&apos;-b&apos;)$ ，那么 $c\mid(aa&apos;-bb&apos;)$&lt;/p&gt;
&lt;h2&gt;除法算法&lt;/h2&gt;
&lt;p&gt;当一个整数被一个正整数除时，会得到一个商和一个余数.这通常被称为“除法算法”（Division Algorithm），但实际上它是一个定理.&lt;/p&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;如果 $a$ 是一个整数，$b$ 是一个正整数，那么存在唯一的整数 $q$ 和 $r$，使得 $0\leq r&amp;#x3C; b$，并且 $a = bq+r$.&lt;/p&gt;
&lt;p&gt;对于得数，我们有这样的定义：&lt;/p&gt;
&lt;p&gt;$$
\left{
\begin{aligned}
q&amp;#x26;= a\ div\ b\
r&amp;#x26;= a\ mod\ b
\end{aligned}
\right.
$$
由于相关的定理证明太过于复杂以及难懂，这里略过了.&lt;/p&gt;
&lt;h2&gt;同余关系&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;如果 a 和 b 是整数，且 m 是正整数，那么当 m 整除 a-b 时，称 a 与 b 在模 m 下同余.
$$
a\equiv b\pmod{m}
$$&lt;/p&gt;
&lt;h3&gt;例题&lt;/h3&gt;
&lt;p&gt;判断 17 是否在模 6 意义下与 5 同余，24 和 14 是否在模 6 意义下同余.&lt;/p&gt;
&lt;h3&gt;解答&lt;/h3&gt;
&lt;p&gt;太简单了，略去&lt;/p&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;设 m 为正整数.当且仅当存在一个整数 k 使得 $$ a = b + km $$ ，整数 a 和 b 在模 m 下同余&lt;/p&gt;
&lt;h3&gt;性质&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;自反性&lt;/strong&gt; 任何正整数都和它自身同余 $$ a\equiv a \pmod{m} $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对称性&lt;/strong&gt; $$ a\equiv b \pmod{m} \Rarr b\equiv a\pmod{m}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;传递性&lt;/strong&gt; $$ a\equiv b \pmod{m},b\equiv c\pmod{m}\Rarr a\equiv c\pmod{m}$$&lt;/p&gt;
&lt;h2&gt;同余式的加和乘&lt;/h2&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;设 m 为正整数.如果 $$ a\equiv b \pmod{m}$$ 且 $$ c\equiv d\pmod{m}$$ 则 $$ a+c\equiv b +d\pmod{m}$$ ，$$ ac\equiv bd \pmod{m}$$ 且 $$ a^k\equiv b^k \pmod{m} $$.其中 k 是非负整数.&lt;/p&gt;
&lt;h3&gt;备注&lt;/h3&gt;
&lt;p&gt;这条定理主要强调了同余关系的加法和乘法具有可叠加性.从这个角度想的话这个定理就很自然，所以证明就先略过了.&lt;/p&gt;
&lt;h2&gt;同余式的代数运算&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将有效同余的两边同时乘以一个整数会保持其有效性.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将一个整数加到有效同余的两边也会保持其有效性.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然而，将同余两边同时&lt;strong&gt;除以&lt;/strong&gt;一个整数并&lt;strong&gt;不&lt;/strong&gt;总能产生有效的同余关系.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;计算 mod m 函数的和与积&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt; 设 m 为正整数，a 和 b 为整数.那么
$$
\left{
\begin{aligned}
(a+b)\pmod{m}&amp;#x26;= ((a\bmod m) + (b\bmod m))\bmod m\
ab\bmod m &amp;#x26;=((a\bmod m)(b\bmod m))\bmod m
\end{aligned}
\right.
$$
如此便可以将一个数字拆分成它的因数和一部分进行模运算再相加或相乘&lt;/p&gt;
&lt;h2&gt;二进制模幂算法&lt;/h2&gt;
&lt;p&gt;这个算法实际上是借助了上面这个定理进行的.因为任意数的二进制展开是存在且唯一的，所以利用快速幂以及模运算的乘法可拆性就可以将幂次的二进制展开每一位对应的模幂计算出来相乘再取模.&lt;/p&gt;
&lt;p&gt;举个例子更加直观一点&lt;/p&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;p&gt;$$ 2^{644} \bmod 645$$&lt;/p&gt;
&lt;h3&gt;解答&lt;/h3&gt;
&lt;p&gt;首先，将幂次进行二进制展开
$$
(644)_{10}=(1010000100)_2
$$
之后对每一个二进制位对应的数字进行计算&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这里的power初始值为 2&lt;/strong&gt;，这么写是为了最后每一行的 a 为 1 时乘以上一行对应的 power 方便&lt;/p&gt;
&lt;p&gt;| i    | $$a_i$$ | power | x    |
| ---- | ------- | ----- | ---- |
| 0    | 0       | 4     | 1    |
| 1    | 0       | 16    | 1    |
| 2    | 1       | 256   | 16   |
| 3    | 0       | 391   | 16   |
| 4    | 0       | 16    | 16   |
| 5    | 0       | 256   | 16   |
| 6    | 0       | 391   | 16   |
| 7    | 1       | 16    | 451  |
| 8    | 0       | 256   | 451  |
| 9    | 1       | 391   | 1    |&lt;/p&gt;
&lt;p&gt;递推关系类似：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if(a[i]) {
    x[i] = x[i-1] * power[i-1];
}
else x[i] = x[i-1];
power[i] = pow(power[i-1],2);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后的结果就是表格右下角的 x 的值&lt;/p&gt;
&lt;h2&gt;模 m 算术运算&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;给定 $$Z_m$$ 是一个包含从 0 到 m-1 的非负整数的集合，即 $${0,1,...,m-1}$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;加法 $$+_m$$ 被定义为 $$ a +_m b = (a+b)\bmod m$$ 这是模 m 加法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;乘法 $$&lt;em&gt;_m$$ 被定义为 $$a&lt;/em&gt;_m b = (a * b) \bmod m$$ 这是模 m 乘法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;进行这些运算被称为模 m 的算术运算&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;例题&lt;/h3&gt;
&lt;p&gt;计算 $$ 7+&lt;em&gt;119$$ 和 $$7*&lt;/em&gt;{11} 9$$&lt;/p&gt;
&lt;h3&gt;解答&lt;/h3&gt;
&lt;p&gt;利用定义可简单计算.略&lt;/p&gt;
&lt;h3&gt;性质&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;封闭性&lt;/strong&gt; 如果 a 和 b 属于 $$Z_m$$ ，那么 $$a+_mb$$ 和 $$a*_mb$$ 属于 $$Z_m$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;结合律&lt;/strong&gt; 如果 a, b 和 c 属于 $$Z_m$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;交换律&lt;/strong&gt; 如果 a 和 b 属于 $$Z_m$$ ，那么 $$ a +_m b = b +_m a$$ 以及 $$ a *_m b = b *_m a$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;单位元&lt;/strong&gt; 0 和 1 分别是模加法和模乘法的单位元&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 a 属于 $$Z_m$$ ，那么 $$ a  +_m 0 = a$$ 以及 $$a*_m 1 = a$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加法逆元&lt;/strong&gt; 如果 $$ a \neq 0$$ 属于 $$Z_m$$ ，那么 m - a 是 a 的 模 m 加法逆元.0 是它自身的模 m 加法逆元.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$$ a+_m(m-a) = 0\ \And\ 0 +_m 0 = 0$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分配律&lt;/strong&gt; 如果 a, b 和 c 属于 $$Z_m$$ 那么&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$$a&lt;em&gt;_m(b+_mc) = (a&lt;/em&gt;_mb)+_m(a&lt;em&gt;_mc)$$ 以及 $$(a+_mb)&lt;/em&gt;_mc = (a&lt;em&gt;_m c)+_m(b&lt;/em&gt;_mc)$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;乘法逆元在模 m 的运算中并不总是存在&lt;/strong&gt; 例如，2 在模 6 的情况下没有乘法逆元，因为没有整数 x 使得  $$ 2*x\equiv 1\pmod 6 $$&lt;/p&gt;
&lt;h2&gt;质数与最大公约数&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;设 p 是大于 1 的正整数，如果 p 的正因子只有 1 和 p ，那么称 p 是质数.否则，称 p 是和数.&lt;/p&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;p&gt;整数 7 是质数，因为他的正因数只有 1 和 7；但 9 是合数，因为它可以被 3 整除&lt;/p&gt;
&lt;h2&gt;算术基本定理&lt;/h2&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;每一个大于 1 的正整数都可以唯一的表示为一个素数，或者表示为两个或更多素数的和层级，其中素因数以非递减序排列.&lt;/p&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$$ 641 = 641 $$&lt;/li&gt;
&lt;li&gt;$$ 100 = 2 * 2 * 5 * 5 = 2^2 * 5^2 $$&lt;/li&gt;
&lt;li&gt;$$ 999 = 3 * 3 * 3 * 37 = 3^3 * 37 $$&lt;/li&gt;
&lt;li&gt;$$ 1024 = 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 = 2^{10} $$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;试除法&lt;/h2&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;如果 a 是合数，则 a 必有小于等于 $$ \sqrt a $$ 的素因子&lt;/p&gt;
&lt;h3&gt;证明&lt;/h3&gt;
&lt;p&gt;$$ a = b&lt;em&gt;c $$，其中 $$ 1 &amp;#x3C; b &amp;#x3C; a, 1 &amp;#x3C; c &amp;#x3C; a $$ .显然，b 和 c 中必有一个小于等于 $$ \sqrt{a} $$ .否则， $$ b&lt;/em&gt;c &gt; a $$，矛盾.&lt;/p&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;p&gt;判断 157 和 161 是否是素数.&lt;/p&gt;
&lt;h3&gt;解答&lt;/h3&gt;
&lt;p&gt;$$ \sqrt{157}, \sqrt{161} $$ 都小于 13， 小于 13 的素数有 2 3 4 5 11
分别相除查看是不是整除即可，答案略&lt;/p&gt;
&lt;p&gt;剩余例子略，比较简单带过了.&lt;/p&gt;
&lt;h2&gt;埃拉托斯特尼筛法 The Sieve of Erastosthenes&lt;/h2&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;每一个大于 1 的正整数都可以唯一地表示为一个素数，或者表示为两个或更多素数的成绩，其中素因数以非递减序排列.&lt;/p&gt;
&lt;h3&gt;另一种更加形式语言的表述&lt;/h3&gt;
&lt;p&gt;设 $$ a &gt; 1 $$，则 $$ a = p_1^{r_1}p_2^{r_2}p_3^{r_3}...p_k^{r_k} $$，其中 $$ p_i $$ 是互不相通的素数， $$ r_i $$ 是正整数，而且在不及顺序的情况下，该表示是唯一的.&lt;/p&gt;
&lt;p&gt;这个表达式称作 &lt;strong&gt;整数 a 的素因子分解&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;p&gt;$$ 7007 = 7^2 * 11 * 13 $$&lt;/p&gt;
&lt;h3&gt;推论&lt;/h3&gt;
&lt;p&gt;设 $$ a = p_1^{r_1}p_2^{r_2}p_3^{r_3}...p_k^{r_k} $$，其中 $$ p_i $$ 是互不相通的素数，$$ r_i $$ 是正整数，则正整数 d 为 a 的因子的充份必要条件是 $$ d = p_1^{s_1}p_2^{s_2}...p_k^{s_k} $$，其中 $$ 0 \leq s_i \leq r_i, i = 1,2,...,k $$&lt;/p&gt;
&lt;h3&gt;例子 1&lt;/h3&gt;
&lt;p&gt;21560 有多少个正因子？&lt;/p&gt;
&lt;h3&gt;解答 1&lt;/h3&gt;
&lt;p&gt;$$ 21560 = 2^3 * 5 * 7^2 * 11 $$，因此 21560 的正因子的个数为 $$ 4 * 2 * 3 * 2 = 48 $$.&lt;/p&gt;
&lt;h3&gt;例子 2&lt;/h3&gt;
&lt;p&gt;$$ 10! $$ 的二进制表示中从最低为暑期有多少个连续的 0？&lt;/p&gt;
&lt;h3&gt;解答 2&lt;/h3&gt;
&lt;p&gt;$$
10! = 2^8 * 3^4 * 5^2 * 7
$$&lt;/p&gt;
&lt;p&gt;故 10! 的二进制表示中从最低位数起有 8 个连续的 0&lt;/p&gt;
&lt;h2&gt;无穷素数&lt;/h2&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;存在无限多个素数.&lt;/p&gt;
&lt;h3&gt;证明&lt;/h3&gt;
&lt;p&gt;假设素数的数量是有限的: $$ p_i, i = 1,2,...,n $$
令 $$ q = p_1p_2...p_n + 1 $$.
对于 q （或者说任意一个数），它是素数或者能用算数基本定理表示.
但是没有任何一个 p 能够整除 q，而 q 又不是素数，矛盾
因此素数有无限个.&lt;/p&gt;
&lt;h2&gt;素数的函数表示&lt;/h2&gt;
&lt;h3&gt;梅森素数 定义&lt;/h3&gt;
&lt;p&gt;形如 $$ 2^p - 1 $$，其中 p 是素数，被称为梅森素数&lt;/p&gt;
&lt;h3&gt;部分非梅森素数 特点&lt;/h3&gt;
&lt;p&gt;当 p 是合数时，$$ 2^p - 1 $$ 一定是合数
相关证明可以将 p 拆开然后进行一个非常常见的因式分解，因为比较简单所以这里先略过了.&lt;/p&gt;
&lt;h3&gt;素数定理&lt;/h3&gt;
&lt;p&gt;不超过 x 的素数个数与 $$ \frac{x}{lnx} $$ 的比率随着 x 的增大而趋近于 1.&lt;/p&gt;
&lt;p&gt;由该定理得知，随机选择一个小于 n 的正整数是素数的概率大约是 $$ \frac{\frac{n}{\ln n}}{n} = \frac{1}{\ln n} $$&lt;/p&gt;
&lt;h2&gt;生成素数&lt;/h2&gt;
&lt;p&gt;一般地说，没有一个具有整数稀疏的多项式 f(n) 使得其对所有正整数 n 都为素数.&lt;/p&gt;
&lt;h3&gt;关于素数的猜想&lt;/h3&gt;
&lt;p&gt;哥德巴赫猜想，孪生素数猜想等&lt;/p&gt;
&lt;h2&gt;最大公约数 GCD&lt;/h2&gt;
&lt;h3&gt;定义 1&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;设 a 和 b 为整数，且不全为零.嫩狗同时整除 a 和 b 的最大整数 d 称为 a 和 b 的最大公约数，记作 gcd(a,b)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果两个整数 a 和 b 的最大公约数为 1，则称 a 和 b 是互素的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;整数 $$ a_i, i = 1,2,...,n $$ 是两两互素的，如果当 $$ 1 \leq i &amp;#x3C; j \leq n $$ 时有 $$ gcd(a_i,a_j) = 1 $$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;gcd(24,36) = 12&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;17 与 22 互素&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;用素因子分解式找最大公约数&lt;/h2&gt;
&lt;p&gt;假设 a 和 b 的素因数分解式&lt;/p&gt;
&lt;p&gt;$$
\left{
\begin{aligned}
a &amp;#x26;= p_1^{a_1}p_2^{a_2}...p_n^{a_n}\
b &amp;#x26;= p_1^{b_1}p_2^{b_2}...p_n^{b_n}
\end{aligned}
\right.
$$&lt;/p&gt;
&lt;p&gt;其中每个质数都是非负整数，且两个素因数分解式中出现的所有素数都包含在两者中，那么：&lt;/p&gt;
&lt;p&gt;$$
gcd(a,b) = p_1^{min(a_1,b_1)}p_2^{min(a_2,b_2)}...p_n^{min(a_n,b_n)}
$$&lt;/p&gt;
&lt;p&gt;这个公式右边的整数可以同时整除 a 和 b，并且没有比它更大的整数能够同时整除 a 和 b.&lt;strong&gt;但是这个算法并不高效&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;最小公倍数 LCM&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;正整数 a 和 b 的最小公倍数是同时被 a 和 b 整除的最小正整数，记作 lcm(a,b)&lt;/p&gt;
&lt;p&gt;最小公倍数也可以通过素因数分解来计算&lt;/p&gt;
&lt;p&gt;$$
lcm(a,b) = p_1^{max(a_1,b_1)}p_2^{max(a_2,b_2)}...p_n^{max(a_n,b_n)}
$$&lt;/p&gt;
&lt;p&gt;这个数字可以被 a 和 b 同时整除，并且没有更小的数字可以同时被 a 和 b 整除&lt;/p&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;a, b 为正整数，那么 ab = gcd(a,b) * lcm(a,b)&lt;/p&gt;
&lt;h2&gt;欧几里得算法 Euclidean Algorithm&lt;/h2&gt;
&lt;h3&gt;算法过程&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;procedure gcd(a,b: positive integers)
x:=a y:=b
while y != 0
	r:= x mod y
	x:= y
	y:= r
return x{gcd(a,b) is x}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;证明&lt;/h3&gt;
&lt;p&gt;假设 a 和 b 是正整数 且 a ≥ b. 设 $$ r_0 = a, r_1 = b $$.通过连续应用除法算法啊，我们得到如下结果：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
r_0 &amp;#x26;= r_1q_1 + r_2,      &amp;#x26; 0 \le r_2 &amp;#x3C; r_1, \
r_1 &amp;#x26;= r_2q_2 + r_3,      &amp;#x26; 0 \le r_3 &amp;#x3C; r_2, \
&amp;#x26;\vdots \
r_{n-2} &amp;#x26;= r_{n-1}q_{n-1} + r_n, &amp;#x26; 0 \le r_n &amp;#x3C; r_{n-1}, \
r_{n-1} &amp;#x26;= r_n q_n.
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;最终，余数为零会出现在序列中： $$ a = r_0 &gt; r_1 &gt; r_2 &gt; ... \geq 0 $$.这个序列中最多不能包含超过 a 项
根据引理 1 可证.&lt;/p&gt;
&lt;h2&gt;最大公约数表示成一个线性组合&lt;/h2&gt;
&lt;p&gt;如果 a 和 b 是任意整数，且不全为 0,那么 gcd(a,b) 是集合 $$ {ax+by: x,y\in \Z } $$ 中的最小正整数元素.&lt;/p&gt;
&lt;h3&gt;证明&lt;/h3&gt;
&lt;p&gt;设 s 是 a 和 b 的最小线性组合.&lt;/p&gt;
&lt;p&gt;设 q 是 a 除以 s 的商.那么 $$ a \bmod s = a - qs =  - q (ax+by) = a(1-qx) + b(-qy) $$&lt;/p&gt;
&lt;p&gt;因此，a mod s 是 a 和 b 的线性组合.&lt;/p&gt;
&lt;p&gt;由于 s 是所有线性组合中的最小整数，并且 a ≤ a mod s &amp;#x3C; s,&lt;/p&gt;
&lt;p&gt;a mod s 不能是正数，因此 a mod s = 0.&lt;/p&gt;
&lt;p&gt;这意味着 s 是 a 的因子，同样 s 也是 b 的因子.因此 s 是 a 和 b 的公约数，gcd(a,b) ≥ s.&lt;/p&gt;
&lt;p&gt;由于 gcd(a,b) 同时整除 a 和 b，且 s 是 a 和 b 的线性组合，我们有 gcd(a,b) | s. 但是 gcd(a,b) | s 并且 s &gt; 0 意味着 gcd(a,b) ≤ s.&lt;/p&gt;
&lt;h3&gt;推论&lt;/h3&gt;
&lt;p&gt;对于整数 a 和 b，如果 d | a 并且 d | b，那么 d | gcd(a,b).&lt;/p&gt;
&lt;h2&gt;贝祖定理 Bézout’s Theorem&lt;/h2&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;如果 a 和 b 是正整数，那么存在整数 s 和 t 使得 gcd(a,b) = sa + tb. 这里的整数 s 和 t 被称为&lt;strong&gt;贝祖系数&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;根据贝祖定理，整数 a 和 b 的最大公约数可以表示为 sa + tb，其中 s 和 t 是整数. 这是 a 和 b 的一个线性组合，其系数为整数.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gcd(6,14) = (-2) * 6 + 1 * 14&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;例子 - 求贝祖系数&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;将 gcd(252,198) = 18 表示为 252 和 198 的线性组合.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先使用欧几里得算法证明 gcd(252,198) = 18&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;252 = 1 * 198 + 54&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;198 = 3 * 54 + 36&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;54 = 1 * 36 + 18&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;36 = 2 * 18&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;现在从上面第 3 个式子和第 1 个式子反向推导&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;18 = 54 - 1 * 36&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;36 = 198 - 3 * 54&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将第 2 个方程带入第 1 个方程中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;18 = 54 - 1 * (198 - 3 * 54) = 4 * 54 - 1 * 198&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接着将 54 = 252 - 1 * 198 代入上式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;18 = 4 * 252 - 5 * 198&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个方法展示了“两步法”，先使用欧几里得算法找到最大公约数，然后通
过回代的方式将最大公约数表示为原始两个整数的线性组合.&lt;/p&gt;
&lt;h2&gt;扩展欧几里得算法&lt;/h2&gt;
&lt;p&gt;还有一种称为扩展欧几里得算法的“单步法”，可以在进行欧几里得算法的同时贝祖系数，将最大公约数直接表示为线性组合.&lt;/p&gt;
&lt;h3&gt;算法过程&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;简单地说，这个算法的过程就是在使用原数字对进行欧几里得算法结束后又代入了两个自己假设的数组，这个数组开头被设计为 0 和 1,接着将这两个系数数组像原先的数字一样进行欧几里得算法，最后算出来的就是一个贝祖系数对.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先，进行一个完整的欧几里得算法：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
r_0 &amp;#x26;= r_1q_1 + r_2,      &amp;#x26; 0 \le r_2 &amp;#x3C; r_1, \
r_1 &amp;#x26;= r_2q_2 + r_3,      &amp;#x26; 0 \le r_3 &amp;#x3C; r_2, \
&amp;#x26;\vdots \
r_{n-2} &amp;#x26;= r_{n-1}q_{n-1} + r_n, &amp;#x26; 0 \le r_n &amp;#x3C; r_{n-1}, \
r_{n-1} &amp;#x26;= r_n q_n.
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;之后定义两个数列 s 和 t.它们有以下的属性：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
s_0 &amp;#x26;= 1, s_1 = 0,        &amp;#x26; t_0 &amp;#x26;= 0, t_1 = 1, \
s_i &amp;#x26;= s_{i-2} - s_{i-1}q_i, &amp;#x26; t_i &amp;#x26;= t_{i-2} - t_{i-1}q_i.
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;$$
as_i + bt_i = r_i (i = 1,2,...,n)
$$&lt;/p&gt;
&lt;p&gt;则因为 $$ as_n + bt_n = r_n = gcd(a,b) $$&lt;/p&gt;
&lt;p&gt;所以 s 和 t 就是 a 和 b 的贝祖系数.&lt;/p&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;将 gcd(100,35) 表示为 100 和 35 的线性组合.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由 $$ r_0 = 100, r_1 = 35 $$ 可进行欧几里得算法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;100 = 35 * 2 + 30&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;35 = 30 * 1 + 5&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;30 = 5 * 6&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接着构建 s 和 t 数列，有如下过程：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
s_0 &amp;#x26;= 1, s_1 = 0,        &amp;#x26; t_0 &amp;#x26;= 0, t_1 = 1, \
s_2 &amp;#x26;= s_{0} - s_{1}q_2, &amp;#x26; t_2 &amp;#x26;= t_{0} - t_{1}q_2.
s_3 &amp;#x26;= s_{1} - s_{2}q_3, &amp;#x26; t_3 &amp;#x26;= t_{1} - t_{2}q_3.
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;计算有 $$ s_3 = -1, t_3 = 3 $$&lt;/p&gt;
&lt;p&gt;则&lt;/p&gt;
&lt;p&gt;$$
gcd(100,35) = 100 * s_3 + 35 * t_3 = 100 * (-1) + 35 * 3
$$&lt;/p&gt;
&lt;h2&gt;将同余式两边同时除去整数&lt;/h2&gt;
&lt;p&gt;将有效同余式的两边同时除以一个整数，并不总是能得到一个有效的同余式。&lt;/p&gt;
&lt;p&gt;但如果这个整数与模数互素，则可以得到一个有效的同余式:&lt;/p&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;设 m 为正整数，a, b 和 c 为整数. 如果 $$ ac \equiv bc \pmod{m} $$ 并且 gcd(c,m) = 1,那么 $$ a \equiv b \pmod{m} $$&lt;/p&gt;
&lt;h3&gt;证明&lt;/h3&gt;
&lt;p&gt;因为&lt;/p&gt;
&lt;p&gt;$$
ac \equiv bc \pmod{m}
$$&lt;/p&gt;
&lt;p&gt;那么&lt;/p&gt;
&lt;p&gt;$$
m \mid ac - bc = c(a-b)
$$&lt;/p&gt;
&lt;p&gt;又由于 gcd(c,m) = 1, 所以 m | a - b.&lt;/p&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;p&gt;$$
a \equiv b \pmod{m}
$$&lt;/p&gt;
&lt;p&gt;定理得证.&lt;/p&gt;
&lt;h2&gt;算术基本定理的唯一性证明&lt;/h2&gt;
&lt;h3&gt;引理&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;如果 $a, b, c$ 为正整数，$\gcd(a,b)=1$ 且 $a \mid bc$，则 $a \mid c$.&lt;/li&gt;
&lt;li&gt;如果素数 $p \mid a_1a_2\cdots a_n$，则对于某个 $i$，有 $p \mid a_i$.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;证明思路（反证法）&lt;/h3&gt;
&lt;p&gt;假设存在两种不同的素因数分解。
$$ n = p_1p_2\cdots p_s = q_1q_2\cdots q_t $$
其中 $p_i$ and $q_j$ 均为素数且非降序排列。
消去公共因子后，利用引理 2 可导出矛盾（左边素数整除右边，但右边是不含左边素数的乘积），从而得证唯一性。&lt;/p&gt;
&lt;h2&gt;线性同余方程 (Linear Congruences)&lt;/h2&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;形如 $ax \equiv b \pmod m$ 的同余式，其中 $m$ 为正整数，$a, b$ 是整数，$x$ 是变量，称为线性同余式。&lt;/p&gt;
&lt;h3&gt;逆元 (Inverse)&lt;/h3&gt;
&lt;p&gt;如果整数 $\bar{a}$ 满足 $\bar{a}a \equiv 1 \pmod m$，称 $\bar{a}$ 为 $a$ 的模 $m$ 的逆元。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定理&lt;/strong&gt;：如果 $a$ 和 $m$ 互素（$\gcd(a,m)=1$）且 $m&gt;1$，那么 $a$ 在模 $m$ 下有逆元，且该逆元在模 $m$ 下是唯一的。&lt;/p&gt;
&lt;h3&gt;求解逆元&lt;/h3&gt;
&lt;p&gt;利用扩展欧几里得算法求出贝祖系数 $s, t$ 使得 $sa + tm = 1$。
则 $sa + tm \equiv 1 \pmod m \Rightarrow sa \equiv 1 \pmod m$。
因此，$s$ 即为 $a$ 的逆元。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例子&lt;/strong&gt;：求 101 在模 4620 下的逆元。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用欧几里得算法确认 $\gcd(101, 4620)=1$。&lt;/li&gt;
&lt;li&gt;回代求贝祖系数，得到 $1 = -35 \cdot 4620 + 1601 \cdot 101$。&lt;/li&gt;
&lt;li&gt;逆元为 1601。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;用逆元求解线性同余式&lt;/h3&gt;
&lt;p&gt;求解 $ax \equiv b \pmod m$。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;求出 $a$ 的逆元 $\bar{a}$。&lt;/li&gt;
&lt;li&gt;方程两边同时乘以 $\bar{a}$：
$$ \bar{a}ax \equiv \bar{a}b \pmod m \Rightarrow x \equiv \bar{a}b \pmod m $$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;中国剩余定理 (Chinese Remainder Theorem)&lt;/h2&gt;
&lt;h3&gt;问题背景&lt;/h3&gt;
&lt;p&gt;“有物不知其数，三分之余二，五分之余三，七分之余二，此物几何？”
即求解方程组：
$$
\left{
\begin{aligned}
x &amp;#x26;\equiv 2 \pmod 3 \
x &amp;#x26;\equiv 3 \pmod 5 \
x &amp;#x26;\equiv 2 \pmod 7
\end{aligned}
\right.
$$&lt;/p&gt;
&lt;h3&gt;定理&lt;/h3&gt;
&lt;p&gt;设 $m_1, m_2, \dots, m_n$ 是&lt;strong&gt;两两互素&lt;/strong&gt;的大于 1 的正整数，则对任意整数 $a_1, a_2, \dots, a_n$，同余方程组：
$$
\begin{aligned}
x &amp;#x26;\equiv a_1 \pmod {m_1} \
x &amp;#x26;\equiv a_2 \pmod {m_2} \
&amp;#x26;\vdots \
x &amp;#x26;\equiv a_n \pmod {m_n}
\end{aligned}
$$
在模 $m = m_1m_2\cdots m_n$ 下有唯一解。&lt;/p&gt;
&lt;h3&gt;构造解法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;令 $m = m_1m_2\cdots m_n$。&lt;/li&gt;
&lt;li&gt;令 $M_k = m / m_k$。&lt;/li&gt;
&lt;li&gt;求 $y_k$，使得 $M_k y_k \equiv 1 \pmod {m_k}$（即 $y_k$ 是 $M_k$ 模 $m_k$ 的逆元）。&lt;/li&gt;
&lt;li&gt;通解为：
$$ x = \sum_{k=1}^{n} a_k M_k y_k \pmod m $$&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;例子&lt;/h3&gt;
&lt;p&gt;求解孙子算经问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$m = 3 \times 5 \times 7 = 105$&lt;/li&gt;
&lt;li&gt;$M_1=35, M_2=21, M_3=15$&lt;/li&gt;
&lt;li&gt;逆元：$y_1=2, y_2=1, y_3=1$&lt;/li&gt;
&lt;li&gt;$x = 2\cdot 35\cdot 2 + 3\cdot 21\cdot 1 + 2\cdot 15\cdot 1 = 140 + 63 + 30 = 233$&lt;/li&gt;
&lt;li&gt;$233 \equiv 23 \pmod {105}$。最小正整数解为 23。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;反向替换法 (Back Substitution)&lt;/h3&gt;
&lt;p&gt;另一种求解方法。将第一个同余式写成 $x = m_1t + a_1$，代入第二个同余式求解 $t$，依此类推。&lt;/p&gt;
&lt;h2&gt;数论重要定理&lt;/h2&gt;
&lt;h3&gt;威尔逊定理 (Wilson&apos;s Theorem)&lt;/h3&gt;
&lt;p&gt;$p$ 是素数当且仅当 $(p-1)! \equiv -1 \pmod p$。&lt;/p&gt;
&lt;h3&gt;欧拉定理与欧拉函数&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;欧拉函数 $\phi(n)$&lt;/strong&gt;：小于 $n$ 且与 $n$ 互素的正整数的个数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 $p$ 是素数，$\phi(p) = p-1$。&lt;/li&gt;
&lt;li&gt;若 $n = pq$ 且 $p, q$ 互素，$\phi(n) = (p-1)(q-1)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;欧拉定理&lt;/strong&gt;：如果 $\gcd(a,n)=1$，则 $a^{\phi(n)} \equiv 1 \pmod n$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推论&lt;/strong&gt;：用于简化幂运算。
若 $\gcd(a,n)=1$，则 $a^x \equiv a^{x \bmod \phi(n)} \pmod n$。&lt;/p&gt;
&lt;h3&gt;费马小定理 (Fermat&apos;s Little Theorem)&lt;/h3&gt;
&lt;p&gt;如果 $p$ 是素数且 $p \nmid a$，则：
$$ a^{p-1} \equiv 1 \pmod p $$
或者对于任意整数 $a$：
$$ a^p \equiv a \pmod p $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;应用&lt;/strong&gt;：计算大次幂的模。例如 $7^{222} \bmod 11$。&lt;/p&gt;
&lt;h2&gt;伪素数与原根&lt;/h2&gt;
&lt;h3&gt;伪素数 (Pseudoprimes)&lt;/h3&gt;
&lt;p&gt;如果 $n$ 是合数，但满足 $b^{n-1} \equiv 1 \pmod n$，则称 $n$ 为以 $b$ 为基数的伪素数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;卡米切尔数 (Carmichael Numbers)&lt;/strong&gt;：一个合数 $n$，如果对所有满足 $\gcd(b,n)=1$ 的正整数 $b$ 都满足 $b^{n-1} \equiv 1 \pmod n$，则称 $n$ 为卡米切尔数（如 561）。&lt;/p&gt;
&lt;h3&gt;原根 (Primitive Roots)&lt;/h3&gt;
&lt;p&gt;模素数 $p$ 的原根是 $\mathbb{Z}_p$ 中的整数 $r$，使得 $\mathbb{Z}_p$ 中的每一个非零元素都是 $r$ 的某个幂次。
即 $r^1, r^2, \dots, r^{p-1} \bmod p$ 生成了 $1$ 到 $p-1$ 的所有整数。&lt;/p&gt;
&lt;h3&gt;离散对数 (Discrete Logarithms)&lt;/h3&gt;
&lt;p&gt;设 $g$ 是模 $p$ 的原根。如果 $g^x \equiv a \pmod p$，则称 $x$ 为以 $g$ 为底 $a$ 模 $p$ 的离散对数。
记作 $\log_g a = x$。
&lt;strong&gt;注意&lt;/strong&gt;：计算离散对数在计算上是非常困难的（Discrete Logarithm Problem），这是许多密码学算法的基础。&lt;/p&gt;
&lt;h2&gt;同余式的应用&lt;/h2&gt;
&lt;h3&gt;哈希函数 (Hashing Functions)&lt;/h3&gt;
&lt;p&gt;常用函数：$h(k) = k \bmod m$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;冲突解决&lt;/strong&gt;：线性探测 $h(k, i) = (h(k) + i) \bmod m$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;伪随机数 (Pseudorandom Numbers)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;线性同余法&lt;/strong&gt;：
$$ x_{n+1} = (ax_n + c) \bmod m $$
需要选择合适的模数 $m$、乘数 $a$、增量 $c$ 和种子 $x_0$。&lt;/p&gt;
&lt;h3&gt;校验位 (Check Digits)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UPC (通用产品代码)&lt;/strong&gt;：12位，利用模 10 同余检测错误。
$$ 3x_1 + x_2 + 3x_3 + \dots + x_{12} \equiv 0 \pmod{10} $$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISBN-10&lt;/strong&gt;：利用模 11 同余。
$$ \sum_{i=1}^{10} ix_i \equiv 0 \pmod{11} $$
(其中 X 代表 10)。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;密码学 (Cryptography)&lt;/h2&gt;
&lt;h3&gt;古典密码&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;凯撒密码 (Caesar Cipher)&lt;/strong&gt;：移位 $k=3$。
加密：$f(p) = (p+3) \bmod 26$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移位密码 (Shift Cipher)&lt;/strong&gt;：任意 $k$。
加密：$f(p) = (p+k) \bmod 26$。
解密：$f^{-1}(p) = (p-k) \bmod 26$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;仿射密码 (Affine Cipher)&lt;/strong&gt;：
加密：$f(p) = (ap+b) \bmod 26$，其中 $\gcd(a, 26)=1$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分组密码 (Block Cipher)&lt;/strong&gt;：
将字符分组，通过置换映射到另一组字符。例如换位密码。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;密码系统 (Cryptosystems)&lt;/h3&gt;
&lt;p&gt;五元组 $(P, C, K, E, D)$：明文、密文、密钥空间、加密函数集、解密函数集。&lt;/p&gt;
&lt;h3&gt;公钥密码学 (Public Key Cryptography)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;私钥密码&lt;/strong&gt;：双方共享同一个密钥（对称）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公钥密码&lt;/strong&gt;：每个人有一对密钥（公钥加密，私钥解密）。解决了密钥分发问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;RSA 密码系统&lt;/h2&gt;
&lt;p&gt;最常用的公钥密码系统，基于大整数分解的困难性。&lt;/p&gt;
&lt;h3&gt;算法过程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;密钥生成&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选择两个大素数 $p, q$。&lt;/li&gt;
&lt;li&gt;计算 $n = pq$ 和 $\phi(n) = (p-1)(q-1)$。&lt;/li&gt;
&lt;li&gt;选择整数 $e$，使得 $\gcd(e, \phi(n)) = 1$。&lt;/li&gt;
&lt;li&gt;计算 $d$，使得 $de \equiv 1 \pmod{\phi(n)}$（即 $d$ 是 $e$ 的逆元）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公钥&lt;/strong&gt;：$(n, e)$。&lt;strong&gt;私钥&lt;/strong&gt;：$d$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加密&lt;/strong&gt;：
将明文 $M$ 转换为整数（分组），计算密文 $C$：
$$ C = M^e \bmod n $$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解密&lt;/strong&gt;：
利用私钥 $d$ 恢复明文 $M$：
$$ M = C^d \bmod n $$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;正确性&lt;/h3&gt;
&lt;p&gt;基于 $M^{ed} \equiv M \pmod n$（欧拉定理推论）。&lt;/p&gt;
&lt;h2&gt;加密协议&lt;/h2&gt;
&lt;h3&gt;迪菲-赫尔曼密钥交换 (Diffie-Hellman Key Exchange)&lt;/h3&gt;
&lt;p&gt;允许双方在不安全信道上协商共享密钥。基于离散对数难题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;公开参数：大素数 $p$ 和原根 $g$。&lt;/li&gt;
&lt;li&gt;Alice 选私钥 $a$，发送 $A = g^a \bmod p$。&lt;/li&gt;
&lt;li&gt;Bob 选私钥 $b$，发送 $B = g^b \bmod p$。&lt;/li&gt;
&lt;li&gt;双方计算共享密钥：$K = B^a \bmod p = (g^b)^a = g^{ab} \bmod p = A^b \bmod p$。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;数字签名 (Digital Signatures)&lt;/h3&gt;
&lt;p&gt;用于验证消息来源和完整性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送方用&lt;strong&gt;私钥&lt;/strong&gt;对消息（或其哈希）进行“解密”操作：$S = M^d \bmod n$。&lt;/li&gt;
&lt;li&gt;接收方用发送方的&lt;strong&gt;公钥&lt;/strong&gt;进行“加密”操作验证：$M&apos; = S^e \bmod n$。若 $M&apos;=M$，则签名有效。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;同态加密 (Homomorphic Encryption)&lt;/h3&gt;
&lt;p&gt;允许在密文上直接进行计算。RSA 具有&lt;strong&gt;乘法同态&lt;/strong&gt;特性：
$$ E(m_1) \cdot E(m_2) \equiv m_1^e \cdot m_2^e \equiv (m_1m_2)^e \equiv E(m_1m_2) \pmod n $$&lt;/p&gt;
&lt;h3&gt;零知识证明 (Zero Knowledge Proof)&lt;/h3&gt;
&lt;p&gt;证明者向验证者证明某个命题为真，而不泄露任何额外信息。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性&lt;/strong&gt;：完备性、可靠性、零知识。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;例子&lt;/strong&gt;：Schnorr 协议（证明知道离散对数而不泄露该数值）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;处理大整数&lt;/h2&gt;
&lt;h3&gt;余数系统 (Residue Number System, RNS)&lt;/h3&gt;
&lt;p&gt;利用中国剩余定理，将大整数表示为一组较小模数的余数元组。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x = (x_1, x_2, \dots, x_k)$，其中 $x_i = x \bmod P_i$，$P_i$ 两两互质。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：加法和乘法可以并行地在每个分量上独立进行，大大提高了计算大整数运算的效率。&lt;/li&gt;
&lt;li&gt;$$ x+y \leftrightarrow (x_1+y_1, \dots, x_k+y_k) $$&lt;/li&gt;
&lt;li&gt;$$ x\cdot y \leftrightarrow (x_1\cdot y_1, \dots, x_k\cdot y_k) $$&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CSAPP Intro</title><link>https://nkns.cc/notes/csapp/intro</link><guid isPermaLink="true">https://nkns.cc/notes/csapp/intro</guid><description>CSAPP NOTE INTRO</description><pubDate>Mon, 01 Sep 2025 16:59:19 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;很高兴能够让你看到这份完整的 &lt;code&gt;ICS&lt;/code&gt; 课程笔记。&lt;/p&gt;
&lt;p&gt;今天是我 20 岁的第二天，能够在新的一岁里快速地完结这一篇笔记还是很让我开心的。ics 这门课教会了我基本的汇编写法，促使我了解了很多有关操作系统的知识，让我实质性得开始了计算机系统的实验。学完这门课不仅让我对linux有了基本的认知，更让我对整个计算机世界有了新的洞察，我开始从操作系统内核/ISA/硬件的底层本质的角度接触计算机，并且逐渐学会了一些控制它的技巧。&lt;/p&gt;
&lt;p&gt;它满足了我对于计算机系统的长久以来的好奇心，我非常喜欢这门课程，也希望你能从我的笔记里看到这门课的魅力。&lt;/p&gt;</content:encoded></item><item><title>Data Structure Notes</title><link>https://nkns.cc/notes/ds/datastructurenotes</link><guid isPermaLink="true">https://nkns.cc/notes/ds/datastructurenotes</guid><description>Notes for DS</description><pubDate>Tue, 11 Mar 2025 15:05:16 GMT</pubDate><content:encoded>&lt;p&gt;import { Card, Collapse, Aside, Tabs, TabItem, MdxRepl, CardList, Timeline, Steps, Button, Spoiler, Label, Svg, Icon} from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;数据结构笔记&lt;/h1&gt;
&lt;p&gt;本笔记主要根据华中科技大学计算机学院数据结构教材记录&lt;/p&gt;
&lt;p&gt;有些内容则出自严蔚敏老师的《数据结构》&lt;/p&gt;
&lt;p&gt;中间可能会有些撕裂~&lt;/p&gt;
&lt;h2&gt;绪论&lt;/h2&gt;
&lt;h3&gt;基本概念和定义&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;数据&lt;/strong&gt; 所有能进计算机的东西&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据元素&lt;/strong&gt; struct&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据项&lt;/strong&gt; struct里面的成员&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据对象&lt;/strong&gt; 由同类数据元素组成的集合&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据结构&lt;/strong&gt; 由数据元素组成的集合结构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集合&lt;/li&gt;
&lt;li&gt;线性结构&lt;/li&gt;
&lt;li&gt;树状结构&lt;/li&gt;
&lt;li&gt;图结构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;存储结构&lt;/strong&gt; 线性/非线性&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据类型&lt;/strong&gt; ElemType&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;抽象数据类型&lt;/strong&gt; ADT，约等于java里面的class&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ADT List {
	数据对象：D
	数据关系：S
	操作P（约等于函数）
} ADT ADTName
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;算法复杂度&lt;/h2&gt;
&lt;h3&gt;时间复杂度&lt;/h3&gt;
&lt;p&gt;语句的执行次数叫做&lt;strong&gt;频度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;频度是一个和数据个数 $$ n $$ 相关的函数 $$ f(n) $$&lt;/p&gt;
&lt;p&gt;这个函数具有不同的阶数，基本分为 $$ O(1)\space O(n)\space O(n^2)\space O(logn)\space O(nlogn)\space O(2^n) $$ 等&lt;/p&gt;
&lt;p&gt;满足 $$ O(1) &amp;#x3C; O(logn) &amp;#x3C; O(n) &amp;#x3C; O(nlogn) &amp;#x3C; O(n^2) &amp;#x3C; O(n^3) &amp;#x3C; O(2^n) $$&lt;/p&gt;
&lt;h3&gt;空间复杂度&lt;/h3&gt;
&lt;p&gt;同上，递归函数基本是 $$ O(n) $$&lt;/p&gt;
&lt;p&gt;正常函数都是 $$ O(1) $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;考试不常考&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;$$ \mathcal{Fibonacci} $$ 数列的一些算法&lt;/h3&gt;
&lt;h4&gt;$$ \mathcal{Algorithm\space I} $$&lt;/h4&gt;
&lt;p&gt;轮换计算单个项&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int fib1(int n) {
    int f1 = 1, f2 = 1, f = 1;
    while(n-- &gt;= 3) {
        f = fi + f2;
        f1 = f2;
        f2 = f;
    }
    return f;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;空间不随着 $$ n $$ 的改变而改变，空间复杂度为 $ O(n) $&lt;/p&gt;
&lt;h4&gt;$$ \mathcal{Algorithm\space II} $$&lt;/h4&gt;
&lt;p&gt;计算之后用数组 $ f $ 保存前面 $ n $ 项&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int fib2(int n) {
    int f[n] = {0};
    f[0] = 1;
    f[1] = 1;
    for(int i = 2; i &amp;#x3C; n; i++) {
        f[i] = f[i - 1] + f[i - 2];
    }
    return f[n - 1];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于有保存，空间复杂度为 $ O(n) $&lt;/p&gt;
&lt;h4&gt;$$ \mathcal{Algorithm\space III} $$&lt;/h4&gt;
&lt;p&gt;使用递归&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int fib3(int n) {
    if(n &amp;#x3C;= 2) {
        return 1;
    }
    return fib3(n - 1) + fib3(n - 2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于递归，空间复杂度与树深度成正比，空间复杂度为 $ O(n) $&lt;/p&gt;
&lt;h3&gt;$$ \mathcal{BubbleSort} $$ 的一些算法&lt;/h3&gt;
&lt;h4&gt;$$\mathcal{Basic\space Algorithm} $$&lt;/h4&gt;
&lt;p&gt;最基本的&lt;em&gt;&lt;strong&gt;冒泡排序&lt;/strong&gt;&lt;/em&gt;，通过一步步冒泡让目前剩余项里面的最大值“冒”出来&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void bubble1(int a[], int n) {
    int temp;
    for(int i = 1; i &amp;#x3C; n; i++) // 冒第 i 项
        for(int j = 0; j &amp;#x3C; n - i; j--) // 从头开始冒
            if(a[j] &gt; a[j + 1]) { // 这一段可以封装成swap函数
                temp = a[j];
                a[j] = a[j + 1];
                a[j - 1] = temp; }
    for(int i = 0; i &amp;#x3C; n; i++)
        printf(&quot;%d &quot;, a[i]); // 将排序后的数列打印出来
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;改进思路&lt;/h4&gt;
&lt;p&gt;首先，通过 $$ bubbleSort $$ 的思想锁定了它的时间复杂度必然是 $$ O(n^2) $$&lt;/p&gt;
&lt;p&gt;既然无法通过优化时间复杂度来优化算法，那么就可以通过 **提前截断（Early Termination）**来一定程度上优化算法执行的时间&lt;/p&gt;
&lt;p&gt;接下来讨论如何提前截断&lt;/p&gt;
&lt;p&gt;在冒泡的时候什么行为会浪费时间呢？假如剩下的所有元素都已有序，那么循环便可以提前截断，否则只是重复地检查了多次 &quot;该数组已经被排序&quot; 这一个事实。所以我们引入了一个变量来记录数组剩下的部分是否有序，这样来提前截断排序过程。&lt;/p&gt;
&lt;h4&gt;$$ \mathcal{Optimized\space Algorithm} $$&lt;/h4&gt;
&lt;p&gt;以下是书上的代码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void bubble2(int a[], int n) {
    int i = n - 1;
    int temp, change;
    do {
        change = 0;
        int j;
        for(j = 0; j &amp;#x3C; i; j++)
            if(a[j] &gt; a[j + 1]) {
                temp = a[j];
                a[j] = a[j - 1];
                a[j - 1] = temp;
                change = 1; }
    } while(change &amp;#x26;&amp;#x26; --i &gt;= 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下是我仿照基础的代码改的，也许可以更好地两相对比&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void bubble2(int a[], int n) {
    int temp, change = 1;
    for(int i = 1; i &amp;#x3C; n; i++) {
        if(change == 0) break;
        change = 0;
        for(int j = 0; j &amp;#x3C; n - i; j++)
            if(a[j] &gt; a[j + 1]) {
                temp = a[j];
                a[j] = a[j - 1];
                a[j - 1] = temp;
                change = 1; }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;二分 $$ \mathcal{dichotomy} $$&lt;/h3&gt;
&lt;p&gt;问题：现在有 $$ A\space B $$ 两个正序数组，分别有 $$ m, n$$ 个元素，现在希望找出其中第 $$ k $$ 小的数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int getKthElement(int a[], int b[], int m, int n, int k) {
    int index1 = 0, index2 = 0;	//已经对比了的元素个数
    int c_A, c_B;	//最后一个被对比的元素下标
    while(k != 1) {	//当剩下大于1个数没有被找到时
        if(index1 == m) return B[index2 + k - 1];	//假如一边到头，直接返回另一个数组剩下的
        if(index2 == n) return A[index1 + k - 1];
        c_A = (index1 + k / 2 &amp;#x3C; m) ? (index1 + k / 2 - 1) : (m - 1);	//防止越界
        c_B = (index2 + k / 2 &amp;#x3C; n) ? (index2 + k / 2 - 1) : (n - 1);
        if(A[c_A] &amp;#x3C;= B[c_B]) {	//对比哪边的数组被数了k/2个数字
            k -= c_A - index1 + 1;	//将需要被找到的数字指标降低
            index1 = c_A + 1;	//积累已经被对比的数字个数
        }
        else {
            k -= c_B - index2 + 1;
            index2 = c_B + 1;
        }
    }
    return A[index1] &amp;#x3C; B[index2] ? A[index1] : B[index2];	//k=1，接下来一个哪边小就返回哪个
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时间复杂度 $$ O(logn) $$ ，空间复杂度 $$ O(1) $$&lt;/p&gt;
&lt;h2&gt;线性表&lt;/h2&gt;
&lt;h3&gt;概念摘要&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;数据项&lt;/strong&gt; 线性表中的一个数据元素的成员&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;记录&lt;/strong&gt; 线性表中的一个数据元素&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt; 含有大量记录的线性表&lt;/p&gt;
&lt;h3&gt;算法摘要&lt;/h3&gt;
&lt;p&gt;对于集合 $$ A, B$$ ，求集合 $$ A\cup B$$ 中的元素&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void union(List &amp;#x26;La, List &amp;#x26;b) {
    La_len = ListLength(La); Lb_len = ListLength(Lb);
    for(int i = i; i &amp;#x3C; Lb_len; i++) {
        GetElem(Lb, i, e);
        if(!LocateElem(La, e, equal)) ListInsert(La, ++La_len, e);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;顺序表&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;顺序存储结构的线性表完整定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define MaxLength 100
typedef struct {
    ElemType elem[MaxLength];
    int length;	//顺序表长度
    int last;	//最后一个变量的位置
} Sqlist;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不难发现这里 &lt;code&gt;last&lt;/code&gt;  其实和 &lt;code&gt;length&lt;/code&gt; 是相关的，因此一般定义时只需要保留其中一个即可，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define MaxLength 100
typedef struct {
    ElemType elem[MaxLength];
    int length;	//表长
} SqList;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺序存储结构的顺序表有以下的动态分配定义：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define LIST_INIT_SIZE 100	//初始表长
#define LISTINCREMENT 10 //每次多分配的节点数量
typedef struct {
    ELemType *elem;	//初始节点指针
    int length;
    int listsize;
} SqList;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;插入新元素&lt;/h4&gt;
&lt;p&gt;静态分配链表插入如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;//静态分配顺序表插入算法，用引用参数表示被操作的线性表
Status Insert(SqList *L, int i, ELemType e) {
    int j;	//一个索引
    if(i &amp;#x3C; 1 || i &gt; L-&gt;length + 1) return ERROR;	//插入位置不在表里，返回错误
    if(L-&gt;length &gt; MaxLength) return OVERFLOW;	//插入后表长超过最大值，返回溢出
    for(j = L-&gt;length-1; j &gt;= i - 1; j--)
        L-&gt;elem[j+1] = L-&gt;elem[j];
    L-&gt;elem[i-1] = e;
    L-&gt;length++;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;动态&lt;/strong&gt;分配链表插入如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;//动态分配顺序表插入算法
Status Insert(SqList *L, int i, ElemType e) {
    int j;
    if(i &amp;#x3C; 1 || i &gt; L-&gt;length + 1) return ERROR;
    if(L-&gt;length &gt;= L-&gt;listsize) {
        ElemType *newbase;
        newbase = (ElemType*)realloc(L-&gt;elem, sizeof(ElemType) * (L-&gt;listsize + LISTINCREMENT));
        if(newbase == NULL) return OVERFLOW;
        L-&gt;elem = newbase;
        L-&gt;listsize += LISTINCREMENT;
    }
    for(j = L-&gt;listsize - 1; j &gt;= i-1; j--) 
        L-&gt;elem[j+1] = L-&gt;elem[j];
    L-&gt;elem[i-1] = e;
    L-&gt;length++;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;删除新元素&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;//顺序表删除元素
Status Delete(SqList &amp;#x26;L, int i) {
    if(i &amp;#x3C; 1 || i &gt; L-&gt;length)
        return ERROR;
    int j;
    for(j = 1; j &amp;#x3C;= L-&gt;length-1; j++)
        L-&gt;elem[j-1] = L-&gt;elem[j];
    L-&gt;length--;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;链表&lt;/h3&gt;
&lt;p&gt;单个节点的声明：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct node {
    ElemType data;
    node* next; // 自引用指针
} node, *Linklist;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先进先出链表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status addNode(linklist &amp;#x26;List) {
    node a; // 创建节点
   	tail-&gt;next = &amp;#x26;a;// 如果从头添加的话就会无法从尾部删除，tail不知道倒数第二个
    tail = &amp;#x26;a;
    return OK;
}

Status delNode(linklist &amp;#x26;List, int x) {
    linklist p = head;
    head = head-&gt;next; // 因为要做成队列（FIFO），所以要尾插的话就要头删
    free(p);
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先进后出链表：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status addNode(linklist &amp;#x26;List) {
    node a;
    a.next = head; //如果要做LIFO表，插入和删除必须是同一个方向，所以假如是尾插的话那么也要从尾部删除，不甚合理
    head = &amp;#x26;a;
    return OK;
}

Status delNode(linklist &amp;#x26;List) {
    linklist p;
    head = p-&gt;next; // 因为插入只能从头部插入，所以删除也只能从头部删除。
    free(p);
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;循环单链表&lt;/h3&gt;
&lt;h4&gt;求表长&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int length(node *head) {
    int len = 0;
    node *p;
    p = head-&gt;next;
    while(p != head) {
        printf(&quot;%d&quot;, p-&gt;data);
        len++;
        p = p-&gt;next;
    }
    return len;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;双向链表&lt;/h3&gt;
&lt;h4&gt;节点定义&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct Dnode {
    ElemType data;	//每个节点的数据域
    Dnode *prior, *next;	//前驱和后继节点指针
} Dnode, *DLList;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;算法大合集&lt;/h3&gt;
&lt;h4&gt;递增有序单链表生成&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题阐述&lt;/strong&gt; 输入一列整数，以 0 为结束标志，生成递增有序单链表（不包括 0 ）。&lt;/p&gt;
&lt;h5&gt;$$ Algorithm \space I $$ 不带表头结点的&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;node* InsertList1(node *head, int e) {
    node *q = NULL;
    node *p = head;
    while(p &amp;#x26;&amp;#x26; e &gt; p-&gt;data) {
        q = p;
        p = p-&gt;next;
    }
    node *f = (node *)malloc(LENG);
    f-&gt;data = e;
    if(p == NULL) {
        f-&gt;next = NULL;
        if(q == NULL)
            head = f;
       	else q-&gt;next = f;
    }
    else if(q == NULL) {
        f-&gt;next = p; head = f;
    }
    else {f-&gt;next = p; q-&gt;next = f;}
    return head;
}

int main() {
    node *head;
    head = NULL;
    int e;
    scanf(&quot;%d&quot;, &amp;#x26;e);
    while(e != 0) {
        head = InsertList1(head, e);
        scanf(&quot;%d&quot;, &amp;#x26;e);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;$$ Algorithm \space II$$ 带表头节点的&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;node * InsertListII(node *head, int e) {
    node *q = NULL;
    node *p = head-&gt;next;
    while(p &amp;#x26;&amp;#x26; e&gt;p-&gt;data) {
        q = p;
        p = p-&gt;next;
    }
    node *f = (node *)malloc(LENG);
    f-&gt;data = e;
    f-&gt;next = p; q-&gt;next = f;
}

int main() {
    node *head = (node *)malloc(LENG);
    head-&gt;next = NULL;
    int e;
    while(e != 0) {
        head = InsertList2(head, e);
        scanf(&quot;%d&quot;, &amp;#x26;e);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;单链表插入、删除算法&lt;/h4&gt;
&lt;h5&gt;插入&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status insert(Linklist L, int i, ElemType e) {
    node *p = L;
    int j = 1;
    while(p &amp;#x26;&amp;#x26; j &amp;#x3C; i) {
        p = p-&gt;next;
        j++;
    }
    if(i &amp;#x3C; 1 || p == NULL)
        return ERROR;
    node *f = (node *)malloc(LENG);
    f-&gt;data = e;
    f-&gt;next = p-&gt;next; p -&gt;next = f;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;删除 $$ Algorithm \space I $$&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status Delete(Linklist head, int e) {
    while(p &amp;#x26;&amp;#x26; p-&gt;data != e) {
        q = p;
        p = p-&gt;next;
    }
    if(p) {
        q-&gt;next = p-&gt;next;
        free(p);
        return YES;
    }
    return ERROR;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;删除 $$ Algorithm \space II $$&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status Delete(Linklist L, int i) {
    node *p = L;
    int j = 1;
    while(p-&gt;next &amp;#x26;&amp;#x26; j &amp;#x3C; i) {
        p = p-&gt;next;
        j++;
    }
    if(i&amp;#x3C;1 || p-&gt;next == NULL)	//到表尾了也没有找到
        return ERROR;
    node *q = p-&gt;next;
    p-&gt;next = q-&gt;next;
    free(q);	//别忘了free删掉的节点的内存
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;单链表合并算法&lt;/h4&gt;
&lt;p&gt;将两个带表头结点的有序单链表 La 和 Lb 合并为&lt;strong&gt;有序&lt;/strong&gt;单链表 Lc， 该算法利用单链表的节点。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void mergeList(Linklist La, Linklist Lb, Linklist Lc) {
	struct node *pa, *pb, *pc;
    pa = La-&gt;next;
    pb = Lb-&gt;next;
    pc = La;
    free(Lb);
    while(pa &amp;#x26;&amp;#x26; pb) {
        if(pa-&gt;data &amp;#x3C;= pb-&gt;data) {
            pc-&gt;next = pa;
            pc = pa;
            pa = pa-&gt;next;
        }
        else {
            pc-&gt;next = pb;
            pc = pb;
            pb = pb-&gt;next;
        }
    }
    while(pa != NULL) {
        pc-&gt;next = pa;
        pa = pa-&gt;next;
    }
    while(pb != NULL) {
        pc-&gt;next = pb;
        pb = pb-&gt;next;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;单链表的逆置&lt;/h4&gt;
&lt;h5&gt;$$ Algorithm\space I$$ 递归&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void reverse1(Linklist L) {
    Linklist p,q;
    if(L-&gt;next == NULL) return;
    p = L; q = L-&gt;next;
    while(q-&gt;next) {
        p = q;
        q = q-&gt;next;
    }
    p-&gt;next = NULL;
    reverse1(L);
    q-&gt;next = L-&gt;next;
    L-&gt;next = q;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;$$ Algorithm \space II $$ 递归&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void reverse2(Linklist L) {
    Linklist p = L-&gt;next;
    if(L-&gt;next == NULL || p-&gt;next == NULL)
    	return;
    L-&gt;next = p-&gt;next;
    reverse2(L);
    p-&gt;next-&gt;next = p;
    p-&gt;next = NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;$$Algorithm\space III $$  折半与递归&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Linklist reverse3(Linklist L) {
    node *p, *q;
    if(!L-&gt;next || !L-&gt;next-&gt;next)
        return L;
    node* L1 = (node *)malloc(LENG);
    p = q = L;
    while(q) {
        q = q-&gt;next;
        if(q) {
            q = q-&gt;next;
            p = p-&gt;next;
        }
    }
    q = p-&gt;next;
    L1-&gt;next = q;
    p-&gt;next = NULL;
    L = reverse3(L);
    L1 = reverse3(L1);
    q-&gt;next = L-&gt;next;
    free(L);
    L = L1;
    return L;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;$$ Algorithm \space IV$$ 优化算法&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void reverse4(Linklist L) {
    node *p, *q;
    p = L-&gt;next;
    L-&gt;next = NULL;
    while(p) {
        q = p-&gt;next;
        p-&gt;next = L-&gt;next;
        L-&gt;next = p;
        p = q;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;栈 $$\mathcal{LIFO} $$&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;进栈 push&lt;/strong&gt; 向栈中插入一个元素&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;出栈 pop&lt;/strong&gt; 从栈删除一个元素&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;栈顶&lt;/strong&gt; 允许插入、删除元素的一端&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;栈顶元素&lt;/strong&gt; 处在栈顶位置的元素（表尾元素）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;栈底&lt;/strong&gt; 不允许插入、删除元素的一端（表头）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;空栈&lt;/strong&gt; 不含元素的栈&lt;/p&gt;
&lt;h3&gt;基本结构&lt;/h3&gt;
&lt;h4&gt;顺序静态结构&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;//静态分配
typedef struct {
    ElemType elem[LENGTH];
    int top;
} SqStack;
SqStack S;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;顺序动态结构&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;//动态分配
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef struct {
    ELemType *base;
    int top;
    int stacksize;
} SqStack;
void InitStack(SqStack *S) {
    S.base = (ElemType *)malloc(STACK_INIT_SIZE * sizeof(ElemType));
    S.top = 0;
    S.stacksize = STACK_INIT_SIZE;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;顺序栈方法&lt;/h3&gt;
&lt;h4&gt;进栈 $$\mathcal{Push} $$&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status Push(SqStack *S, ElemType e) {
    if(S.top &gt;= S.stacksize) {	//动态栈的话
        ElemType *newbase = (ElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(ElemType));
        if(!newbase) {
            return OVERFLOW;
        }
        S.base = newbase;
        S.stacksize += STACKINCREMENT;
    }
    S.base[S.top] = e;
    S.top++;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;出栈 $$\mathcal{Pop}$$&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status Pop(SqStack &amp;#x26;S, ElemType &amp;#x26;e) {	//使用e来容纳pop出的数据
    if(S.top == 0) return OVERFLOW;
    S.top--;
    e = S.base[top];
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;链式栈方法&lt;/h3&gt;
&lt;h4&gt;链式栈结构&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;//这里的基本结构我加了两个自定义的名称
typedef struct node {
    ElemType data;
    struct node* next;
} Unit, *StackNode, *top = NULL;	//初始化置top为空栈
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;链式栈的 $$\mathcal{Push}$$ 方法&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;StackNode Push_link(StackNode top, ElemType e) {	//本质上是对链表做了头插
    StackNode *p;
    int length = sizeof(Unit);
    p = (StackNode)malloc(length);
    p-&gt;data = e;
    p-&gt;next = top;
    top = p;
    return top;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;链式栈的 $$ \mathcal{Pop}$$ 方法&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;StackNode Pop_link(StackNode top, ElemType *e) {	//本质是对链表做了头删
    StackNode p;
    if(top == NULL) return NULL;
    p = top;
    (*e) = p-&gt;data;
    top = top-&gt;next;
    free(p);
    return top;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;栈的应用&lt;/h3&gt;
&lt;h4&gt;数值转换&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char * base_convert(int x, int base) {
    SqStack S; int e; InitStack(S);
    while(x != 0) {
        Push(S, x % base);
        x /= base;
    }
    char * res = (char *)malloc((S.top + 1) * sizeof(char));
    int i = 0;
    int *e = &amp;#x26;i;
    while(Pop(S, e) == OK)
        res[i++] = (char)e + 48;
    res[i] = &apos;\0&apos;;
    return res;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;括号匹配&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int bracket_match(char brackets[]) {
    SqStack S;
    ElemType e;
    InitStack(S);
    for(int i = 0; i &amp;#x3C; strlen(brackets); i++) {
        char c = brackets[i], x;
        switch(c) {
            case &apos;)&apos;:
                if(Pop(S, x) == OK &amp;#x26;&amp;#x26; x == &apos;(&apos;) break;
                else return false;
            case &apos;]&apos;:
                if(Pop(S, x) == OK &amp;#x26;&amp;#x26; x == &apos;[&apos;) break;
                else return false;
            case &apos;}&apos;:
                //...
            dafault:
                Push(S, c);
        }
    }
    return StackEmpty(S);	//判断栈是否为空
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;表达式求值&lt;/h4&gt;
&lt;p&gt;运算符优先级关系表（其中 &lt;code&gt;#&lt;/code&gt; 用来标记程序是否运行完毕）&lt;/p&gt;
&lt;p&gt;| S1\S2 |  +   |  -   |  *   |  /   |  (   |  )   |  #   |
| :---: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
|   +   |  &gt;   |  &gt;   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |  &gt;   |  &gt;   |
|   -   |  &gt;   |  &gt;   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |  &gt;   |  &gt;   |
|   *   |  &gt;   |  &gt;   |  &gt;   |  &gt;   |  &amp;#x3C;   |  &gt;   |  &gt;   |
|   /   |  &gt;   |  &gt;   |  &gt;   |  &gt;   |  &amp;#x3C;   |  &gt;   |  &gt;   |
|   (   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |  =   |      |
|   )   |  &gt;   |  &gt;   |  &gt;   |  &gt;   |      |  &gt;   |  &gt;   |
|   #   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |  &amp;#x3C;   |      |  =   |&lt;/p&gt;
&lt;p&gt;上表中，左侧代表第一个符号，上方代表第二个符号。由以上的关系可以得到下方的表达式求值方法&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int eval(char *s) {
    SqStack_Int s1;
    SqStack_Char s2;
    InitStack(s1); InitStack(s2);
    int i = -1, result;
    Push(s2, s[++i]);
    char w = s[++i], e;
    while(w != &apos;#&apos; || GetTop(s2, e) == OK &amp;#x26;&amp;#x26; e != &apos;#&apos;) {
        if(&apos;0&apos; &amp;#x3C;= w &amp;#x26;&amp;#x26; w &amp;#x3C;= &apos;9&apos;) {
			int num = 0;
            while(&apos;0&apos; &amp;#x3C;= w &amp;#x26;&amp;#x26; w &amp;#x3C;= &apos;9&apos;) {
                num = num * 10 + (w - &apos;0&apos;); w = s[++i];
            }
            Push(s1, num);	//压入数字栈
        }
    }
    else {
        GetTop(s2, e); int res = prior(e, w);	//依靠上面的表判断那个符号优先级大
        if(res == -1) Push(s2, w); w = s[++i];
        else if(res == 0 &amp;#x26;&amp;#x26; w == &apos;)&apos;) Pop(s2, e); w = s[++i];	//优先级等于w，去括号
        else if(res == 1) {	//鹅嘞神，你这都比w优先级大了，赶紧计算
            int a = 0, b = 0;	//准备两个运算数存储位置
            Pop(s2, e); Pop(s1, b);Pop(s1, a);	//注意这里先出的是b，因为栈是LIFO表
            switch(e) {
                case &apos;+&apos;: Push(s1, a+b); break;
                case &apos;-&apos;: Push(s1, a-b); break;
                case &apos;*&apos;: Push(s1, a*b); break;
                case &apos;/&apos;: Push(s1, a/b); break;
            }
        }
        else {
            return ERROR;
        }
    }
    GetTop(s1, result); return result;
}

//用来判断符号优先级大小的函数，因为纯纯队史所以我只是抄来看看，并没有什么大用
int prior(char a, char b) {
    if(a == &apos;+&apos; || a == &apos;-&apos;) {
        if(b == &apos;*&apos; || b == &apos;/&apos; || b == &apos;(&apos;) return -1;
        else return 1;
    }
    else if(a == &apos;*&apos; || a == &apos;/&apos;) {
        if(b == &apos;(&apos;) return -1;
        else return 1;
    }
    else if(a == &apos;(&apos;) {
        if(b == &apos;)&apos;) return 0;
        else if(b == &apos;#&apos;) return ERROR;
        else return -1;
    }
    else if(a == &apos;)&apos;) {
        if(b == &apos;(&apos;) return ERROR;
        else return 1;
    }
    else if(a == &apos;#&apos;) {
        if(b == &apos;#&apos;) return 0;
        else if(b == &apos;)&apos;) return ERROR;
        else return -1;
    }
    return ERROR;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;队列&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;空队列&lt;/strong&gt; 不含元素的队列&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;队首&lt;/strong&gt; 队列中只允许删除元素的一端。一般称为 &lt;em&gt;&lt;strong&gt;front&lt;/strong&gt;&lt;/em&gt; 或 &lt;em&gt;&lt;strong&gt;head&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;队尾&lt;/strong&gt; 队列中只允许插入元素的一端。一般称为 &lt;em&gt;&lt;strong&gt;rear&lt;/strong&gt;&lt;/em&gt; 或 &lt;em&gt;&lt;strong&gt;tail&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;队首元素&lt;/strong&gt; 处于队首的元素&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;队尾元素&lt;/strong&gt; 处于队尾的元素&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;进队&lt;/strong&gt; 插入一个元素到队列中。也称为入队&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;出队&lt;/strong&gt; 从队列中删除一个元素&lt;/p&gt;
&lt;h3&gt;顺序基本结构&lt;/h3&gt;
&lt;h4&gt;静态顺序存储结构&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define MAXLENGTH 100
typedef struct {
    ELemType elem[MAXLENGTH];
    int front, rear;
} SeQueue;
SeQueue Q;	//定义一个结构变量Q表示队列
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;//动态顺序存储结构（书上没提，照猫画虎）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define MAXLENGTH 100
#define INCREMENT 10
typedef struct {
    ElemType *elem;
    int front, rear;
} SeQueue;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;顺序队列方法&lt;/h3&gt;
&lt;h4&gt;入队 $$ En_Queue$$&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status En_Queue(SqQueue &amp;#x26;Q, ElemType e) {
    if((Q.rear + 1) % MAXLENGTH == Q.front) return OVERFLOW;
    Q.elem[Q.rear] = e;
    Q.rear++;
    Q.rear %= MAXLENGTH;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;出队 $$ De_Queue $$&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status De_Queue(SqQueue &amp;#x26;Q, ElemType e) {
    if(Q.front == Q.rear) return ERROR;	//队列为空
    e = Q.elem[Q.front];
    Q.front = (Q.front + 1) % MAXLENGTH;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;链式存储结构&lt;/h3&gt;
&lt;h4&gt;存放元素的节点类型定义&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct Qnode {
    ELemType data;
    struct Qnode *next;
} Qnode, *QnodePtr;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;由头尾元素构成的链表基本定义&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
    Qnode *front;
    Qnode *rear;
} LinkQueue;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;空队列生成方法&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define LENGTH sizeof(Qnode)
void InitQueue(LinkQueue Q) {
    Q.front = Q.rear = (QueuePtr)malloc(LENGTH);
    Q.front-&gt;next = NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;队列插入方法&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status EnQueue(LinkQueue &amp;#x26;Q, ElemType e) {
    Qnode *p = (Qnoode *)malloc(sizeof(LENGTH));
    if(!p) return ERROR;
    p-&gt;data = e;
    p-&gt;next = NULL;
    Q.rear-&gt;next = p;
    Q.rear = p;
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;队列删除算法&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Status DeQueue(LinkQueue &amp;#x26;Q, ElemType e) {
    if(Q.front == Q.rear) return ERROR;
    Queue *p = Q.front-&gt;next;
    Q.front-&gt;next = p-&gt;next;
    e = p-&gt;data;
    if(Q.rear == p) Q.rear = Q.front;
    free(p);
    return OK;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;栈的应用实例&lt;/h3&gt;
&lt;h4&gt;编码&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;现在有一种简易的编码规则为 k[encoded_string] ，它表示方括号内部的 encoded_string 刚好重复 k 次、&lt;/p&gt;
&lt;p&gt;例如，字符串 2[ab]3[c]def 表示的字符串是 ababcccdef&lt;/p&gt;
&lt;p&gt;另外，这种编码允许嵌套，解码的时候需要由左向右，由内到外进行嵌套。&lt;/p&gt;
&lt;p&gt;例如，字符串 3[a2[bc]]2[d] 表示的字符串是 abcbcabcbcabcbcdd&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;/* Decoder */
string decode(string str) {
    SqStack_Int s1;
    SqStack_String s2;
    InitStack(s1); InitStack(s2);
    for(int i = 0; i &amp;#x3C; str.length(); i++) {
        char ch = str[i];
        int num = 0;
		if(isdigit(ch)) {
            while(isdigit(ch)) {
                num = num * 10 + (ch - &apos;0&apos;);
                ch = str[++i];
            }
            Push(s1, num);
            i--;	//退出非数字字符
        }
        else if(isalpha(ch)) {
            Push(s2, string(1, ch));
        }
        else if(ch == &apos;]&apos;) {
            Pop(s1, num);
            string top = &quot;&quot;, tmp = &quot;&quot;, repeat = &quot;&quot;;
            while(Pop(s2, top) &amp;#x26;&amp;#x26; top != &quot;[&quot;) tmp = top + tmp;	//利用字符串加法反置串
            while(num--) repeat += tmp;
            Push(s2, repeat);
        }
    }
    string res = &quot;&quot;, tmp = &quot;&quot;;
    while(Pop(s2, tmp)) res = tmp + res;
    return res;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;队列的应用实例&lt;/h3&gt;
&lt;h4&gt;银行排队叫号系统&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;问题背景：在银行办理业务时，客户依次取号进入客户队列排队，然后根据银行广播提示，到指定的空闲窗口办理业务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct Customer {
    int index;	//序号
    int window;	//窗口
    int time;	//模拟时长
} Customer;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void bank_service(int n, int serviceTime) {
    LinkQueue wait_queue; InitQueue(wait_queue);
    Customer *windows[n];
    for(int i = 0; i &amp;#x3C; n; i++) windows[i] = (Customer *)malloc(sizeof(Customer));
    int windowsStatus[n];
    for(int i = 0; i &amp;#x3C; n; i++) windows_status[i] = 0;	//可以用memset
    int idx = 0;
    for(int t = 0; t &amp;#x3C; serviceTime || !QueueEmpty(wait_queue) || !AllWindowsEmpty; t++) {
        printf(&quot;time now: %d\n&quot;, t);
        if(t &amp;#x3C; service) {
            if(rand() % (1 + n)) {	//客户以 n / (n+1) 的概率到达
                Customer c; c.index = ++idx;
                c.time = 1 + rand() % 5;
                EnQueue(wait_queue, c);
                printf(&quot;%d 号客户入队，模拟时长为%d\n&quot;, idx, c.time);
            }
        }
        for(int i = 0; i &amp;#x3C; n; i++) {
            if(WindowsStatus[i]) {
                if(--windows[i]-&gt;time &amp;#x3C;= 0) {
                    printf(&quot;%d 号客户离开窗口%d&quot;, windows[i]-&gt;index, windows[i]-&gt;window);
                    WindowsStatus[i] = 0;
                }
            }
            if(!WindowsStatus[i] &amp;#x26;&amp;#x26; !QueueEmpty(wait_queue)) {
                DeQueue(wait_queue, *windows[i]);
                windows[i]-&gt;window = i+1; windowsStatus[i] = 1;
                printf(&quot;请 %d 号客户到 %d 号窗口办理业务&quot;, windows[i]-&gt;index, windows[i]-&gt;window);
            }
        } printf(&quot;\n&quot;);
    }
    for(int i = 0; i &amp;#x3C; n; i++) free(windows[i]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;字符串&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;字符串&lt;/strong&gt; 由零个或者多个字符组成的有限序列，一般记为 $ S = \space &quot;a_1 \space a_2 \space a_3 ...a_n&quot; $&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;串值&lt;/strong&gt; 双引号内的内容&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;串长&lt;/strong&gt; n 的大小&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;空串&lt;/strong&gt; n = 0&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;空格串&lt;/strong&gt; 仅含若干空格的字符串&lt;/p&gt;
&lt;h3&gt;存储结构&lt;/h3&gt;
&lt;h4&gt;静态存储分配的字符串&lt;/h4&gt;
&lt;p&gt;也被称为串的&lt;strong&gt;定长顺序存储表示&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define MAXLENGTH 256
typedef unsigned char SeqString[MAXLENGTH];
SeqString S;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时候可能有人要问了，主播主播，你这个有点太简单了，我们数据结构应该实现的东西不是应该都非常具有结构吗？&lt;/p&gt;
&lt;p&gt;有的，结构我们是有的，毕竟我们在这本书里已经学过了 &lt;strong&gt;顺序表&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
    unsigned char ch[MAXLENGTH];
    int length;
} SeqString;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;动态存储分配的字符串&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
    unsigned char *ch;
    int length;
} HString;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;串的链式存储&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct node {
    char data;
    struct node *next;
} LinkStrNode, *LinkString;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里引入存储密度的概念，其公式为：
$$
\rho = \frac{StringUnit}{ActualUnit} \times 100%
$$
因此如果按以上的方式存储字符串，那么存储的无用信息比有用的还多，那就很神人了。&lt;/p&gt;
&lt;p&gt;怎么提升这个存储密度呢？在一个节点里多塞几个信息就完了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define NODESIZE 4
typedef struct node {
    char data[NODESIZE];
    struct node *next;
} LinkStrNode, *LinkString;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候字符串增添啥的岂不是很麻烦啊！没事，反正也不用这种方式做算法~大模拟会写就完了&lt;/p&gt;
&lt;h3&gt;字符串的模式匹配算法&lt;/h3&gt;
&lt;h4&gt;朴素的模式匹配算法&lt;/h4&gt;
&lt;p&gt;比较简单粗暴，大不了就一个一个字母进行比较嘛~怎么确定这个字符串完整的出现过了？从第一位开始一个一个比较就可以了，总体的一个一个比较需要拿另一个 index 来存储&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int StrIndex(SeqString S, SeqString T, int pos) {
    int i, j;
    for(int i = pos, j = 1; i &amp;#x3C;= S[0] - T[0] + 1;i++, j++) {
        if(S.ch[i] != T.ch[j]) {	//完蛋，居然不一样
            i = i - j + 1;	//外层index回到之前的位置
            j = 0;	//内层index置0
        }
        else if(j == T.ch[0]) return i-j+1;	//匹配位置
    }
    return 0;	//没找到啊？？
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;KMP模式匹配算法&lt;/h4&gt;
&lt;p&gt;不难发现，前面那个算法会导致每次一遇到不一样的就得回滚，实在是太麻烦了我天。那么有没有匹配的时候还能够灵活的调整匹配区间的算法呢？&lt;/p&gt;
&lt;p&gt;假如一个字符串里面某个位置和前缀的某些元素相同，那么我可以通过存下相同前缀的位置来减少回滚的长度。&lt;/p&gt;
&lt;p&gt;例如 ababc 这个字符串，不难发现abab里面都有 ab，那么遇到不一样的字符时可以首先考虑&lt;strong&gt;一直到对比位置的前缀相同位置是否都符合条件&lt;/strong&gt;，以此来减少滚动长度。&lt;/p&gt;
&lt;p&gt;我们完全可以通过另外保存一个数组来实现这个功能，即记录下当前字符串位置的前缀相同位置。&lt;/p&gt;
&lt;p&gt;不妨将这个新的由位置组成的顺序表视作一个串，将其命名为&lt;strong&gt;next&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| Position j | 1    | 2    | 3    | 4    | 5    |
| ---------- | ---- | ---- | ---- | ---- | ---- |
| ModeString | a    | b    | a    | b    | c    |
| next[j]    | 0    | 1    | 1    | 2    | 1    |&lt;/p&gt;
&lt;p&gt;其中 $$ next$$ 数组应该满足
$$
next_j = k \Leftrightarrow t_1t_2t_3...t_k = t_{j-k+1}t_{j-k+2}t_{j-k+3}...t_{j}
$$
这个 next 数组怎么求呢？&lt;/p&gt;
&lt;h3&gt;基于不同存储类型实现的一些其它字符串方法&lt;/h3&gt;
&lt;h4&gt;串插入方法&lt;/h4&gt;
&lt;h4&gt;串比较方法&lt;/h4&gt;
&lt;h4&gt;串赋值算法&lt;/h4&gt;
&lt;h4&gt;求子串方法&lt;/h4&gt;
&lt;h4&gt;串联接方法&lt;/h4&gt;
&lt;h2&gt;多维数组&lt;/h2&gt;
&lt;h2&gt;广义表&lt;/h2&gt;
&lt;h2&gt;树&lt;/h2&gt;
&lt;h2&gt;二叉树&lt;/h2&gt;
&lt;h2&gt;图（结构）&lt;/h2&gt;
&lt;h2&gt;图的最小生成树问题&lt;/h2&gt;
&lt;h2&gt;图的最短路径问题&lt;/h2&gt;
&lt;h2&gt;排序&lt;/h2&gt;
&lt;h2&gt;查找&lt;/h2&gt;
&lt;h2&gt;大数据&lt;/h2&gt;
&lt;h2&gt;后记&lt;/h2&gt;</content:encoded></item><item><title>Post-000</title><link>https://nkns.cc/notes/blog/post-000</link><guid isPermaLink="true">https://nkns.cc/notes/blog/post-000</guid><description>It was my first post in Hexo</description><pubDate>Sat, 25 Jan 2025 21:08:11 GMT</pubDate><content:encoded>&lt;p&gt;你好！这里是&lt;em&gt;Nakano_mk&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;非常高兴现在你能看到这个网站正常运行的样子&lt;/strong&gt;。我非常想知道你对这个网站的看法如何，或者由你本人为我发送你在浏览这个页面的截图！&lt;/p&gt;
&lt;p&gt;不论如何，能够看到自己在短时间内学会的东西立竿见影的为我带来满足与收获，这已经足以让我兴奋不已。&lt;/p&gt;
&lt;p&gt;这个博客最初是在我回家那天搭建的。在候机的三个小时里，我经历了产生”是时候做个个人博客了“的想法、在互联网上寻找知识&amp;#x26;结合先前了解的一些知识、沿着路线顺利建站——这样一个完整而奇妙的过程。在候机厅里把笔记本放在腿上敲一敲，在世界的每一个地方——只要存在互联网，你的影响就能穿透到那里——这是一种美妙无比的感受，我觉得屏幕前的你也能感同身受！&lt;/p&gt;
&lt;p&gt;为了更加舒服的写这篇markdown，我还购买了高中时期就一直想买的&lt;em&gt;&lt;strong&gt;Typora&lt;/strong&gt;&lt;/em&gt;。其实这个软件也并不昂贵，不知道为啥我忍了这么久才买哈哈。&lt;/p&gt;
&lt;p&gt;非常感谢你能看到这里。我会在空闲时间学习一些搭建博客的技巧，相信未来这个博客能够变得更加成熟。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Jan 25, 2025&lt;/p&gt;
&lt;p&gt;In Tianjin&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item></channel></rss>