mud_sanc 板


LINE

作者 [email protected] (打混的蟑螂史巴克), 看板 Mud 标题 [蟑螂贺失恋] 中阶 LPC - 第七章 - 除错 时间 中山医学院BBS站 (Wed Jul 29 23:24:55 1998) ─────────────────────────────────────── 中阶 LPC Descartes of Borg November 1993 第七章: 除错 7.1 错误的种类 至今, 你大概已经到处碰过各式各样的错误. 一般上, 你可能看到的错误有三种: 编译时段错误 (compile time error) 、执行时段错误 (run time error) 、故 障的程式码 (malfunctioning code). 在大多数的 mud 中, 你会找到一个私人 的档案, 里头记录着你的编译时段错误. 对大多数人来说, 你可以在你的家 (home) 目录找到名叫 "log" 或 ".log" 的档案, 或在 "/log" 目录找到以你 的名字命名的档案. 另外, mud 执行时, 会维持一份执行时段错误的纪录. 而此 档案也在 "/log" 目录中. 对 MudOS mud 来说, 它叫做 "debug.log". 其他的 mud 中, 称为不同的名字, 像是 "lpmud.log". 如果你还不知道编译时段和执行 时段错误纪录在哪里, 请询问你的系统管理者. 编译时段错误是 driver 试着载入一个物件到记忆体的时候发生的错误. 如果此 时它看不懂你写的东西, 它会无法把物件载入记忆体, 并在你私人的错误纪录档 中记录为什麽它无法载入该物件. 最普遍的编译时段错误是打字错误、遗漏或多 加 () , {}, [], ""、没有正确宣告物件所使用的函式和变数. 执行时段错误是一个在记忆体中的物件, 当它执行某段叙述时所发生的错误. 举 例来说, driver 不可能知道任何情况下, "x/y" 是否有效. 实际上, 它是一个 有效的 LPC 运算式. 但是, 如果 y 的值为 0, 则会发生执行时段错误, 因为 你不能除以 0. 当 driver 执行一个函式时碰上错误, 它放弃执行函式并纪录在 游戏执行时段错误纪录档中. 如果有定义、如果玩家是创作者, driver 也会对 this_player() 显示错误讯息, 不然就只对玩家显示 "什麽 ?". 大多数导致执 行时段错误的原因, 是不正确的值和试着执行没有定义运算资料型态的运算式. 不过, 最狡猾的错误种类, 就是故障的程式码. 这些错误不会纪录下来, 因为 driver 永远不可能知道有地方出错. 简单地说, 这种错误就是你认为程式码做 的是一件事, 但是实际上它做的是另一件事. 常遇到这种错误的人, 一定会认定 是 mudlib 或 driver 的错误. 每个人都制造过各式各样的错误, 而更常见的不 是程式码不按照它该运作的的方式工作, 而是你错读它. 7.2 修正编译时段错误 编译时段错误是最常见以及最容易修正的错误. 新手程式撰写人常常因为一些怪 异的错误讯息, 而感到挫折. 虽然如此, 只要一个人变得习惯於他们 driver 产 生的错误讯息, 修正编译时段错误就成了例行公事. 在你的错误纪录中, driver 会告诉你错误的种类, 还有它最後在第几行注意到 该错误. 注意, 这不表示此行一定是错误实际发生的地方. 除了打字错误, 最常 见的编译时段错误是遗漏或多加各式括号和引号: (), [], {}, "". 这种是最常 困扰新手程式撰写人的错误, 因为 driver 不会注意到遗漏或多加的部分, 直到 稍後出问题为止. 以下是范例: 1 int test(string str) { 2 int x; 3 for(x =0; x<10; x++) 4 write(x+"\n"); 5 } 6 write("Done.\n"); 7 } 看你想做的是什麽, 此处实际上的错误在第三行 (表示你遗漏了一个 {) 或第五 行 (表示你多加一个 }) . 但是, driver 会回报它在第六行找到一个错误. 实 际的 driver 讯息每种 driver 可能都不一样, 但是不管是哪一种 driver, 你 会看到第六行产生一个错误. 因为第五行的 } 会解释为 test() 函式结束. 在 第六行, driver 看见你有一个 write() 出现在函式定义之外, 所以回报为错 误. 一般来说, driver 也会继续回报它在第七行找到一个多加 } 的错误. 修正这种错误的秘诀在於程式撰写风格. 将结束的 } 与该子句开头的 { 垂直 对齐, 在你除错时, 会让你看到你哪里遗漏它们. 同样, 当你使用多组括号时, 像这样用空白将各组分开: if( (x=sizeof(who=users()) > ( (y+z)/(a-b) + (-(random(7))) ) ) 你可以看到, for() 叙述的括号与其余的叙述以空白隔开. 另外, 个别的子群也 用空白隔开, 让它们在产生错误时易於找出. 一旦你拥有帮助你找出错误的程式撰写风格, 你就会学到哪一种错误讯息倾向於 指出哪一种错误. 修正此种错误时, 你会检查出问题的那一行之前与之後的程式 码. 大多数的情况下, 你会直接找到错误. 另一种普遍的编译时段错误是 driver 回报一个不明的 identifier. 一般来说, 打字错误和错误宣告变数导致此种错误. 幸运的是, 错误纪录档中几乎都能告诉 你错误所发生的实际位置. 所以修正此种错误时, 进入编辑程式并找到出问题的 该行. 如果该问题出在变数上而不是打字错误, 请确定你正确地宣告该变数. 另 一方面, 如果是打字错误, 就改正它 ! 但是, 小心一件事, 这种错误有时候会与遗漏括号的错误结合在一起. 在这种情 形下, 不明 identifier 的问题常常是误报. driver 误读 {} 或其他东西, 而 导致变数宣告混淆. 因此在烦恼此种错误困扰之前, 请确定已修正所有其他的编 译时段错误. 与前述的错误同一级的是普通的语法错误. 当 driver 无法了解你写的东西时, 它就产生此种错误. 这又常是打字错误引起的, 却也是因为不了解某些特徵正确 的语法所致, 像是把 for() 叙述写成这样: for(x=0, x<10, x++) 如果你像这样的错误, 却不是语法错误, 试着重新检查错误发生的叙述中, 语法 是否正确. 7.3 修正执行时段错误 执行时段错误比起编译时段错误要复杂得多. 幸运的是, 这些错误都有纪录, 但 是许多创作人并不了解, 或是他们不知道纪录在哪里. 执行时段错误的纪录一般 也纪录得比编译时段错误详细, 也就是你可以从它开始到它出错之处, 追踪执行 程序的过程. 所以你可以利用纪录档, 使用前编译器叙述 (precompiler statement) 设置除错陷阱 (debugging trap). 但是, 执行时段错误常肇因於 复杂的程式撰写技巧, 而初学者并不使用这些技巧. 这表示你一般会碰上比简单 的编译时段错误还要复杂的错误. 执行时段错误几乎都是肇因於使用错误的 LPC 资料型态. 最常见的是, 试着用 NULL 值的物件变数做外界呼叫, 索引指向 NULL 值的映射、阵列、字串变数, 或函式传入错误的参数. 我们看一个 Nightmare 真实的执行时段错误: Bad argument 1 to explode() 程式: bin/system/_grep.c, 物件: bin/system/_grep 第 32 行 ' cmd_hook' in ' std/living.c' (' std/user#4002') 第 83 行 ' cmd_grep' in ' bin/system/_grep.c' (' bin/system/_grep') 第 32 行 Bad argument 2 to message() 程式: adm/obj/simul_efun.c, 物件: adm/obj/simul_efun 第 34 行 ' cmd_hook' in ' std/living.c' (' std/user#4957') 第 83 行 ' cmd_look' in ' bin/mortal/_look.c' (' bin/mortal/_look') 第 23 行 ' examine_object' in ' bin/mortal/_look.c' (' bin/mortal/_look') 第 78 行 ' write' in 'adm/obj/simul_efun.c' (' adm/obj/simul_efun') 第 34 行 Bad argument 1 to call_other() 程式: bin/system/_clone.c, 物件: bin/system/_clone 第 25 行 ' cmd_hook' in ' std/living.c' (' std/user#3734') 第 83 行 ' cmd_clone' in ' bin/system/_clone.c' (' bin/system/_clone') 第 25 行 Illegal index 程式: std/monster.c, 物件: wizards/zaknaifen/spy#7205 第 76 行 ' heart_beat' in ' std/monster.c' ('wizards/zaknaifen/spy#7205') 第 76 行 除了最後一个以外, 所有的错误, 都对一个函式传入一个错误的参数. 第一个错 误, 是对 explode() 传入错误的第一个参数. explode() 外部函式要一个字串 当作第一个参数. 修正这类型的错误时, 我们会到 /bin/system/_grep.c 的第 32 行检查第一个传入参数到底其资料型态为何. 在此情况下, 传入的值应是字 串. 如果因为某些原因, 我实际上传入其他的东西, 我在此只要确定传入字串就能修 正错误. 但是在此情况要复杂得多. 我需要追踪传入 explode() 的变数值为何, 我才能知道传入 explode() 外部函式的值到底是什麽. 出问题的那行是: borg[files[i]] = regexp(explode(read_file(files[i]), "\n"), exp); files 是一个字串阵列, i 是整数, borg 是映射. 所以很明显, 我们需要找出 read_file(file[i]) 的值到底是什麽. 好, read_file() 这个外部函式传回一 个字串, 除非该档案根本不存在, 或是该物件没有权限读取该档案, 或是该档案 是个空的档案, 这些情形都会导致此函式传回 NULL. 很明显, 我们的问题是这 些情形的其中一种. 要找出是哪一种, 我们要看 file[i]. 检查程式码, 这个档案阵列透过 get_dir() 外部函式取得它的值. 如果该物件 有权限读取此目录, get_dir() 就传回目录中所有的档案. 所以问题不在於权限 不足或档案不存在. 导致这个错误的档案一定是空的. 而且事实上, 这就是导致 错误的原因. 要修正此错误, 我们要透过 filter_array() 外部函式传入档案, 确定只有档案大小大於 0 的档案可以读入阵列. 修正执行时段错误的关键在於, 了解有问题的所有变数值在产生错误之时, 它们 确实的值为何. 你阅读你的执行时段错误纪录时, 小心地经由错误发生的档案分 辨物件. 举个例子, 上面的索引错误是物件 /wizard/zaknaifen/spy 产生, 但 是错误发生在执行它所继承的 /std/monster.c 函式. 7.4 故障的程式码 你所遇到最阴险的问题, 就是你程式码的行为不是预期中的行为. 物件顺利载入, 没有产生任何执行时段错误, 但是事情就是不对劲. 既然 driver 不可能认出这 种错误, 就没有任何纪录. 所以你需要一行接一行浏览整个程式码, 并搞清楚到 底发生了什麽事. 第一步: 找出你已知能顺利执行的最後一行程式码. 第二步: 找出你已知开始出错的第一行程式码. 第三步: 从已知顺利执行的地方到第一个出错的地方, 检查程式码的流程. 常常, 这些问题出现於你使用 if() 叙述没有料到所有的可能情形. 举个例: int cmd(string tmp) { if(stringp(tmp)) return do_a() else if(intp(tmp)) return do_b() return 1; } 在此段程式码中, 我们发现它编译和执行起来没有问题. 问题是它执行起来完全 没作用. 我们确定 cmd() 函式已经执行, 所以我们可以从此着手. 我们也知道 实际上 cmd() 传回 1, 因为我们输入此命令时, 没看到 "什麽 ?" . 马上, 我 们可以看到因为某些原因, tmp 变数有字串或整数以外的值. 就此得到的答案是, 我们输入的命令没有参数, 所以 tmp 是 NULL , 并让所有的测试条件失败. 上面的例子相当简单, 几近於愚蠢. 但是, 它让你知道在修正故障的程式码时, 如何检查程式码的流程. 其他的工具能协助你除错. 最重要的工具就是使用前编 译器来除错. 以前面的例子来说, 我们有一个子句检查传入 cmd() 的整数. 我 们输入 "cmd 10" 时, 我们希望执行 do_b() . 进入回圈之前, 我们需要看 tmp 的值为何: #define DEBUG int cmd(string tmp) { #ifdef DEBUG write(tmp); #endif if(stringp(tmp)) return do_a(); else if(intp(tmp)) return do_b(); else return 1; } 在我们输入命令之後, 立刻就知道 tmp 的值是 "10" . 回头看程式码, 我们会 怪自己愚蠢, 忘了我们把 tmp 当整数使用之前, 必须要用 sscanf() 把命令参 数转换成整数. 7.5 总结 修正任何 LPC 问题的关键是, 永远要知道你程式码中任何一步的变数值为何. LPC 的执行降到变数值改变这种最简单的层级上, 所以程式码载入记忆体时, 不 正确的值导致错误发生. 如果你遇到函式有不正确的参数时, 常常是你对一个函 式传入 NULL 值的参数. 这情形常发生於物件, 因为大家常常会做以下的事: 1) 使用设定在一个物件中的值, 而该物件已经被摧毁. 2) 使用 this_player() 的传回值, 而根本没有 this_player(). 3) 使用 this_object() 的传回值, 而 this_object() 刚好已被摧毁. 另外, 大家会常常遇上不合法的索引 (illegal indexing) 或索引指向不合法的 型态 (indexing on illegal types). 最常见的是因为有问题的映射或阵列没有 初始化, 所以无法索引之. 关键在於了解出问题的地方, 其阵列和映射的完整值. 另外, 注意索引编号是否比阵列的长度还大. 最後, 使用前编译器暂时扔出或扔进显示变数值的程式码. 前编译器让你很容易 地删掉除错程式码. 你只需要在除错完毕之後, 删除 DEBUG 定义. Copyright (c) George Reese 1993 译者: Spock of the Final Frontier 98.Jul.29. 中阶 LPC 翻译全文可至 ftp ff.muds.net 4100 下载. 格式为 cintermediate.tar.gz, Windows 环境使用者可使用 Winzip 解开. -- Boldly go where no mudder has gone before... Spock (roach admin) 蟑螂管理员 homepage: http://bbs.csmc.edu.tw/spock/ From The Final Frontier 140.128.136.12 4000 ※ 来源:‧中山医学院BBS -- 絮情小站 bbs.csmc.edu.tw‧[FROM: www.csmc.edu.tw] --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 218.170.228.153







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:BabyMother站内搜寻

TOP