您正在从Perl 5.6.2查看此文档的版本。查看最新版本

目录

名称

perlhack-如何破解Perl内部

说明

本文档试图解释Perl开发是如何进行的,最后为希望成为真正的搬运工的人提供了一些建议。

perl5-porters邮件列表是维护和开发Perl标准发行版的地方。根据辩论的激烈程度,这个列表每天可以收到10到150条消息。大多数情况下,一次会讨论两三个补丁、扩展、功能或错误。

列表的可搜索存档位于以下位置之一:

http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/

http://archive.develooper.com/perl5-porters@perl.org网站/

列表订户(搬运工自己)有几种风格。有些人是安静好奇的潜伏者,他们很少参与进来,而是关注正在进行的开发,以确保他们事先得到关于Perl中新更改或功能的警告。一些是供应商的代表,他们在那里确保Perl继续在他们的平台上编译和工作。有些人会修补他们知道如何修复的任何报告错误,有些人正在积极修补他们的宠物区域(线程、Win32、regexp引擎),而其他人似乎什么也不做,只是抱怨。换句话说,这是您通常的技术人员组合。

拉里·沃尔(Larry Wall)掌管着这群搬运工。他对Perl语言中的哪些内容可以更改,哪些内容不可以更改拥有最终决定权。Perl的各种版本都由一个“pumpking”来管理,这个“pumpling”是一个负责收集补丁程序的搬运工,根据补丁程序的特性来决定什么将进入和不进入这个版本。例如,Gurusamy Sarathy是Perl 5.6版本的泵送者,Jarkko Hietaniemi是5.8版本的水泵送者,Hugo van der Sanden将是5.10版本的水泵输送者。

此外,不同的人在为不同的事情抽水。例如,Andy Dougherty和Jarkko Hietaniemi分享配置南瓜。

Larry认为Perl的发展符合美国政府的方针:有立法机构(搬运工)、行政部门(抽水机)和最高法院(Larry)。立法机构可以随意讨论并向行政部门提交补丁,但行政部门可以自由否决。很少有情况下,最高法院会站在行政部门一边而不是立法机构一边,或者站在立法机构一边而不是行政部门。然而,大多数情况下,立法机关和行政部门应该和睦相处,在没有弹劾或法庭案件的情况下解决分歧。

您有时可能会看到对规则1和规则2的引用。Larry作为最高法院的权力在《规则:

  1. Larry对Perl应该如何表现的定义总是正确的。这意味着他对核心功能拥有最终否决权。

  2. 拉里被允许在以后改变对任何事情的想法,无论他之前是否援引了规则1。

明白了吗?拉里总是对的,即使他错了。很少看到这两条规则都得到了执行,但它们经常被提及。

该语言的新特性和扩展存在争议,因为抽水站、Larry和其他搬运工用于决定应实现和合并哪些特性的标准并没有像其他一些语言那样编入一些小的设计目标中。相反,启发式是灵活的,通常很难理解。以下是一个人的启发式列表,大致按重要性递减顺序排列,这些启发式必须与新功能进行权衡:

概念是否符合Perl的总体目标?

这些都不是用石头写的,但一个近似值是:

1.保持快速、简单和有用。2.保持特征/概念尽可能正交。3.没有任意限制(平台、数据大小、文化)。4.在任何地方都可以使用/patch/ampository Perl,让它保持开放和令人兴奋。5.要么吸收新技术,要么为它们架起桥梁。
实施在哪里?

没有实现,世界上所有的谈论都是无用的。几乎在任何情况下,支持新功能的人都应该是实现它的人。能够编码新功能的搬运工有他们自己的日程安排,无法实现你的(可能是好的)想法。

向后兼容性

破坏现有的Perl程序是一大罪过。新的警告是有争议的——一些人说发出警告的程序没有被破坏,而另一些人则说是这样。添加关键字有可能破坏程序,更改现有标记序列或函数的含义可能会破坏程序。

可以改为模块吗?

Perl5具有扩展机制、模块和XS,特别是为了避免不断更改Perl解释器。您可以编写导出函数的模块,您可以提供这些函数的原型,以便像内置函数一样调用它们,如果您想实现真正复杂的东西,甚至可以编写XS代码来处理Perl解释器的运行时数据结构。如果它可以在模块中完成,而不是在核心中完成,那么就极不可能添加它。

该功能是否足够通用?

这是只有提交者想要添加到语言中的东西吗,还是会有广泛的用途?有时,搬运工可能会决定等到有人实现更通用的功能时再添加功能,而不是集中精力添加功能。例如,搬运工没有实施“延迟评估”功能,而是在等待一个允许延迟评估等更多功能的宏观系统。

它是否可能引入新的错误?

彻底重写大量Perl解释器可能会引入新的错误。更改越小、越本地化,效果越好。

它是否排除了其他需要的功能?

如果一个补丁关闭了未来的发展途径,它很可能会被拒绝。例如,一个在原型上放置真实最终解释的补丁可能会被拒绝,因为对于原型的未来仍有一些选项尚未解决。

实施是否稳健?

好的补丁(严密的代码、完整的、正确的)更有可能被使用。松散或不正确的补丁可能被放在次要位置,直到泵有时间修复为止,或者可能在没有进一步通知的情况下被完全丢弃。

实现是否通用到可以移植?

最差的补丁程序使用特定于系统的功能。Perl语言中不可移植的添加内容不太可能被接受。

实现是否经过测试?

改变行为(修复错误或引入新功能)的补丁必须包括回归测试,以验证一切是否如预期那样工作。如果没有原始作者提供的测试,其他任何人在将来更改perl时如何确保他们没有无意中破坏补丁实现的行为?如果没有测试,补丁的作者怎么能确信他/她的辛勤工作在补丁中不会在将来被人意外抛弃?

有足够的文件吗?

没有文档的补丁可能是考虑不周或不完整的。没有文档就无法添加任何内容,因此为相应的手册页和源代码提交补丁总是一个好主意。

还有别的方法吗?

Larry说“尽管Perl口号是有多种方法,我犹豫是否要用10种方法来做某事。然而,这是一个棘手的启发式问题——一个人的本质附加是另一个人的毫无意义的累赘。

它创造了太多的工作吗?

为泵工作,为Perl程序员工作,为模块作者工作。。。Perl应该很容易。

补丁胜于雄辩

工作代码总是比pie-in-the-sky想法更受欢迎。与随机功能请求相比,添加功能的补丁更有可能被添加到语言中,无论请求多么激烈。这与“它有用吗?”联系在一起,因为有人花了很多时间来制作补丁,这表明了他们对这个功能的强烈渴望。

如果你在名单上,你可能会听到“核心”这个词到处都是。它是指标准分布``对核心“”进行黑客攻击意味着您正在将C源代码更改为Perl解释器``核心模块“”是Perl附带的模块。

保持同步

Perl解释器的不同版本的源代码保存在由修订控制系统(目前是Perforce程序,请参阅http://perforce.com网站/ ). 抽水机和其他一些设备可以访问存储库以签入更改。Perl开发版本的泵将定期发布一个新版本,以便其余的搬运工可以看到发生了什么变化。存储库主干的当前状态,以及描述自上次公开发布以来发生的单个更改的修补程序,都可以从以下位置获得:

http://public.activestate.com/gsar/APC/ftp://ftp.linux.activestate.com/pub/staff/gsar/APC/

如果您正在寻找一个特定的更改,或一个影响特定文件集的更改,您可能会发现Perl存储库浏览器有用的:

http://public.activestate.com/cgi-bin/perlbrowse

您可能还想订阅perl5-changes邮件列表,以接收提交给perl存储库的维护和开发“分支”的每个补丁的副本。请参见http://lists.perl.org/获取订阅信息。

如果您是perl5-porters邮件列表的成员,最好与最新的更改保持联系。如果不仅仅是为了验证您将要发布的错误报告是否在最新的可用perl开发分支中还没有得到解决,该分支也称为perl-current、bleading edge perl、bleedperl或bleadperl。

不用说,perl-current中的源代码通常处于永久的进化状态。你应该预料到它会很麻烦。将其用于测试和开发以外的任何目的。

可以通过多种方式与最新分支保持同步,但最方便和可靠的方式是使用远程同步,网址为ftp://rsync.samba.org/pub/rsync/ . (您也可以通过FTP获取最新的分支。)

如果您选择使用rsync保持同步,有两种方法可以做到这一点:

同步源树

假设您位于perl源所在的目录中,并且已安装并可用rsync,则可以使用以下命令“升级”到bleadperl:

#rsync-avzrsync://ftp.linux.activestate.com/perl-current/ .

这需要将源代码树中的每一项更新到最新应用的修补程序级别,创建新的文件(对于您的发行版),并设置现有文件的日期/时间戳以反映bleadperl状态。

请注意,这不会删除“”中的任何文件在rsync之前。确定rsync正确运行后,使用--delete和--dry-run选项运行它,如下所示:

#rsync-avz--delete--dry-runrsync://ftp.linux.activestate.com/perl-current/ .

这将模拟rsync运行还删除了bleadperl主副本中不存在的文件。仔细观察这次运行的结果。如果您确信实际运行不会删除任何对您来说珍贵的文件,那么您可以删除“--dry-run”选项。

您可以通过查看文件来检查应用的最新补丁.补丁,它将显示最新修补程序的编号。

如果您有多台机器需要保持同步,并且不是所有机器都可以访问WAN(因此您无法将所有源树与实际源同步),那么有一些方法可以解决这个问题。

在LAN上使用rsync

设置本地rsync服务器,使rsync源树可用于LAN,并根据此目录同步其他计算机。

发件人http://rsync.samba.org/README.html :

“Rsync使用rsh或ssh进行通信setuid和不需要特殊的安装权限。不需要inetd条目或守护程序。然而,你必须,有一个工作的rsh或ssh系统。建议使用ssh它的安全功能。"
使用推送NFS

在NFS上挂载其他系统后,您可以采取主动推送方法,将刚刚更新的树与其他未同步的树进行比较。例如:

#!/usr/bin/perl-w使用严格;使用文件::复制;我的%MF=地图{米/(\S+)/;$1=>[(统计$1)[2,7,9]];#模式、大小、时间}`cat MANIFEST`;my%remote=map{$_=>“/$_/pro/3gl/CAN/perl5.7.1”}qw(主机1主机2);foreach my$host(密钥%remote){除非(-d$remote{$host}){打印STDERR“无法对主机$host进行Xsync”;下一步;}foreach my$文件(键%MF){my$rfile=“$remote{$host}/$file”;my($mode,$size,$mtime)=(stat$rfile)[2,7,9];定义了$size或($mode,$size,$mtime)=(0,0,0);$size==$MF{$file}[1]和$mtime==$NF{$file{[2]和下一个;打印f“%4s%-34s%8d%9d%8d%9d\n”,$host,$file,$MF{$file}[1],$MF}$file}[2],$size,$mtime;取消链接$rfile;复制($file,$rfile);实用时间,$MF{$file}[2],$rfile;chmod$MF{$file}[0],$rfile;}}

虽然这并不完美。可以通过在更新之前检查文件校验和来改进。并非所有NFS系统都支持可靠的utime支持(在NFS上使用时)。

同步补丁

源树由对树中的文件应用补丁的抽运程序维护。这些补丁要么是由抽水机自己使用差异-c手动更新文件或应用perl5-porters列表中海报发送的补丁后。这些补丁也会被保存并且可以进行rsync,因此您可以将它们自己应用到源文件中。

假设您位于补丁所在的目录中,您可以将它们与同步

#rsync-avzrsync://ftp.linux.activestate.com/perl-current-diffs/ .

这样可以确保将最新可用的修补程序下载到修补程序目录中。

然后由您使用类似的东西应用这些补丁

#last=`ls-t*.gz|sed q`#rsync-avzrsync://ftp.linux.activestate.com/perl-current-diffs/ .#找到-名称“*.gz”-较新的$last-exec gzcat{}\;>漂白补片#光盘/过电流#补丁-p1-N</perl当前diffs/blead.patch

或者,由于这只是对其工作原理的提示,因此可以使用Andreas König的CPAN-patchaperl更好地控制修补过程。

为什么rsync源树

rsync源树更容易

由于您不必自己应用修补程序,因此可以确保源树中的所有文件都处于正确的状态。

它更可靠

虽然可rsync的源代码和修补程序区域每几分钟都会自动更新一次,但请记住,应用修补程序有时可能意味着需要小心地手动操作,特别是如果您的版本补丁程序不知道如何处理新文件、具有8位字符的文件或没有尾随换行符的文件。

为什么rsync补丁程序

rsync补丁更容易

如果您有多台机器需要使用bleadperl跟踪,那么只需对补丁进行一次rsync,然后将它们应用到不同机器上的所有源树就更容易了。

如果您试图在5台不同的机器上保持同步,其中只有一台机器可以访问WAN,那么应该在NFS上同步所有源树5次。只需对补丁进行一次rsync,我就可以将它们自动应用于所有源树。需要你多说些什么吗?-)

这是一个很好的参考

如果您不仅想拥有最新的开发分支,还想修理bug或扩展特性,您需要深入研究源代码。如果你是一个经验丰富的perl核心潜水员,你不需要任何手册、提示、路线图、perlguts.pod或其他辅助工具就能找到你的方向。但如果你是初学者,这些补丁可能会帮助你找到应该从哪里开始,以及如何改变困扰你的地方。

文件变化在抽油机将其视为自己的小同步点时进行更新。在这些情况下,他会发布当前源代码树的tar-ball(即。perl@7582.tar.gz),这将是选择使用“rsync-the-patches”方案时的一个很好的起点。从开始珍珠@7582,这意味着最新应用的修补程序编号为7582的一组源文件,您可以应用此后可用的所有后续修补程序(7583、7584…)。

您可以在以后将这些补丁用作一种搜索存档。

寻找起点

如果您想修复/更改函数/功能Foo的行为,只需扫描补丁,寻找主题、注释或补丁正文中提到Foo的补丁。这个补丁很可能会向您显示受该补丁影响的文件,这些文件很可能是您深入了解perl内核的起点。

查找如何修复错误

如果你找到了哪里Foo的功能/特性表现不好,但您不知道如何修复(但您确实知道要进行的更改),您可以再次仔细阅读补丁以了解类似的更改,并查看其他人如何应用修复。

寻找不当行为的根源

当你与bleadperl保持同步时,抽水机会很乐意看见社区的努力真的奏效了。因此,在他的每个同步点之后,你要“进行测试”,检查是否一切都正常。如果是,您可以“确认”,这将向perlbug@perl.org。(如果您没有从刚刚成功完成“make test”的系统访问邮件程序的权限,可以执行“make okfile”,它会创建文件珀尔.ok,你可以把它带到你最喜欢的邮递员那里自己邮寄)。

当然,和往常一样,事情不会总是通向成功之路,一个或多个测试也不会通过“制造测试”。在发送错误报告之前(使用“make nok”或“make notkfile”),如果其他人已经报告了错误,请检查邮件列表,如果是,请回复该消息进行确认。如果没有,你可能想追查这种不当行为的根源之前发送错误,这将有助于所有其他搬运工找到解决方案。

这里保存的补丁非常方便。您可以检查补丁程序列表,查看哪个补丁程序更改了哪个文件,以及是什么更改导致了错误行为。如果您在错误报告中注意到这一点,它会保存试图解决它的错误,并查找该点。

如果搜索补丁太麻烦,您可以考虑使用perl的bugtron来查找有关已发布错误的讨论和漫谈的更多信息。

如果您想在这两个方面都达到最佳效果,请rsync源代码树以方便、可靠和容易,rsync补丁以供参考。

Perlbug给药

使用RT公司 错误跟踪程序系统,由维护罗伯特·斯皮尔。成为一名管理员,关闭所有可以使用粘性手套的错误:

http://rt.perl.org

的bugtracker机制珍珠5特别是以下错误:

http://bugs6.perl.org/perlbug

向错误系统管理员发送电子邮件:

“perlbug-admin”<perlbug-admin@perl.org>

提交修补程序

始终将修补程序提交给perl5-porters@perl.org。如果您正在修补核心模块,并且列出了作者,请向作者发送一份副本(请参阅“修补核心模块”). 这让其他搬运工可以查看您的修补程序,从而捕获修补程序中数量惊人的错误。使用diff程序(可从以下位置获得源代码形式ftp://ftp.gnu.org/pub/gnu/,或使用Johan Vromas的临时修补程序(可从以下位置获得注册会计师协会/作者/id/JV/). 首选统一差异,但接受上下文差异。不要发送RCS样式的差异或没有上下文行的差异。更多信息请参见移植/修补podPerl源代码发行版中的文件。请根据最新版本进行修补发展版本(例如,如果您正在修复5.005曲目中的错误,请针对最新的5.005_5x版本进行修补)。只有经受住开发分支热度的补丁才能应用于维护版本。

您的补丁程序应该更新文档和测试套件。请参见“编写测试”.

要报告Perl中的错误,请使用该程序珍珠贝Perl附带的(如果您无法使Perl工作,请将邮件发送到该地址perlbug@perl.orgperlbug@perl.com). 通过报告错误佩尔布格输入到自动错误跟踪系统中,可通过以下网址访问该系统:http://bugs.perl.org/ . 检查perl5-porters邮件列表的存档,看看您报告的错误是否以前被报告过,如果是,是否被认为是错误,这通常是值得的。有关可搜索档案的位置,请参见上文。

CPAN测试仪(http://testers.cpan.org/)是一组志愿者,他们在各种平台上测试CPAN模块。Perl吸烟者(http://archives.devlooper.com/daily-build@org/)在具有各种配置的平台上自动测试perl源代码版本。这两项努力都欢迎志愿者。

在插嘴之前,阅读并潜伏一段时间是一个好主意。这样你可以看到对话的动态,了解球员的性格,并希望在你开口说话时能更好地做出有益的贡献。

如果在所有这些之后,您仍然认为您想加入perl5-porters邮件列表,请发送邮件至perl5-porters-subscribe@perl.org。要取消订阅,请发送邮件至perl5-porters-unsubscribe@perl.org.

要攻击Perl内核,您需要阅读以下内容:

珍珠胶

这一点至关重要,因为它是Perl源代码中内容的文档。反复阅读几次,它可能会开始有意义——如果还没有,不用担心,因为研究它的最佳方法是结合阅读Perl源代码,我们稍后会这样做。

您可能还想看看Gisle Aas的带插图的perlguts——不能保证它绝对是Perl核心中最新文档的最新版本,但基本原理是正确的。( http://gisle.aas.no/perl/illguts/ )

珀尔克斯图特珍珠色

XSUB编程的实用知识对核心黑客非常有用;XSUB使用从PP代码中提取的技术,PP代码是实际执行Perl程序的内脏部分。从简单的例子和解释中学习这些技术比从核心本身学习要温和得多。

珀拉皮

Perl API的文档解释了一些内部函数的功能,以及源代码中使用的许多宏。

端口/泵.pod

这是一本关于Perl搬运工的智慧之言集;其中一些只对南瓜架有用,但大多数适用于任何想要进行Perl开发的人。

perl5-porters常见问题解答

这应该可以从以下地址获得http://simon-cozens.org/writings/p5p-faq ; 或者,您可以通过向以下地址发送邮件,将常见问题通过电子邮件发送给您perl5-porters-faq@perl.org。它包含关于阅读perl5 porters的提示,关于perl5 porters如何工作以及Perl开发一般如何工作的信息。

找到你的路

Perl维护可以分为多个区域,每个区域都由特定人员(南瓜)负责。这些区域有时对应于源工具包中的文件或目录。这些领域包括:

核心模块

作为Perl内核的一部分提供的模块图书馆/扩展名/子目录:图书馆/用于pure-Perl模块,并且扩展名/包含核心XS模块。

测验

几乎所有模块、内置模块和主要功能都有测试。测试文件都有.t后缀。模块测试在图书馆/扩展名/被测试模块旁边的目录。其他人住在t吨/。请参阅“编写测试”

文档

文档维护包括在吊舱/目录,(以及贡献新文档)和核心模块的文档。

配置

配置过程是我们使Perl在其支持的无数操作系统中可移植的方式。配置、构建和安装过程的责任,以及核心代码的整体可移植性都由configure pumpk承担,其他人可以帮助处理各个操作系统。

涉及的文件是操作系统目录(赢32/,操作系统2/,虚拟机/等等)生成的shell脚本配置。小时生成文件以及生成的元配置文件配置(核心发行版中不包括metaconfig。)

口译员

当然,还有Perl解释器本身的核心。让我们更详细地看一下。

不过,在我们离开之前,请不要忘记这一点显示不仅包含Perl发行版中的文件名,还包含对其中内容的简短描述。有关重要文件的概述,请尝试以下操作:

perl-lne'在/^[^\/]+\时打印。[ch]\s+/'管理

口译员的要素

解释器的工作分为两个主要阶段:将代码编译成内部表示或字节码,然后执行它。perlguts中的“编译代码”解释编译阶段是如何发生的。

下面是perl操作的简短细分:

启动

操作开始于珀尔曼。c(c).(或miniperlmain公司。c(c)对于miniperl)这是非常高级的代码,足以放在一个屏幕上,它类似于珀尔嵌入; 大多数真实的动作发生在珀尔。c(c)

第一,珀尔曼。c(c)分配一些内存并构造Perl解释器:

1 PERL_SYS_INIT3(&argc,&argv,&env);23如果(!PL_do_undump){4 my_perl=perl_alloc();5如果(!my_perl)6出口(1);每构建7个(my_perl);8 PL_perl_destruct_level=0;9 }

第1行是一个宏,其定义取决于您的操作系统。第3行参考PL_卸载,一个全局变量-Perl中的所有全局变量都以开头损益_。这告诉您当前运行的程序是否是用-u个标记为perl,然后隆起这意味着它在任何理智的情况下都是错误的。

第4行调用中的函数珀尔。c(c)为Perl解释器分配内存。这是一个非常简单的函数,其内部看起来像这样:

my_perl=(PerlInterpreter*)PerlMem_malloc(sizeof(PerlInterpreter));

在这里,您可以看到Perl系统抽象的一个示例,我们稍后将看到:PerlMem_分配是您的系统的malloc公司,或Perl自己的malloc公司定义见马洛克。c(c)如果您在配置时选择了该选项。

接下来,在第7行中,我们构造解释器;这将设置Perl需要的所有特殊变量、堆栈等。

现在,我们向Perl传递命令行选项,并告诉它执行以下操作:

exitstatus=perl_parse(my_perl,xs_init,argc,argv,(char**)NULL);if(!exitstatus){exitstatus=perl_run(my_perl);}

perl_parse实际上是一个包装器解析正文(_P),定义见珀尔。c(c),它处理命令行选项,设置任何静态链接的XS模块,打开程序并调用yyparse(年解析)来解析它。

正在分析

此阶段的目标是获取Perl源代码,并将其转换为操作树。我们稍后会看到其中一个是什么样子的。严格地说,这里发生了三件事。

yyparse(年解析),解析器,位于佩利。c(c),尽管您最好在中阅读原始的YACC输入珍珠般的。(是的,弗吉尼亚州,在那里Perl的YACC语法!)解析器的工作是获取代码并“理解”它,将其拆分为句子,决定哪些操作数与哪些操作符一起使用,等等。

语法分析器得到了lexer的大力协助,它将您的输入分成标记,并决定每个标记是什么类型的东西:变量名、运算符、裸词、子例程、核心函数等等yylex公司,它及其相关例程可以在中找到托克。c(c).Perl与其他计算机语言不太相似;有时它对上下文非常敏感,很难确定某个标记是什么类型的,或者标记的结束位置。因此,标记器和解析器之间有很多相互作用,如果你不习惯的话,这可能会非常可怕。

当解析器理解Perl程序时,它会建立一个操作树,供解释器在执行过程中执行。构造和链接各种操作的例程可以在op.c公司,稍后将进行检查。

优化

现在,解析阶段已经完成,完成的树表示Perl解释器执行程序所需执行的操作。接下来,Perl对树进行了一次试运行,以寻找优化:常量表达式,如3 + 4将立即计算,优化器还将查看是否可以用单个操作替换任何多个操作。例如,要获取变量$foo美元而不是抓球*foo公司在查看标量组件时,优化器会修改操作树,以使用一个函数直接查找有问题的标量。主要优化器是偷窥在里面op.c公司许多操作系统都有自己的优化功能。

正在运行

现在我们终于准备好了:我们已经编译了Perl字节码,剩下的工作就是运行它运行操作标准中的函数运行。c(c); 更具体地说,它是由这三条看起来很无辜的线条完成的:

while((PL_op=CALL_FPTR(PL_op->op_paddr)(aTHX)){PERL_ASYNC_CHECK();}

您可能会更熟悉Perl版本:

当$PERL::op=&{$PERL::op->{function}}时,执行PERL_ASYNC_CHECK();

嗯,也许不是。无论如何,每个操作都包含一个函数指针,它规定了实际执行操作的函数。此函数将返回序列中的下一个操作-这允许如下操作如果在运行时动态选择下一个操作。这个性能_ASYNC_CHECK如果需要,确保信号之类的东西会中断执行。

实际调用的函数称为PP代码,它们分布在四个文件之间:pp_热。c(c)包含最常用且高度优化的“hot”代码,pp_sys(秒)。c(c)包含所有系统特定功能,第页_共页。c(c)包含实现控制结构的函数(如果,虽然等)和第c页包含其他所有内容。如果您愿意,这些是Perl内置函数和运算符的C代码。

内部变量类型

你现在应该看看珍珠胶,它告诉您Perl的内部变量类型:SV、HV、AV和其他类型。如果没有,现在就这样做。

这些变量不仅用于表示Perl-space变量,还用于表示代码中的任何常量,以及一些完全位于Perl内部的结构。例如,符号表是一个普通的Perl散列。您的代码在读入解析器时由SV表示;您调用的任何程序文件都是通过普通Perl文件句柄等打开的。

核心Devel::Peek公司该模块允许我们检查Perl程序中的SV。例如,让我们看看Perl如何处理常量“你好”.

%perl-MDevel::Peek-e“转储(“hello”)”1 SV=0xa04ecbc时的PV(0xa041450)2参考值=13个标志=(POK,只读,pPOK)4 PV=0xa0484e0“你好”\05电流=56长度=6

阅读Devel::Peek公司输出需要一些练习,所以让我们逐行进行。

第1行告诉我们,我们正在查看一个居住在0xa04ecbc(欧洲银行代码)内存中。SV本身是非常简单的结构,但它们包含指向更复杂结构的指针。在本例中,它是一个PV,一个在位置处保存字符串值的结构0xa041450第2行是参考计数;这个数据没有其他引用,所以它是1。

第3行是这个SV的标志-可以将其用作PV,它是只读SV(因为它是一个常量),数据在内部是PV。接下来,我们得到了字符串的内容,从位置开始0xa0484e0.

第5行给出了字符串的当前长度-注意这是包括null终止符。第6行不是字符串的长度,而是当前分配的缓冲区的长度;随着字符串的增长,Perl通过一个名为SvGROW公司.

你可以很容易地从C得到这些量中的任何一个;只需添加瑞典到代码段中显示的字段的名称,您将得到一个宏,它将返回值:SvCUR(sv)返回字符串的当前长度,SvREF计数(sv)返回引用计数,SvPV(sv,len)返回字符串本身及其长度等。可以在中找到更多用于操作这些属性的宏珍珠胶.

让我们以操作PV为例sv_catpvn,英寸sv.c公司

1个空隙2 Perl_sv_catpvn(pTHX_寄存器sv*sv,寄存器const char*ptr,寄存器STRLEN len)3  {4斯特林顿;5个字符*垃圾;6垃圾=SvPV_force(sv,tlen);7 SvGROW(sv,tlen+len+1);8如果(ptr==垃圾)9 ptr=SvPVX(sv);10移动(ptr,SvPVX(sv)+tlen,len,char);11 SvCUR(sv)+=长度;12*SvEND(sv)=“\0”;13(无效)SvPOK_only_UTF8(sv);/*验证指针*/14服务(sv);15  }

这是一个添加字符串的函数,脉冲重复频率,长度为伦恩存储在sv公司。我们在第6行中要做的第一件事是确保SV通过调用服务PV_强制宏以强制PV。作为副作用,特伦设置为PV的当前值,PV本身返回到废旧物品.

在第7行中,我们确保SV有足够的空间容纳旧字符串、新字符串和空终止符。如果伦恩不够大,SvGROW公司将为我们重新分配空间。

现在,如果废旧物品与我们试图添加的字符串相同,我们可以直接从SV获取字符串;SvPVX公司是SV中PV的地址。

10号线进行实际的连接:移动宏移动内存块:我们移动字符串脉冲重复频率到PV的末尾-这是PV的开始加上当前长度。我们要搬家了伦恩字节类型烧焦。完成此操作后,我们需要通过更改来告诉Perl我们已经扩展了字符串电流以反映新的长度。服务端是一个宏,它给出了字符串的结尾,因此需要是"\0".

第13行操纵旗帜;由于我们更改了PV,任何IV或NV值都将不再有效:如果我们更改了$a=10$a.=“6”;我们不想使用旧的IV值10。服务POK_only_utf8是支持UTF-8的特殊版本仅SvPOK_,一个关闭IOK和NOK标志并打开POK的宏。决赛SvTAINT公司是一个宏,如果启用了污染模式,它将清洗受污染的数据。

AV和HV更复杂,但SV是目前最常见的变量类型。在了解了如何操作这些操作之后,让我们继续看看操作树是如何构建的。

Op树

首先,操作树是什么?操作树是程序的解析表示,正如我们在解析一节中看到的那样,它是Perl执行程序所经历的操作序列,如我们在中看到的“正在运行”.

op是Perl可以执行的基本操作:所有内置函数和操作符都是ops,还有一系列ops处理解释器内部需要的概念——输入和输出块、结束语句、获取变量等等。

操作树以两种方式连接:可以想象有两条“路径”通过它,两种顺序可以遍历树。首先,解析顺序反映了解析器如何理解代码,其次,执行顺序告诉perl执行操作的顺序。

检查操作树的最简单方法是在完成解析后停止Perl,并让它转储树。这正是编译器的后端B: :特蕾丝,B: :简明扼要B: :调试这样做。

让我们看看Perl是如何看待的$a=$b+$c:

%perl-MO=Terse-e'$a=$b+$c'1 LISTOP(0x8179888)离开2 OP(0x81798b0)输入3 COP(0x8179850)下一状态4个BINOP(0x8179828)符号5 BINOP(0x8179800)添加[1]6 UNOP(0x81796e0)空[15]7 SVOP(0x80fafe0)gvsv GV(0x80fa4cc)*b8 UNOP(0x81797e0)空[15]9 SVOP(0x8179700)gvsv GV(0x80efeb0)*c10 UNOP(0x816b4f0)空[15]11 SVOP(0x816dcf0)gvsv GV(0x80fa460)*a

让我们从中间开始,在第4行。这是一个二进制运算符BINOP,它位于0x8179828号。所讨论的特定操作员是腰带-标量赋值-您可以在函数中找到实现它的代码pp_分配在里面pp_热。c(c)。作为一个二进制运算符,它有两个子运算符:add运算符,提供$b+$c,位于第5行的最上方,左侧位于第10行。

第10行是空操作:这完全不起作用。那在那里做什么?如果您看到null op,则表明解析后某些内容已被优化。正如我们在中提到的“优化”,优化阶段有时会将两个操作转换为一个操作,例如在获取标量变量时。发生这种情况时,与其重写操作树并清除悬空指针,不如用空操作替换冗余操作。最初,树应该是这样的:

10 SVOP(0x816b4f0)rv2sv[15]11 SVOP(0x816dcf0)gv gv(0x80fa460)*a

也就是说,取项,然后查看其标量分量:全球vsv(pp_gvsv病毒进入之内pp_热。c(c))碰巧做了这两件事。

从第5行开始的右侧与我们刚才看到的类似:我们有添加操作(pp_添加也在pp_hot。c(c))将两个相加全球vsv第条。

现在,这是关于什么的?

1 LISTOP(0x8179888)离开2 OP(0x81798b0)输入3 COP(0x8179850)下一状态

进入离开正在确定操作的范围,他们的工作是在每次进入和离开块时执行任何内务管理:词法变量被整理,未引用的变量被销毁,等等。每个程序都有前三行:离开是一个列表,其子项是块中的所有语句。语句由分隔下一状态,因此块是下一状态ops,每个语句要执行的ops是下一状态.进入是用作标记的单个op。

这就是Perl从上到下解析程序的方式:

程序|声明|=/ \/   \美元+/ \b$c美元

然而,这是不可能的执行操作顺序如下:必须找到十亿美元$c美元例如,在你把它们加在一起之前。因此,贯穿操作树的另一个线程是执行顺序:每个操作都有一个字段操作_下一步它指向下一个要运行的操作,因此遵循这些指针可以告诉我们perl是如何执行代码的。我们可以使用执行官选择B: :特蕾丝:

%perl-MO=Terse,exec-e'$a=$b+$c'1 OP(0x8179928)输入2 COP(0x81798c8)下一状态3 SVOP(0x81796c8)gvsv GV(0x80fa4d4)*b4 SVOP(0x8179798)gvsv GV(0x80efeb0)*c5 BINOP(0x8179878)添加[1]6 SVOP(0x816dd38)gvsv GV(0x80fa468)*a7 BINOP(0x81798a0)符号8 LISTOP(0x8179900)离开

这可能对人类更有意义:输入一个块,开始一条语句。获取的值十亿美元$c美元,并将它们相加。查找美元,并将一个分配给另一个。然后离开。

Perl在解析过程中构建这些操作树的方式可以通过检查珍珠般的。,YACC语法。让我们来完成构建树所需的部分$a=$b+$c

1术语:术语分配术语2{$$=newASSIGNOP(OPf_STACKED,$1,$2,$3);}3 |期限ADDOP期限4{$$=newBINOP($2,0,标量($1),标数($3));}

如果你不习惯阅读BNF语法,这就是它的工作原理:你可以通过标记器获得某些内容,通常以大写结尾。在这里,ADDOP公司标记器看到时提供+在代码中。分配在以下情况下提供=用于分配。这些是“终端符号”,因为没有比它们更简单的了。

上面片段的第一行和第三行语法告诉您如何构建更复杂的表单。这些复杂形式的“非终结符”通常用小写。学期这是一个非终结符符号,表示单个表达式。

语法给了你以下规则:如果你看到右边所有的东西都是按顺序排列的,你可以把它放在冒号的左边。这被称为“减少”,解析的目的是完全减少输入。有几种不同的方法可以执行缩减,并用竖线分隔:因此,学期然后=然后学期生成学期,以及学期然后+然后学期也可以使学期.

所以,如果你看到两个术语=+,您可以将它们转换为单个表达式。当您这样做时,您将在下一行执行块中的代码:如果您看到=,您将执行第2行中的代码。如果你看到+,您将执行第4行中的代码。正是这段代码促成了操作树。

|术语ADDOP术语{$$=newBINOP($2,0,标量($1),标数($3));}

这样做的目的是创建一个新的二进制操作,并为其提供一些变量。变量指的是标记:$1是输入中的第一个标记,$2第二个,以此类推&考虑正则表达式的反向引用。$$是此缩减返回的op。所以,我们打电话给新BINOP创建一个新的二进制运算符。的第一个参数新BINOP,中的函数op.c公司,是操作类型。它是一个加法运算符,所以我们希望类型为ADDOP公司。我们可以直接指定它,但它正好作为输入中的第二个标记,所以我们使用$2。第二个参数是op的标志:0表示“无特殊情况”。然后要添加的内容:在标量上下文中,表达式的左侧和右侧。

堆栈

当perl执行以下操作时添加(addop),它如何将结果传递给下一个操作?答案是,通过使用堆栈。Perl有许多堆栈来存储它当前正在处理的内容,我们将在这里查看三个最重要的堆栈。

参数堆栈

参数被传递到PP代码,并使用参数堆栈从PP代码返回,装货单。处理参数的典型方法是将它们从堆栈中弹出,按您的意愿处理它们,然后将结果推回到堆栈中。例如,余弦操作符就是这样工作的:

NV值;值=POPn;value=Perl_cos(值);XPUSHn(值);

当我们在下面考虑Perl的宏时,我们将看到一个更复杂的例子。POPn(流行音乐)为您提供堆栈上顶部SV的NV(浮点值):x美元在里面成本($x)然后计算余弦,并将结果作为NV返回。这个X(X)在里面XPUSHn公司意味着如果需要的话应该扩展堆栈-这里不需要,因为我们知道堆栈上还有一个项目的空间,因为我们刚刚删除了一个!这个XPUSH公司*宏至少可以保证安全。

或者,您可以直接摆弄堆栈:服务提供商为您提供堆栈部分中的第一个元素,并且顶部*为您提供堆栈上的顶部SV/IV/NV/等。例如,要对整数进行一元求反:

SETi(-TOPi);

只需将顶部堆栈项的整数值设置为其负值即可。

核心中的参数堆栈操作与XSUB中的完全相同-请参阅珀尔克斯图特,珍珠色珍珠胶有关堆栈操作中使用的宏的详细说明。

标记堆栈

我在上面说“你的堆栈部分”,因为PP代码不一定能把整个堆栈都传递给它自己:如果你的函数调用另一个函数,你只想公开针对被调用函数的参数,而不一定让它获取你自己的数据。我们这样做的方法是有一个“虚拟的”自下而上的包,向每个函数公开。标记堆栈将书签保存到每个函数都可以使用的参数堆栈中的位置。例如,当处理绑定变量时(在内部,使用“P”魔法),Perl必须调用方法来访问绑定变量。然而,我们需要将暴露于方法的参数与暴露于原始函数的参数分离开来——存储或获取或其他可能的参数实施;看见av_推送在里面平均值。c(c):

1个按钮(SP);2伸出(SP,2);3 PUSHs(SvTIED_obj((SV*)av,mg));4个PUSH(val);5推杆;6回车;7调用方法(“PUSH”,G_SCALAR|G_DISCARD);8休假;9个POPSTACK;

与标记堆栈相关的行是第一行、第五行和最后一行:它们保存、恢复和删除参数堆栈的当前位置。

让我们检查一下整个实施过程,以便进行实践:

1个按钮(SP);

将堆栈指针的当前状态推送到标记堆栈上。这样,当我们完成向参数堆栈添加项时,Perl就会知道我们最近添加了多少内容。

2伸出(SP,2);3 PUSHs(SvTIED_obj((SV*)av,mg));4个PUSH(val);

我们将在参数堆栈上再添加两个项:当您有一个绑定数组时子例程接收要推送的对象和值,这正是我们这里所拥有的-绑定对象,用SvTIED_obj公司和SV值val值.

5推杆;

接下来,我们告诉Perl对全局堆栈指针进行更改:数字SP只给了我们一份本地副本,而没有提到全球。

6回车;7调用方法(“PUSH”,G_SCALAR|G_DISCARD);8休假;

ENTER(输入)离开本地化代码块-它们确保所有变量都已整理好,所有已本地化的内容都会返回其先前的值,等等{}Perl块的。

要真正执行魔法方法调用,我们必须在Perl空间中调用一个子例程:调用方法(_M)解决了这一问题,如中所述珍珠贝。我们称之为方法,我们将放弃它的返回值。

9个POPSTACK;

最后,我们删除了放置在标记堆栈上的值,因为我们不再需要它了。

保存堆栈

C没有本地作用域的概念,所以perl提供了一个。我们已经看到了ENTER(输入)离开用作范围括号;存储堆栈实现的C等价物,例如:

{本地$foo=42;...}

请参见perlguts中的“本地化更改”了解如何使用保存堆栈。

数百万宏

关于Perl源代码,您会注意到一件事,那就是它充满了宏。一些人认为宏的广泛使用是最难理解的事情,其他人则认为它增加了清晰度。让我们以实现加法运算符的代码为例:

1 PP(PP_add)2  {3 dSP;目标;tryAMAGICbin(添加,opASSIGN);4      {5 dPOPTOPnnrl_ul;6 SETn(左+右);7返回;8      }9  }

这里的每一行(当然除了大括号)都包含一个宏。第一行按照Perl对PP代码的期望设置函数声明;第3行为参数堆栈和操作的返回值目标设置变量声明。最后,它尝试查看加法操作是否过载;如果是这样,则调用相应的子例程。

第5行是另一个变量声明-所有变量声明都以d日-从参数堆栈顶部弹出两个NV(因此nn个)并将它们放入变量中正确的左边,因此爱尔兰。这是加法运算符的两个操作数。接下来,我们打电话给SETn公司将返回值的NV设置为两个值相加的结果。完成后,我们返回-返回宏确保返回值得到正确处理,并传递下一个操作符以返回到主运行循环。

大多数宏在中进行了解释佩拉皮中解释了一些更重要的问题珍珠色也。特别注意perlguts中的“背景和PERL_IMPLICIT_CONTEXT”有关[pad]THX_?宏。

.i目标

可以在中展开宏食品。c(c)通过说来归档

制作foo。

它将使用cpp扩展宏。不要害怕结果。

在Perl上扑克

要真正了解Perl,您可能需要构建用于调试的Perl,如下所示:

./配置-d-d优化=-g制作

-克是C编译器的一个标志,它可以生成调试信息,从而允许我们单步执行正在运行的程序。配置也会打开调试编译符号,用于启用Perl中的所有内部调试代码。有很多东西可以用这个进行调试:珀尔伦列出了它们,了解它们的最好方法就是和它们一起玩。最有用的选项可能是

l上下文(循环)堆栈处理t跟踪执行o方法和过载解决方案c字符串/数字转换

调试代码的某些功能可以使用XS模块实现。

-Dr=>使用重新“调试”-Dx=>使用O“调试”

使用源级调试器

如果调试输出-D类这对您没有帮助,现在是用源代码级调试器逐步执行perl的时候了。

要启动调试器,请键入

gdb/珍珠

您需要在Perl源代码树中执行此操作,以便调试器可以读取源代码。您应该会看到版权信息,然后是提示。

(gdb)

帮助将帮助您了解文档,但以下是最有用的命令:

运行[args]

使用给定的参数运行程序。

中断函数名称
中断源。c:xxx

告诉调试器,当我们到达指定的函数时,我们想暂停执行(但请参见perlguts中的“内部函数”!) 或指定源文件中的给定行。

一次单步执行一行程序。

下一个

一次一行地逐步执行程序,而不向下分解为函数。

持续

运行到下一个断点。

完成

运行到当前函数结束,然后再次停止。

“输入”

只需按Enter键,即可再次执行最近的操作—这对于遍历数英里的源代码来说是一件好事。

打印

执行给定的C代码并打印其结果。警告:Perl大量使用宏,并且gdb公司不一定支持宏(参见下文“gdb宏支持”). 您必须自己替换它们,或者在源代码文件上调用cpp(请参见“.i目标”)例如,你不能说

打印SvPV_nolen(sv)

但你必须说

打印Perl_sv_2pv_nolen(sv)

你可能会发现拥有一本“宏字典”很有帮助,你可以这样说cpp-dM perl.c|排序即便如此,中央处理器不会为您递归应用这些宏。

gdb宏支持

的最新版本gdb公司有相当好的宏支持,但为了使用它,您需要使用调试信息中包含的宏定义来编译perl。使用海湾合作委员会版本3.1,这意味着使用配置-Doptimize=-g3。其他编译器可能使用不同的开关(如果它们根本支持调试宏)。

转储Perl数据结构

绕过这个宏地狱的一种方法是在中使用转储函数转储。c(c); 这些有点像内部Devel::Peek公司,但它们也涵盖了从Perl无法获得的OP和其他结构。让我们举个例子。我们将使用$a=$b+$c我们以前用过,但给它一点上下文:$b=“6XXXX”$c=2.3;.哪里是停下来闲逛的好地方?

关于pp_add(pp_add),我们之前检查的函数,用于实现+操作员:

(gdb)中断Perl_pp_add0x46249f处的断点1:文件pp_hot.c,第309行。

注意我们使用Perl_pp_add而不是pp_添加-参见perlguts中的“内部函数”。有了断点,我们可以运行程序:

(gdb)运行-e'$b=“6XXXX”$c=2.3$a=$b+$c'

当gdb读取相关的源文件和库时,会经过大量垃圾,然后:

断点1,pp_hot.c:309处的Perl_pp_add()309 dSP;目标;tryAMAGICbin(添加,opASSIGN);(gdb)步骤311 dPOPTOPnnrl_ul;(gdb)

我们之前看过这段代码,我们说过dPOPTOPnnrl_ul(&U)安排两个人内华达州将放置到左边正确的-让我们稍微扩展一下:

#定义dPOPTOPnnrl_ul NV right=POPn\SV*leftsv=TOP\NV left=USE_left(leftsv)?SvNV(左sv):0.0

POPn(流行音乐)从堆栈顶部获取SV并直接获取其NV(如果瑞典国家石油公司设置)或通过调用sv_2nv型功能。最上等的从堆栈顶部获取下一个SV-是的,POPn(流行音乐)使用最上等的-但不会移除。然后我们使用SvNV公司从获取NV左sv和以前一样-是的,POPn(流行音乐)使用SvNV公司.

因为我们没有NV十亿美元,我们必须使用sv_2nv型转换它。如果我们再迈出一步,我们会发现自己在那里:

sv.c:1669处的Perl_sv_2nv(sv=0xa0675d0)1669 if(!sv)(gdb)

我们现在可以使用Perl_sv_dump调查SV:

SV=0xa0675d0时的PV(0xa057cc0)参考值=1襟翼=(POK,pPOK)PV=0xa06a510“6XXXX”\0电流=5长度=6$1=无效

我们知道我们会6因此,让我们完成子例程:

(gdb)饰面运行,直到退出sv.c:1671处的#0 Perl_sv_2nv(sv=0xa0675d0)pp_hot.c:311处Perl_pp_add()中的0x462669311 dPOPTOPnnrl_ul;

我们还可以转储此op:当前op始终存储在打印(_O)我们可以用Perl_op_dump。这将为我们提供类似的输出B: :调试.

{13类型=添加===>14目标=1标记=(鳞片,儿童){类型=空===>(12)(是rv2sv)标记=(鳞片,儿童){11类型=gvsv==>12标记=(标尺)GV=主要::b}}

#稍后再完成这个#

修补

好了,我们现在已经了解了如何导航Perl源代码,以及在处理它们时需要了解的一些事情。现在,让我们开始创建一个简单的补丁。拉里建议:如果单位包装,(例如,打包“U3C8”,@stuff)则结果字符串应被视为UTF-8编码。

我们如何准备解决这个问题?首先,我们找到有问题的代码-包装在运行时发生,因此它将位于聚丙烯文件夹。果然,pp_返回在中第c页。由于我们要更改此文件,让我们将其复制到第c页~.

[嗯,是在第c页编写本教程时。现在已经与pp_解包到自己的文件中,pp_包。c(c)]

现在让我们看一下pp_包:我们采用一种模式拍打,然后循环模式,将每个格式字符依次转换为基准_类型然后,对于每个可能的格式字符,我们吞下模式中的其他参数(字段宽度、星号等),并将下一个块输入转换为指定的格式,将其添加到输出SV中.

我们如何知道单位是中的第一种格式拍打? 如果我们有一个指针指向拍打那么,如果我们看到单位我们可以测试我们是否仍然位于字符串的开头拍打已设置:

STRLEN fromlen;寄存器char*pat=SvPVx(*++标记,fromlen);注册字符*专利=pat+fromlen;寄存器I32 len;I32基准类型;SV*来自str;

我们将在其中有另一个字符串指针:

STRLEN fromlen;寄存器char*pat=SvPVx(*++标记,fromlen);注册字符*专利=pat+fromlen;+char*patcopy;寄存器I32 len;I32基准类型;SV*来自str;

在开始循环之前,我们将设置复印件成为…的开始拍打:

项目=SP-标记;标记++;sv_setpvn(目录,“”,0);+patcopy=pat;while(帕特<专利){

现在如果我们看到单位它位于字符串的开头,我们打开UTF8标准输出SV的标志,:

+if(基准类型==“U”&&pat==patcopy+1)+SvUTF8_on(猫);if(基准类型==“#”){while(pat<专利&&*pat!=“\n”)pat++;

记住,它必须是patcopy+1因为字符串的第一个字符是单位被吞没了基准类型!

哎呀,我们忘了一件事:如果模式的开头有空格怎么办?pack(“U*”,@stuff)将会有单位作为第一个活跃的角色,即使它不是模式中的第一件事。在这种情况下,我们必须提前局部检查随着拍打当我们看到空格时:

if(isSPACE(基准类型))继续;

需要成为

if(isSPACE(基准类型)){patcopy++;继续;}

好的。这就是C部分。现在,在这个补丁准备就绪之前,我们必须做两件额外的事情:我们已经改变了Perl的行为,因此我们必须记录这一改变。我们还必须提供更多的回归测试,以确保我们的补丁能够正常工作,并且不会在其他地方产生错误。

每个运营商的回归测试t/操作/,所以我们复制了t/op/pack。t吨t/op/pack.t台~现在我们可以将测试添加到末尾。首先,我们将测试单位确实会创建Unicode字符串。

t/op/pack.t有一个合理的ok()函数,但如果没有,我们可以使用t/test.pl中的函数。

需要“/test.pl';计划(测试=>159);

所以不是这样:

打印“not”,除非“1.20.300.4000”eq sprintf“%vd”,pack(“U*”,1,203004000);打印“ok$test\n”$测试++;

我们可以写得更合理(参见测试::更多有关is()和其他测试函数的完整说明)。

是(“1.20.300.4000”,sprintf“%vd”,包装(“U*”,1,203004000),“U*生成unicode”);

现在,我们要测试一下,我们的太空创业业务是否正确:

是(“1.20.300.4000”,sprintf“%vd”,包装(“U*”,1,203004000),“开头有空格”);

最后,我们将测试,如果单位第一个活动格式:

isnt(v1.20.300.4000,sprintf“%vd”,pack(“C0U*”,1,203004000),“U*not-first不是unicode”);

千万不要忘记更改顶部显示的测试数量,否则自动化测试人员会感到困惑。这要么看起来像这样:

打印“1..156\n”;

或者这样:

计划(测试=>156);

我们现在编译Perl,并在测试套件中运行它。我们的新测试通过了,万岁!

最后是文档。在文书工作结束之前,这项工作永远不会完成,所以让我们描述一下我们刚刚做出的改变。相关地点是pod/perlfunc.pod; 同样,我们制作一份副本,然后将此文本插入对包装:

=项目*如果模式以C<U>开头,则将处理结果字符串作为UTF-8编码的Unicode。可以在字符串中强制启用UTF-8编码带有初始C<U0>,随后的字节将解释为Unicode字符。如果你不想发生这种情况,你可以开始使用C<C0>(或其他任何东西)来强制Perl不使用UTF-8编码字符串,然后在模式中的某个位置使用C<U*>。

全部完成。现在,让我们创建补丁。移植/修补.pod告诉我们,如果我们正在进行重大更改,我们应该在开始修改之前将整个目录复制到安全的位置,然后再进行修改

diff-ruN-旧-新>补丁

然而,我们知道我们更改了哪些文件,我们可以简单地这样做:

diff-u pp.c~pp.c>补丁diff-u t/op/pack.t~t/op/pak.t>>补丁diff-u pod/perlfunc.pod~pod/pertlfunc.pd>>补丁

我们最终得到的补丁看起来有点像这样:

---pp.c~ 2000年6月2日星期五04:34:10+++pp.c 2000年6月16日星期五11:37:25@@ -4375,6 +4375,7 @@登记I32项;STRLEN fromlen;寄存器char*pat=SvPVx(*++标记,fromlen);+char*patcopy;注册字符*专利=pat+fromlen;寄存器I32 len;I32基准类型;@@ -4405,6 +4406,7 @@...

最后,我们将它连同我们的理由提交给perl5-porters。工作完成了!

修补核心模块

这就像修补任何其他东西一样,需要额外考虑。许多核心模块也在CPAN上运行。如果是这样,请修补CPAN版本而不是内核,并将修补程序发送给模块维护者(并将副本发送给p5p)。这将有助于模块维护者保持CPAN版本与核心版本同步,而无需不断扫描p5p。

向核心添加新函数

如果作为修复bug的补丁的一部分,或者仅仅因为你有一个特别好的想法,你决定在核心中添加一个新函数,那么在开始工作之前就讨论一下你对p5p的想法。可能是其他人已经尝试做您正在考虑的事情,并且可以提供很多好的建议,甚至可以为您提供他们已经开始(但从未完成)的代码。

你必须按照上面给出的所有建议进行修补。彻底测试任何添加并添加新测试以探索新函数要处理的所有边界条件,这一点非常重要。如果新函数仅由一个模块使用(例如toke),那么它可能应该命名为S_your_function(表示静态);另一方面,如果您希望它可以从Perl中的其他函数访问,则应该将其命名为Perl_your_function。请参见perlguts中的“内部函数”了解更多详细信息。

任何新代码的位置也是一个重要的考虑因素。不要只创建一个新的顶级.c文件并将代码放在那里;您必须对Configure进行更改(以便正确创建Makefile),还可能需要很多include文件。这是严格意义上的抽水业务。

最好将函数添加到现有的顶级源代码文件中,但由于Perl发行版的性质,您的选择很复杂。只有标记为已编译静态的文件位于perl可执行文件中。其他所有内容都位于共享库中(如果在WIN32下运行,则为DLL)。因此,例如,如果一个函数仅由位于toke.c中的函数使用,那么您的代码可以放在toke.c.中。但是,如果您想从universal.c调用该函数,那么您应该将代码放在另一个位置,例如util.c。

除了编写c代码之外,您还需要在embedd.pl中创建一个适当的条目来描述您的函数,然后运行“make regen_headers”在perl正确编译所需的众多头文件中创建条目。请参见perlguts中的“内部函数”获取有关您可以在embed.pl中设置的各种选项的信息。您会忘记多次这样做,并且在编译阶段会收到警告。确保你在发布补丁到P5P时提到这一点;抽水工需要知道这一点。

编写新代码时,请注意perl源文件中使用的现有代码约定。请参见珍珠岩风格了解详细信息。虽然所讨论的大多数指南似乎都关注Perl代码,而不是c,但它们都适用(除非它们不适用;)。另请参见移植/修补.pod有关格式化和提交更改补丁的详细信息,请参阅Perl源代码发行版中的文件。

最后,TESTTESTESTEST在发布到p5p之前测试任何代码。在尽可能多的平台上进行测试。测试尽可能多的perl配置选项(例如MULTIPLICITY)。如果您有分析或内存工具,请参阅“调试性能的外部工具”下面介绍如何使用它们进一步测试代码。请记住,P5P上的大多数人都是在自己的时间内完成这项工作的,没有时间调试您的代码。

编写测试

每个模块和内置函数都有一个关联的测试文件(或应该……)。如果添加或更改功能,则必须编写一个测试。如果你修复了一个bug,你必须编写一个测试,这样bug就不会再出现了。如果您更改文档,最好测试一下新文档的内容。

简而言之,如果您提交补丁,那么您可能还必须对测试进行补丁。

对于模块,测试文件就在模块本身的旁边。lib/strict。t吨测验库/严格.pm这是最近的一项创新,因此存在一些障碍(如果你能把它们清除掉,那就太棒了),但基本上是这样的。其他一切都存在t吨/.

(以下列表和说明描述了perl 5.8.1中的情况。在perl 5.6中,测试套件要复杂得多。)

t/底座/

测试Perl的绝对基本功能。比如如果、基本文件读取和写入、简单正则表达式等。这些都是首先在测试套件中运行的,如果其中任何一个失败,则会出现以下情况真正地断裂。

t/命令/

这些测试测试基本控制结构,if/else(如果/否则),虽然、子程序等。

t/comp公司/

测试Perl如何解析和编译自身的基本问题。

t/io公司/

测试内置IO功能,包括命令行参数。

t/lib型/

模块测试的老地方,你不应该在这里放任何新东西。这里还有一些零碎的东西需要搬走。也许你可以移动它们?谢谢!

t/操作/

测试不适合任何其他目录的perl内置函数。

吨/箱/

POD指令测试。这里仍然有一些关于吊舱模块的测试需要迁移到图书馆/.

t/运行/

测试perl实际运行方式的特性,包括退出代码和perl*环境变量的处理。

吨/单位/

测试Unicode的核心支持。

吨/温32/

特定于Windows的测试。

吨/x2p

s2p转换器的测试套件。

核心使用与Perl其余部分相同的测试风格,即简单的“ok/not ok”贯穿测试::Harness,但有一些特殊的注意事项。

有三种方法可以在核心中编写测试。测试::More、t/Test.pl和ad-hoc打印$test?“ok 42\n”:“不正常42\n”。使用哪个测试套件的决定取决于您正在使用的测试套件的哪个部分。这是一种防止高级故障(例如Config.pm中断)导致基本功能测试失败的措施。

t/基础t/comp

由于我们不知道require是否有效,甚至不知道子程序是否有效,因此对这两个程序使用特别测试。小心操作,避免使用正在测试的功能。

t/cmd t/运行t/io t/op

现在已经测试了基本require()和子例程,您可以使用t/test.pl库来模拟test::More的重要功能,同时使用最少的核心功能。

您也可以有条件地使用某些库,如Config,但如果没有测试,请务必优雅地跳过它。

t/lib扩展库

现在已经测试了Perl的核心,可以使用Test::More了。您还可以在测试中使用全套核心模块。

当您说“make test”时,Perl使用t/测试程序来运行测试套件。所有测试都从t吨/目录,包含测试的目录。这会导致中的测试出现一些问题图书馆/,所以这里有一些修补的机会。

您必须三重意识到跨平台问题。这通常归结为使用File::Spec并避免这样的事情叉子()系统()除非绝对必要。

特殊制造测试目标

有各种特殊的make目标可用于测试Perl,与标准的“test”目标略有不同。并不是所有人都能达到100%的成功率。其中许多人有几个别名。

岩心测试

运行珍珠所有岩芯测试(吨/*库/[a-z]*杂注测试)。

测试.分离

通过B::Deparse运行所有测试。并非所有测试都会成功。

最小的

运行迷你雪茄t/底座,t/comp公司,吨/立方厘米,t/运行,t/io公司,t/操作,以及吨/单位测验。

测试.valgrind检查.valgrund测试.valgrand ucheck.valgrid

(仅在Linux中)使用内存泄漏+顽皮内存访问工具“valgrind”运行所有测试。日志文件将被命名测试名称.valgrind.

测试.第三次检查.第三个测试.第二个ucheck.第三

(仅在Tru64中)使用内存泄漏+调皮内存访问工具“三度”运行所有测试。日志文件将被命名perl3.log.testname.

测试.温度扭曲测试

运行所有常规测试和一些额外测试。从Perl 5.8.0开始,唯一的额外测试是Abigail的JAPH,t/japh/abigail公司。t吨.

你也可以用t/线束通过给予-折磨的参数t/线束.

utest ucheck test.utf8检查.utf8

使用-Mutf8运行所有测试。并非所有测试都会成功。

测试线束

使用运行测试套件t/线束控制程序,而不是t/测试.t/线束更复杂,并使用测试::线束模块,因此使用此测试目标假设perl基本上可以工作。我们的主要优点是它在最后打印了失败测试的详细摘要。此外,与t/测试,它不会将stderr重定向到stdout。

手动运行测试

您可以通过使用来自t吨/目录:

./perl-I./lib TEST列表-.t-files

./perl-I./lib-.t文件的线束列表

(如果您没有指定测试脚本,则会运行整个测试套件。)

您可以通过类似以下命令运行单个测试

./perl-I../lib-patho/to/foo。t吨

但线束设置了一些可能影响测试执行的环境变量:

PERL_CORE=1

表示我们正在运行perl核心测试套件的这个测试部分。这对于CPAN上具有双重寿命的模块很有用。

PERL_DESTRUCT_LEVEL=2级

如果尚未设置,则设置为2(请参见“性能_破坏_级别”)

PERL语言

(仅由使用t/测试)如果设置了,则覆盖应该用于运行测试的perl可执行文件的路径(默认为./perl文件).

PERL_SKIP_TTY_TEST测试

如果设置了,则告知跳过需要终端的测试。它实际上是由Makefile自动设置的,但也可以通过运行“maketest_notty”人为强制设置。

调试性能的外部工具

有时,在调试和测试Perl时使用外部工具会有所帮助。本节试图指导您在Perl中使用一些常见的测试和调试工具。这是作为这些工具与Perl接口的指南,而不是作为工具本身的任何使用指南。

注释1:在Purify、valgrind或Third Degree等内存调试器下运行会大大减慢执行速度:秒变分,分钟变小时。例如,从Perl 5.8.1开始,ext/Encode/t/Unicode.t在Purify、Third Degree和valgrind下完成需要非常长的时间。在valgrinde下完成它需要六个多小时,即使是在一台时髦的计算机上也是如此。所说的测试一定是在做一些对内存调试器来说非常不友好的事情。如果您不想等待,那么您可以简单地终止perl进程。

注释2:要最小化内存泄漏假警报的数量(请参阅“性能_破坏_级别”有关更多信息,必须将环境变量PERL_DESTRUCT_LEVEL设置为2。这个测试和线束脚本自动完成这一任务。但是,如果您手动运行一些测试——对于类似csh的shell:

setenv PERL_DESTRUCT_LEVEL 2级

对于Bourne型外壳:

PERL_DESTRUCT_LEVEL=2级导出PERL_DESTRUCT_LEVEL

或者在UNIXy环境中,也可以使用环境价值命令:

env PERL_DESTRUCT_LEVEL=2伏磨/perl-伊利布。。。

注释3:当eval或require中存在编译时错误时,存在已知的内存泄漏,请参阅执行(_D)在调用堆栈中是一个很好的迹象。不幸的是,修复这些泄漏并不容易,但最终必须修复。

Rational软件的净化

Purify是一个商业工具,它有助于识别内存溢出、野指针、内存泄漏和其他此类错误。Perl必须以特定的方式编译,以便使用Purify进行最佳测试。Purify在Windows NT、Solaris、HP-UX、SGI和Siemens Unix下可用。

Unix上的Purify

在Unix上,Purify创建了一个新的Perl二进制文件。为了从Purify中获得最大的好处,您应该使用以下命令创建perl To Purify:

sh配置-Accflags=-DPURIFY-Doptimize='-g'\-Uusemymalloc-Dusemultiplicity公司

其中这些参数的含义是:

-Accflags=-DPURIFY

禁用Perl的arena内存分配函数,以及强制使用从系统malloc派生的内存分配函数。

-Doptimize='-g'

添加调试信息,以便查看发生问题的确切源语句。如果没有此标志,您将看到的只是发生错误的源文件名。

-使用mymalloc

禁用Perl的malloc,以便Purify可以更密切地监视分配和泄漏。使用Perl的malloc将使Purify报告“潜在”泄漏类别中的大多数泄漏。

-多尘性

启用multiplicity选项可以让perl在解释器关闭时彻底清理,从而减少了来自Purify的虚假泄漏报告的数量。

一旦您编译了一个适合Purify'ing的perl,那么您就可以:

制作pureperl

它创建了一个名为“pureperl”的二进制文件,该文件已经过Purify处理。当您想要调试perl内存问题时,可以使用该二进制文件代替标准的“perl”二进制文件。

例如,为了显示在标准Perl测试集期间产生的任何内存泄漏,您将创建Purify’ed Perl并以如下方式运行:

制作pureperlcd t(光盘)../pureperl-I../lib线束

它将在test.pl上运行Perl并报告任何内存问题。

Purify默认在“Viewer”窗口中输出消息。如果您没有一个窗口环境,或者如果您只是想让Purify输出不引人注目地转到一个日志文件而不是交互式窗口,那么使用以下选项来输出到日志文件“perl.log”:

setenv PURIFYOPTIONS“-链长度=25-窗口=否\-log-file=perl.log-append-logfile=yes“

如果您计划使用“查看器”窗口,那么您只需要此选项:

setenv PURIFYOPTIONS“-链长=25”

在Bourne型外壳中:

PURIFYOPTIONS=“…”导出PURIFYOPTIONS

或者如果您有“env”实用程序:

env PURIFYOPTIONS=“…”/纯白。。。

NT上的净化

WindowsNT上的Purify动态地插入Perl二进制文件“Perl.exe”。您应该更改makefile中的几个选项,以最大限度地利用Purify:

定义

您应该将-DPURIFY添加到DEFINES行,使DEFINES行看起来像这样:

定义=-DWIN32-D_CONSOLE-DNO_STRICT$(CRYPT_FLAG)-DPURIFY=1

禁用Perl的arena内存分配函数,以及强制使用从系统malloc派生的内存分配函数。

USE_MULTI=定义

启用多重性选项允许perl在解释器关闭时彻底清理,这减少了Purify中虚假泄漏报告的数量。

#PERL_MALLOC=定义

禁用Perl的malloc,以便Purify可以更密切地监视分配和泄漏。使用Perl的malloc将使Purify报告“潜在”泄漏类别中的大多数泄漏。

CFG=调试

添加调试信息,以便您可以看到问题发生的确切源语句。如果没有此标志,您将看到的只是发生错误的源文件名。

例如,为了显示在标准Perl测试集期间产生的任何内存泄漏,您将创建Purify并以如下方式运行:

光盘win32制作光盘/t吨净化/perl-I../lib线束

它将在内存中插入Perl,在test.pl上运行Perl,然后最终报告任何内存问题。

瓦尔格林

优秀的valgrind工具可用于查找内存泄漏和非法内存访问。截至2003年8月,它只能在x86(ELF)Linux上运行。特殊的“test.valgrind”目标可用于在valgrind.下运行测试。发现的错误和内存泄漏记录在名为测试.valgrind.

由于系统库(最显著的是glibc)也会触发错误,因此valgrind允许使用抑制文件来抑制此类错误。valgrind附带的默认禁止显示文件已经捕获了很多。中定义了一些附加抑制t/perl.supp型.

要获取valgrind并了解更多信息,请参阅

http://developer.kde.org/~sewardj/

康柏/数字/惠普三级

第三级是用于内存泄漏检测和内存访问检查的工具。它是ATOM工具包中的众多工具之一。该工具包仅在Tru64(以前称为Digital UNIX,以前称为DEC OSF/1)上可用。

在构建Perl时,必须首先使用-Doptimize=-g和-Uusemymalloc标志运行Configure,然后才能使用make目标“Perl.third”和“test.third”。(需要的是必须使用-克标志,您可能需要重新配置。)

简而言之,通过“atom”,您可以对Perl可执行文件进行指令插入,以创建一个名为perl.第三。当检测的可执行文件运行时,它会在名为perl.3日志。有关更多信息,请参阅atom和第三个手册页。Compaq“Tru64 UNIX Programmer's Guide”的“Debugging Programs with Third Degree”一章中提供了最全面的三级文档。

“test.third”留下了许多名为foo_bar.3log在t/子目录中。这些文件有一个问题:第三级非常有效,它也会在系统库中发现问题。因此,您应该使用Porting/tirdclean脚本来清理*.3日志文件夹。

对于给定的泄漏定义,也有一些泄漏不是。请参阅“性能_破坏_级别”了解更多信息。

每个析构级别

如果您想使用例如valgrind、pureperl或perl.third可执行文件手动运行任何测试,请注意默认情况下perl不会显式清理它分配的所有内存(例如全局内存区域),但相反,让整个程序的exit()“处理”此类分配,也称为“对象的全局销毁”。

有一种方法可以告诉perl完成清理:将环境变量perl_DESTRUCT_LEVEL设置为非零值。t/TEST包装器确实将其设置为2,如果您不想看到“全局泄漏”,那么您也需要这样做:例如,对于“三阶”Perl:

env PERL_DESTRUCT_LEVEL=2/perl.third-Ilib t/foo/bar。t吨

(注意:mod_perl apache模块出于自身目的也使用此环境变量,并扩展了其语义。有关更多信息,请参阅mod_perl-documentation。此外,派生线程相当于将此变量设置为值1。)

如果在跑步结束时你收到消息N个标量泄漏,可以使用重新编译-DDEBUG_LEAKING_SCALARS公司,这将导致所有泄漏的SV的地址被转储;它还转换新SV()从宏转换为实际函数,这样您就可以使用您最喜欢的调试器来发现那些讨厌的SV是在哪里分配的。

分析

根据您的平台,有各种评测Perl。

有两种常用的分析可执行文件的技术:统计时间采样基本块计数.

第一种方法定期采集CPU程序计数器的样本,由于程序计数器可以与为函数生成的代码相关联,因此我们可以获得程序在哪些函数中花费时间的统计视图。需要注意的是,非常小/快速的函数在配置文件中出现的概率较低,并且周期性中断程序(这通常是以毫秒为单位,非常频繁地进行)会增加额外的开销,从而可能会歪曲结果。第一个问题可以通过长时间运行代码来缓解(一般来说,这是进行分析的一个好主意),第二个问题通常由分析工具本身加以防范。

第二种方法将生成的代码分为基本块。基本块是仅在开头输入、仅在结尾退出的代码段。例如,条件跳转启动基本块。基本块分析通常通过仪表化通过添加代码输入基本块#nnnn生成代码的记账代码。在代码执行期间,基本块计数器会相应地更新。需要注意的是,添加的额外代码可能会歪曲结果:同样,分析工具通常会尝试将自己的影响从结果中分离出来。

Gprof简介

gprof是一种可用于许多UNIX平台的分析工具,它使用统计时间采样.

您可以通过调用make目标“perl.gprof”来构建名为“perl.gprof-第页标志,您可能需要重新配置)。运行分析版本的Perl将创建一个名为gmon.out(通用)是创建的,其中包含执行期间收集的分析数据。

然后,gprof工具可以以各种方式显示收集的数据。通常gprof了解以下选项:

-一个

禁止从概要文件中静态定义函数。

-b条

取消概要文件中的详细描述。

-e例行程序

从配置文件中排除给定例程及其后代。

-f例行程序

在配置文件中仅显示给定例程及其子例程。

-秒

生成一个名为的摘要文件gmon.sum公司然后可以将其提供给后续的gprof运行,以在多次运行中积累数据。

-z(z)

显示使用率为零的例程。

有关可用命令和输出格式的详细说明,请参阅您自己的gprof本地文档。

GCC gcov分析

从GCC 3.0开始基本块剖析正式可用于GNU CC。

您可以构建一个名为perl.gcov公司通过调用make目标“perl.gcov”(必须使用带标志的gcc编译perl)-fprofile-arcs-ftest覆盖率,您可能需要重新配置)。

运行配置文件版本的Perl将生成配置文件输出。对于每个源文件,将创建一个附带的“.da”文件。

要显示结果,可以使用“gcov”实用程序(如果安装了gcc 3.0或更新版本,则应安装该实用程序)。覆盖测试在源代码文件上运行,如下所示

gcov sv.c公司

这将导致sv.c.gcov公司待创建。这个.gcov文件文件包含用“#”标记指示的相对执行频率注释的源代码。

有用的选项覆盖测试包括-b条它将总结基本块、分支和函数调用覆盖范围,以及-c(c)而不是相对频率将使用实际计数。有关使用的更多信息覆盖测试以及使用gcc进行基本块分析,请参阅最新的GNU CC手册,从gcc 3.0开始,请参阅

http://gcc.gnu.org/onlinedocs/gcc-3.0/gcc.html

以及其标题为“8”的部分。gcov:测试覆盖程序”

http://gcc.gnu.org/onlinedocs/gcc-3.0/gcc_8.html#SEC132

像素轮廓

Pixie是IRIX和Tru64(又名Digital UNIX又名DEC OSF/1)平台上可用的分析工具。Pixie使用基本块计数.

您可以构建一个名为perl.混合通过调用make目标“perl.pixie”(需要的是perl必须使用-克标志,您可能需要重新配置)。

在Tru64中,一个名为珀尔。地址也将以静默方式创建,此文件包含基本块的地址。运行分析版本的Perl将创建一个名为“Perl.Counts”的新文件,其中包含该特定程序执行的基本块的计数。

要显示结果,请使用教授实用程序。确切的咒语取决于您的操作系统,IRIX中的“prof-perl.Counts”,以及Tru64中的“prof-pixie-all-L.perl”。

在IRIX中,以下prof选项可用:

-小时

按使用降序报告使用频率最高的行。用于查找热点线。

-我

按过程对行进行分组,过程按使用降序排序。在过程中,行按源顺序列出。用于查找过程的热点。

在Tru64中,以下选项可用:

-p[程序]

按每个过程中执行的循环数降序排列的过程。用于查找热点过程。(这是默认选项。)

-h[重型]

行按每行执行的周期数降序排序。用于查找热点线。

-i[位置]

被调用的过程按对过程的调用次数降序排序。用于查找最常用的程序。

-l[行]

按过程分组,按每个过程执行的周期排序。用于查找过程的热点。

-测试覆盖率

编译器为这些行发出了代码,但代码没有被执行。

-z[ero]

未执行的程序。

有关更多信息,请参阅系统的精灵和教授手册页。

杂项技巧

结论

我们对Perl源代码进行了简要介绍,对各个阶段进行了概述珍珠介绍它何时运行您的代码,以及如何使用调试器来挖掘Perl的内核。我们解决了一个非常简单的问题,并演示了如何通过文档、回归测试以及最后一个提交给p5p的补丁来完全解决它。最后,我们讨论了如何使用外部工具调试和测试Perl。

我现在建议你把那些参考资料再看一遍,然后尽快弄脏你的手。学习的最佳方式是通过实践,因此:

这条路从它开始的那扇门一直延伸下去。

如果您能做到这些,那么您就已经开始了Perl移植的漫长道路。感谢您想帮助改进Perl,并让它变得更快乐!

作者

本文档由Nathan Torkington编写,由perl5-porters邮件列表维护。

1 POD错误

分析POD时遇到以下错误:

370线附近:

之前看到的非ASCII字符=“König”中的编码。假设CP1252