strlen的神奇实现

https://blog.delphij.net/2012/04/freebsd-strlen3.html


与 Pascal 等语言不同,C 的字符串并不保存串的长度,而是在字符串末尾以 nul 字符('\0')来表示字符串结束。这个设计决策是上世纪 60 年代作出的,有都市传说是为了省几个字节的空间,不过我个人认为也可能是因为汇编里面到处都是判断是否碰到了 0 的操作。不管怎么说,这个设计令 strlen 变成了一个 O(n) 的操作。

早期的 BSD Unix 采用的 strlen 是非常简单的循环比较每一个字符是不是 nul。1993年,J.T. Conklin 为 i386 系统撰写了一个汇编的版本,这个版本的核心用的是 REP SCASB,实际上和 C 版本的算法是一样的(不知道为什么 C 编译器不能写出同样的代码)。

为了配合 x86_64 平台,后来又有了一个新的汇编版本,这个版本的核心算法是按字匹配,找到包含 nul 字符的字之后,再在其中用原始的算法找到 nul 字符。

我在 2009 年根据这个 x86_64 版本的汇编的思路重写了一个 C 的版本,并在 2010 年做了一次最终的变动,形成了目前的版本。这个版本的大致流程如下:

  • 判断第一个字中是否存在 nul,如果存在,扫描查找其中有效位置的 nul 字符;
  • 按字扫描剩余的字符串;如果发现字中带 nul,则扫描并返回其位置。

实现细节

整个算法中,比较难理解的是判断字中是否带 nul 字符。具体的方法是计算两个中间变量:

  1. a = (x - 0x01010101)
  2. b = (~x & 0x80808080)
  3. 计算 a & b == 0

这里的 0x01010101 和 0x80808080 可以进一步扩展。第一步,如果每个字节都 <= 0x7f,只要那个字节不是 0,做差必然得到一个 < 0x80 的结果(换言之,最高位是0);如果有字节 >= 0x81,做差必然得到一个 > 0x80 的结果。对于等于 0x80 的情况,我们会得到 0x7f,但这并不重要。

注意到,此处,任何一个字节的最高 bit 是 1 的话,则必然是前面两种情况之一:要么这个字节是 0,要么它 >= 0x81。如果不考虑后一种情况,我们直接把结果 & 0x80808080 即可;然而,由于需要考虑后一种情况,我们接着计算 ~x & 0x80808080。若某一字节 >= 0x80,则对应的结果将是那个位置上的一个 0x80。

将两个结果做逻辑与,若结果非 0 则说明至少有一个字节是 nul。

这里说起来的过程很复杂,但事实上计算机计算这些要比一个一个去判断每个字节是否为 0 要快。这里有几方面的原因:

  • 按字长做操作,令 CPU 无需模拟按字节为长度操作的情况,后者是比较耗时的;
  • 前两步操作(分别计算a, b)可以并发执行;
  • 最后一步操作可以直接在两个寄存器之间进行,且是速度较快的与运算;

在实际的实现中,还有一些其他的技巧。

第一个技巧是,从第一个小节就开始用字长的操作。一般来说,内存分配器在分配内存时是以字边界开始的,因此,通常 strlen() 的操作的指针都是对齐的。不过,即使不是,这个指针往前退到第一个整字位置(例如字长=8,指针 0x9,则退回 0x8)开始的一个整字必然是在同一个内存页上,因此这个访问不会越界。如果在这个整字中有 nul 字符,我们只需从指针开始处扫描到第一个整字结尾的地方即可知道是不是真的找到了字符串的末尾。

由于整个字已经在处理器缓存中,后续的循环也不会太慢。

第二个技巧与此类似,我们一直都用整字的操作。如果字的起始地址在内存页中,则终止地址也必然在同一个内存页中。这个访问同样也不会发生意外越界(尽管在分配内存时可能出现类似分配了 4 个字节,但访问了 8 个字节的情况)。换言之,如果程序原先不会发生越界异常,则现在也不会。

这个版本的 strlen 源代码可以在 这里 找到。


转载于:https://www.cnblogs.com/marryZhan/archive/2012/05/23/2797292.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379682.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python求和_Python程序查找特殊求和系列的解决方案

python求和We are going to design a special sum series function which has following characteristics: 我们将设计一个特殊的求和系列函数&#xff0c;该函数具有以下特征&#xff1a; f(0) 0f(1) 1f(2) 1f(3) 0f(x) f(x-1) f(x-3)Python solution of the above sum…

linux内核设计与实现---进程管理

进程管理1 进程描述符及任务结构分配进程描述符进程描述符的存放进程状态设置当前进程状态进程上下文进程家族树2 进程创建写时拷贝fork()vfork()3 线程在Linux中的实现内核线程4 进程终结删除进程描述符孤儿进程造成的进退微谷5 小结进程的另一个名字叫做任务&#xff08;task…

JS错误代码解释大全+VBS错误代码解释大全

JScript 运行时错误 JScript 运行时错误是指当 JScript 脚本试图执行一个系统不能运行的动作时导致的错误。当正在运行脚本、计算变量表达式、或者正在动态分配内存时出现 JScript 运行时错误时。 错误号 描述 5029 数组长度必须为一有限正整数 5030 必须赋给数组长度一个有…

生日蜡烛(蓝桥杯)

某君从某年开始每年都举办一次生日party&#xff0c;并且每次都要吹熄与年龄相同根数的蜡烛。 现在算起来&#xff0c;他一共吹熄了236根蜡烛。 请问&#xff0c;他从多少岁开始过生日party的&#xff1f; 请填写他开始过生日party的年龄数。 注意&#xff1a;你提交的应该是…

python日历模块_Python日历模块| firstweekday()方法与示例

python日历模块Python calendar.firstweekday()方法 (Python calendar.firstweekday() Method) firstweekday() method is an inbuilt method of the calendar module in Python. It works on simple text calendars and returns the current setting for the weekday to start…

php 处理 mysql to json, 前台js处理

public function GetJson(){$query"select * from table";$result mysql_query($query);$rows array();while($row mysql_fetch_array($result)){$rows [] $row;}echo json_encode($rows); } js处理 $.get( "./bll.php", option,function(data ) {var j…

Linux内核设计与实现---进程调度

进程调度1 策略I/O消耗型和处理器消耗型的进程进程优先级时间片进程抢占2 Linux调度算法可执行队列优先级数组重新计算时间片schedule()计算优先级和时间片睡眠和唤醒负载平衡程序3 抢占和上下文切换用户抢占内核抢占4 实时5 与调度相关的系统调用与调度策略和优先级相关的系统…

ServletContext(核心内容)

什么是ServletContext对象 ServletContext代表是一个web应用的环境&#xff08;上下文&#xff09;对象&#xff0c;ServletContext对象 内部封装是该web应用的信息&#xff0c;ServletContext对象一个web应用只有一个 一个web应用有多个servlet对象 ServletContext对象的生…

【转载】[TC]飞船动画例子--《C高级实用程序设计》

【声明和备注】本例子属于转载来源于《C高级实用程序设计》&#xff08;王士元&#xff0c;清华大学出版社&#xff09;第11章&#xff0c;菜单设计与动画技术&#xff0c;第11.5节&#xff0c;一个动画例子。 本例讲解的是在一个繁星背景下&#xff0c;一个由经纬线组成的蓝色…

math.sqrt 有问题_JavaScript中带有示例的Math.SQRT2属性

math.sqrt 有问题JavaScript | Math.SQRT2属性 (JavaScript | Math.SQRT2 Property) Math.SQRT2 is a property in math library of JavaScript that is used to find the value of square root of 2. It is generally used to solve problems related to circular figures. Ma…

Linux内核设计与实现---系统调用

系统调用1 API、POSIX和C库2 系统调用系统调用号3 系统调用处理程序指定恰当的系统调用参数传递4 系统调用的实现参数验证5 系统调用上下文绑定一个系统调用的最后步骤从用户空间访问系统调用为什么不通过系统调用的方式实现1 API、POSIX和C库 API&#xff1a;应用编程接口。一…

内核编译配置选项含义

Linux 2.6.19.x 内核编译配置选项简介 作者&#xff1a;金步国 版权声明 本文作者是一位自由软件爱好者&#xff0c;所以本文虽然不是软件&#xff0c;但是本着 GPL 的精神发布。任何人都可以自由使用、转载、复制和再分发&#xff0c;但必须保留作者署名&#xff0c;亦不得对声…

js编码处理(转)

js编码处理(转) 1. 使用 JS 中的 encodeURIComponent 或 encodeURI 方法。 说明&#xff1a; encodeURIComponent(String) 对传递参数进行设置。不编码字符有 71 个&#xff1a; ! &#xff0c; &#xff0c; ( &#xff0c; ) &#xff0c; * &#xff0c; - &#…

手动去设置HTTP响应行、响应头、响应体

①手动去设置HTTP响应行中的状态码&#xff0c;这里用到了response的setStatus(int sc);这个方法 package com.itheima.line;import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpSer…

Java SecurityManager checkListen()方法与示例

SecurityManager类的checkListen()方法 (SecurityManager Class checkListen() method) checkListen() method is available in java.lang package. checkListen()方法在java.lang包中可用。 checkListen() method invokes checkPermission with the given SocketPermission(&q…

基本的二分查找、寻找第一个和最后一个数的二分查找

二分查找1 二分查找的框架2 寻找一个数&#xff08;基本的二分搜索&#xff09;3 寻找左侧边界的二分搜索4 寻找右侧边界的二分查找5 合并二分查找场景&#xff1a;有序数组寻找一个数、寻找左侧边界&#xff08;有序数组第一个等目标数的下标&#xff09;、寻找右侧边界&#…

PostgreSQL 中的递归查询 与oracle 的比较

PostgreSQL 中的递归查询&#xff0c;2种方法&#xff1a; 1、用with decursive WITH RECURSIVE d AS (SELECT d1.id,d1.parent_id,d1.caption FROM course_types d1 where d1.dr 0 and d1.idtypeId union ALL SELECT d2.id,d2.parent_id,d2.caption FROM course_types d2, d …

教你如何玩转GitHub

使用GitHub ①目的&#xff1a;借助GitHub托管项目代码 基本概念&#xff1a; ①仓库(Repository)&#xff1a; 用来存放项目代码&#xff0c;每个项目对应一个仓库&#xff0c;多个开源项目对应多个仓库 ②收藏(Star)&#xff1a; 收藏项目&#xff0c;方便下次查看 ③…

Java SecurityManager checkDelete()方法与示例

SecurityManager类的checkDelete()方法 (SecurityManager Class checkDelete() method) checkDelete() method is available in java.lang package. checkDelete()方法在java.lang包中可用。 checkDelete() method calls checkPermission with FilePermission(filename,"d…

jQuery中的treeview插件

jQuery做树状结构真的很简单,下面做一个最简单的示例: 在html文件中引用: <link rel"stylesheet" href"../jquery.treeview.css" /> <link rel"stylesheet" href"../red-treeview.css" /> <link rel"styles…