<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[行思随记]]></title> 
<atom:link href="https://melab.cc/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[大道行思，取则行远]]></description>
<link>https://melab.cc/</link>
<language>zh-cn</language>

<item>
    <title>寄存器基础概念</title>
    <link>https://melab.cc/post/258</link>
    <description><![CDATA[<p>寄存器是 CPU 内部的一小块高速存储，容量极小但读写几乎不耗时间。运算、传参、存返回值、记下一条指令地址，都要先经过它；调试和逆向时盯住寄存器，基本就能知道程序此刻在干什么。x86-64 上常见的几个：rax 放返回值，rdi、rsi、rdx 依次传前几个整数参数，rsp 指栈顶，rbp 指栈帧底，rip 指下一条要执行的指令。</p>
<p>用法就一句话：把程序丢进调试器，断点停下后直接打印寄存器，需要看数据时再按寄存器里的地址读内存。</p>
<p>最小示例：</p>
<pre><code class="language-bash">gcc -g -O0 demo.c -o demo   # 带调试信息编译，-O0 关优化，寄存器才和源码对得上
 gdb ./demo                  # 启动调试器
 # 进入 gdb 后依次输入：
 # break main               # 在 main 下断点
 # run                      # 跑起来，停在断点
 # info registers rip rsp rbp   # 查看关键寄存器
 # x/4i $rip                # 从 rip 开始反汇编 4 条指令
</code></pre>
<p>新手最容易踩的两个坑。一是编译时开了优化，变量被塞进寄存器甚至直接优化掉，调试器里打印变量会提示 optimized out，别以为调试器坏了，看寄存器本身就行。二是 rax、eax、ax、al 是同一个寄存器的不同宽度视图，写 eax 会把高 32 位清零，写 al 只动最低 8 位，读的时候别把它们当成四个独立变量。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 13:31:11 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/258</guid>
</item>
<item>
    <title>Lua 数据类型概览</title>
    <link>https://melab.cc/post/257</link>
    <description><![CDATA[<p>Lua 的值一共 8 种类型：nil、boolean、number、string、table、function、userdata、thread。用 <code>type()</code> 能拿到类型名字符串，判断类型就靠它。</p>
<ul>
<li><strong>nil</strong>：没赋过值的变量就是 nil；给表字段赋 nil 等于把这个字段删掉。</li>
<li><strong>boolean</strong>：只有 <code>true</code> 和 <code>false</code>。判断真假时，只有 <code>false</code> 和 <code>nil</code> 算假，<code>0</code> 和空字符串 <code>""</code> 都算真。</li>
<li><strong>number</strong>：整数和小数都算 number，直接写就行。</li>
<li><strong>string</strong>：单引号双引号都行，多行用 <code>[[ ]]</code>；<code>..</code> 拼接，<code>tonumber()</code> 转数字，<code>tostring()</code> 转字符串。</li>
<li><strong>table</strong>：唯一的结构类型，数组和字典都用它。默认索引从 1 开始，<code>#t</code> 取长度，<code>t.k</code> 和 <code>t["k"]</code> 是一回事。</li>
<li><strong>function</strong>：函数也是值，能存进变量、放进表、当参数传。</li>
<li><strong>thread</strong>：协程对象，<code>coroutine.create</code> 出来的东西。</li>
<li><strong>userdata</strong>：C 那边传进来的数据，脚本里自己造不出来。</li>
</ul>
<pre><code class="language-lua">-- type() 返回类型名字符串
print(type(nil))      -- nil
print(type(true))     -- boolean
print(type(10))       -- number
print(type("abc"))    -- string
print(type({}))       -- table
print(type(print))    -- function

local t = {1, 2, 3}   -- 数组写法，索引从 1 开始
print(#t)             -- 3
t.name = "lua"        -- 同时也能当字典用
print(t.name)         -- lua
t.name = nil          -- 赋 nil 就是删掉这个字段
print(t.name)         -- nil</code></pre>
<p>拿到值不确定类型时，先 <code>type(v) == "table"</code> 再往下用，比硬猜稳。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 13:17:00 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/257</guid>
</item>
<item>
    <title>session 与 cookie 区别</title>
    <link>https://melab.cc/post/256</link>
    <description><![CDATA[<p>cookie 把数据存在浏览器里，用户看得见也改得了，每次请求会自动带上；session 的数据存在服务端，浏览器只留一个 session_id，通常也是靠 cookie 传回去。</p>
<p>cookie 能设长有效期，关了浏览器还在；session 默认关浏览器就断，能留多久看服务端的清理配置。</p>
<p>cookie 只能存很小的数据，别放密码之类的东西；session 存服务端，能放更多内容，也相对安全。</p>
<p>用户禁了 cookie，session_id 传不过去，session 也跟着失效。判断登录状态一般用 session，记住账号这类小标记用 cookie。</p>
<pre><code class="language-php"><?php
// cookie：数据在浏览器，下次请求自动带回
setcookie("user", "tom", time() + 3600, "/"); // 有效期 1 小时
echo $_COOKIE["user"] ?? "没有";

// session：数据在服务端，浏览器只留 session_id
session_start();
$_SESSION["user"] = "tom";
echo $_SESSION["user"];

session_destroy(); // 清掉服务端会话
</code></pre>]]></description>
    <pubDate>Thu, 12 Nov 2026 13:16:54 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/256</guid>
</item>
<item>
    <title>Lua 冒号调用与点调用区别</title>
    <link>https://melab.cc/post/255</link>
    <description><![CDATA[<p>冒号调用是语法糖：<code>obj:fn(a)</code> 完全等同于 <code>obj.fn(obj, a)</code>，它自动把 obj 当作第一个参数 self 传进去；点调用则得自己把对象手动传一遍。定义时规矩一样，冒号定义自动带 self 形参，点定义要自己写 self。</p>
<pre><code class="language-lua">local obj = { name = "Tom" }

-- 点定义：self 要自己写在参数里
function obj.say1(self, msg)
  print(self.name .. msg)
end

-- 冒号定义：self 是隐含的第一个参数
function obj:say2(msg)
  print(self.name .. msg)
end

obj.say1(obj, "你好")   -- 点调用，手动传 self
obj:say2("你好")        -- 冒号调用，自动传 self
obj.say2(obj, "你好")   -- 和上一行等价</code></pre>
<p>记住一条：定义用冒号，调用也用冒号，最省事。定义用冒号、调用用点，self 就变成了第一个实参，容易报错；定义用点、调用用冒号，则会多传一个参数，参数个数对不上。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 13:16:52 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/255</guid>
</item>
<item>
    <title>spl_autoload_register 用法</title>
    <link>https://melab.cc/post/254</link>
    <description><![CDATA[<p>spl_autoload_register 用来挂一个自动加载函数：代码里 new 一个还没引入的类时，PHP 会按注册顺序回调这个函数，把类名当参数传进来，你在函数里按类名找文件 require 进来就行，不用到处写 require。</p>
<pre><code class="language-php"><?php
// 注册一个匿名函数当加载器
spl_autoload_register(function ($class) {
    $file = __DIR__ . '/classes/' . $class . '.php';
    if (is_file($file)) {
        require $file; // 找到文件才引入
    }
    // 找不到就什么都不做，交给后面的加载器
});

$obj = new User(); // 自动去 classes/User.php 找
</code></pre>
<p>类名带命名空间时，把反斜杠换成目录分隔符再拼路径：</p>
<pre><code class="language-php"><?php
spl_autoload_register(function ($class) {
    $file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
    if (is_file($file)) {
        require $file;
    }
});

new App\Model\User(); // 对应 App/Model/User.php
</code></pre>
<p>第三个参数传 true 会把加载器插到队列最前面，优先执行；第一个参数也可以直接写函数名字符串，比如 spl_autoload_register('myLoader')。想看已经注册了哪些，用 spl_autoload_functions()；想撤掉某个，用 spl_autoload_unregister()。加载函数里不要抛异常，找不到文件就安静返回，让下一个加载器接着找。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 11:10:40 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/254</guid>
</item>
<item>
    <title>本地模型启动失败排查</title>
    <link>https://melab.cc/post/253</link>
    <description><![CDATA[<p>本地模型启动失败排查，就是模型在自己机器上跑不起来时，按错误信息把路径、显存、端口、启动参数逐项排掉。</p>
<p>先看日志第一条报错，确认模型目录里有配置和权重文件；再查显存或内存够不够、端口有没有被占用；接着把启动参数减到最少再试。</p>
<pre><code class="language-bash"># 1. 确认模型目录和关键文件
ls -lh /path/to/model

# 2. 看显存占用；没有独显就跳过
nvidia-smi

# 3. 看端口是否被占用，比如 8000
ss -ltnp | grep 8000
</code></pre>
<p>新手常踩的坑：只盯最后一行报错，真正原因往往在前面；模型文件没下全或路径写错，日志里也会表现为加载失败，别一上来就怀疑参数。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 10:15:21 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/253</guid>
</item>
<item>
    <title>Lua 模式匹配基础</title>
    <link>https://melab.cc/post/252</link>
    <description><![CDATA[<p>Lua 没有正则表达式，用的是模式串，写法更短。四个函数够用：<code>string.find</code> 返回起止下标，<code>string.match</code> 返回匹配到的内容，<code>string.gmatch</code> 循环取出全部匹配，<code>string.gsub</code> 做替换。</p>
<p>常用模式字符：<code>%a</code> 字母、<code>%d</code> 数字、<code>%s</code> 空白、<code>%w</code> 字母或数字，改成大写就是取反，比如 <code>%D</code> 表示非数字。魔法字符要加 <code>%</code> 转义，比如 <code>%%</code> 表示百分号本身。量词：<code>*</code> 表示零次以上，<code>+</code> 表示一次以上，<code>-</code> 表示零次以上但尽量短，<code>?</code> 表示零次或一次。<code>[]</code> 是字符集，<code>[^]</code> 表示取反，<code>^</code> 锚定开头，<code>$</code> 锚定结尾，<code>()</code> 用来捕获。</p>
<pre><code class="language-lua">local s = "name=Tom, age=18"

-- () 捕获，只取括号里的部分
print(string.match(s, "name=(%a+)"))  -- Tom
print(string.match(s, "age=(%d+)"))   -- 18

-- find 返回起止下标
print(string.find(s, "Tom"))          -- 6  8

-- gmatch 逐个取出所有匹配
for k, v in string.gmatch(s, "(%a+)=(%w+)") do
  print(k, v)
end

-- gsub 替换，%1 %2 引用捕获内容
print(string.gsub("a1b2", "(%a)(%d)", "%2%1"))  -- 1a2b  2
</code></pre>
<p><code>-</code> 和 <code>*</code> 都是零次以上，区别是 <code>-</code> 匹配最短，抓标签、引号里的内容用它更稳。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 09:05:51 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/252</guid>
</item>
<item>
    <title>AI 审查结果误报处理</title>
    <link>https://melab.cc/post/251</link>
    <description><![CDATA[<p>AI 审查工具会对着代码改动提问题，但有些提示是误报：它只看到改动片段，不知道这段代码的调用方、项目约定或测试数据来源。处理办法是把误报变成项目规则，而不是每次都手动忽略。</p>
<p>步骤：先拉出这条告警对应的文件、函数和调用方配置，一起提供给审查工具，让它看到完整上下文；再判断能否用一句话解释“这里为什么没问题”，解释不了就先当作真问题去查；确认是误报后，写进项目规则文件或忽略配置，必须带上原因和适用路径；改造完成后重跑一次审查，确认同类告警消失，且没有把新代码也忽略掉。</p>
<pre><code class="language-json">{
  "suppress": [
    {
      "rule": "hardcoded-credential", // 告警规则标识
      "path": "test/fixtures/**", // 只忽略这个目录，别写全仓库
      "reason": "测试夹具里的假数据，不含真实密钥", // 必填，后来人靠它判断
      "owner": "backend" // 谁负责复查这条豁免
    }
  ]
}</code></pre>
<p>新手最容易出错的地方有两个：一是只写忽略、不写原因，几个月后没人敢删，这条误报就成了真空档；二是忽略范围放得太宽，直接按规则名全局关闭，把真问题一起忽略掉。忽略条目要精确到“规则 + 路径”，范围越小越安全。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 07:05:35 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/251</guid>
</item>
<item>
    <title>Lua string.gsub 替换</title>
    <link>https://melab.cc/post/250</link>
    <description><![CDATA[<p>string.gsub 就是全局替换，把字符串里所有匹配到的部分换成指定内容。写法是 string.gsub(s, 模式, 替换内容, 次数)，返回两个值：替换后的新字符串和替换次数；原字符串不会变，结果要记得接收。</p>
<p>模式用的是 Lua 自己的一套规则：%d 数字、%a 字母、%s 空白、%w 字母数字。替换内容里 %0 代表整个匹配，%1 代表第一个捕获。</p>
<pre><code class="language-lua">local s = "a1b2c3"
local new, n = string.gsub(s, "%d", "#")  -- 把数字都换成 #
print(new, n)                             -- a#b#c#  3

-- 第四个参数限制替换次数，只换前 2 个
print(string.gsub(s, "%d", "#", 2))      -- a#b#c3  2</code></pre>
<p>想按捕获内容调换位置，就用括号分组；模式里匹配真正的点必须写成 %.，不然 . 是通配符。</p>
<pre><code class="language-lua">-- 把 key=value 换序成 value:key
print(string.gsub("a=1,b=2", "(%w+)=(%w+)", "%2:%1"))  -- 1:a,2:b

-- 匹配真正的点号
print(string.gsub("1.2.3", "%.", "-"))                  -- 1-2-3</code></pre>
<p>替换内容还能传函数或表：传函数时，函数收到匹配到的内容，返回值就是替换值，返回 nil 保持原样；传表时，用匹配到的内容去查表取值。做批量关键词替换用表最省事。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 07:00:32 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/250</guid>
</item>
<item>
    <title>char 与 varchar 区别</title>
    <link>https://melab.cc/post/249</link>
    <description><![CDATA[<p>CHAR 是定长，VARCHAR 是变长，这是两者唯一的根本区别。</p>
<p>CHAR(n) 不管实际存几个字符，都固定占用 n 个字符的位置，长度不够时会在末尾用空格补齐；在检索取值时，MySQL 会把尾部空格去掉。n 最大为 255。适合长度基本固定的字段，比如手机号、省份编码、MD5 值。</p>
<p>VARCHAR(n) 只占用实际字符数，再加 1 到 2 字节的长度前缀，用来记录实际长度；当长度不超过 255 字节时用 1 字节，超过则用 2 字节。这里的 n 是最大字符数，不是字节数。理论上限 65535 字节，实际还受单行大小限制。适合长度差别很大的字段，比如昵称、地址、标题。</p>
<p>建表时的选择原则是：长度固定就用 CHAR，其余一律用 VARCHAR，不要为了方便而全表使用 CHAR。</p>
<pre><code class="language-sql">CREATE TABLE user (
  id INT PRIMARY KEY AUTO_INCREMENT,
  phone CHAR(11) NOT NULL,        -- 固定 11 位，用 CHAR
  nickname VARCHAR(30) NOT NULL,  -- 长度不定，用 VARCHAR
  intro VARCHAR(200) DEFAULT ''   -- 长短差别大，用 VARCHAR
);</code></pre>
<p>有一点需要注意：VARCHAR 会保留末尾的空格，CHAR 不会。</p>]]></description>
    <pubDate>Thu, 12 Nov 2026 04:50:22 +0800</pubDate>
    <dc:creator>Alexer</dc:creator>
    <guid>https://melab.cc/post/249</guid>
</item>
</channel>
</rss>