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

strlen的神奇实现相关推荐

  1. strlen“好神奇”,永真式——if(strlen(q) - strlen(p) = 0)

    今天突然发现神奇的一幕 这个if中居然是个永真式 怎么可能 char *p = "aaaaaaaa";char *q = "ssss";int x = strl ...

  2. C++语言中std::array的神奇用法总结,你需要知道!

    摘要:在这篇文章里,将从各个角度介绍下std::array的用法,希望能带来一些启发. td::array是在C++11标准中增加的STL容器,它的设计目的是提供与原生数组类似的功能与性能.也正因此, ...

  3. 香橙派iot板子编译过程_编译过程并不神奇

    编译过程并不神奇 工具 Atmel Studio 过程的简单描述 这篇文章尽可能地说清楚从编译程序到把代码烧到板子里,然后开始上电后运行程序.很多细节是根据Arduino来写的,因为Arduino没有 ...

  4. 神奇的位运算——进制转换问题(16转8)

    说道位运算符,应该都了解,但是很少有人会用,在网上看别人的代码,我至今只见过一道题用到了位运算符,并被它深深的搞晕了(当然,他搞晕我是要负责的,我现在已经深深的迷上了他,真的是太神奇了),位运算符的原 ...

  5. bnu1258 神奇礼物盒 C语言版

    北京师范大学珠海分校 Judge Online of ACM ICPC 1258 神奇礼物盒 C语言版 #include <stdio.h> #include <string.h&g ...

  6. 单词分析 题目描述 小蓝正在学习一门神奇的语言,这门语言中的单词都是由小写英文字母组 成,有些单词很长,远远超过正常英文单词的长度。

    题目描述 小蓝正在学习一门神奇的语言,这门语言中的单词都是由小写英文字母组 成,有些单词很长,远远超过正常英文单词的长度.小蓝学了很长时间也记不住一些单词,他准备不再完全记忆这些单词,而是根据单词中哪 ...

  7. 详解USACO这个神奇的OJ

    今天终于注册了USACO的账号,进入后发现这个OJ非常的神奇.所以特意在这篇文章中了解一下(本文以第一题作为例题讲解这个神奇OJ的不同之处) 先看一下注册的地方,这几个是必须要填的,第一行填邮箱(建议 ...

  8. 一段神奇的c代码错误分析

    源代码 #include <stdio.h>int main(int argc, char* argv[]) {int i = 0;int arr[3] = {0};printf(&quo ...

  9. python deque双端队列的神奇用法

    python中的deque双端队列,类似list的任意一端都可实现较快的add和pop操作 from collections import dequed=deque(maxlen=20) for i ...

最新文章

  1. 充电 | 打开机器学习的大门,需要了解哪些知识?
  2. 【聚能聊有奖话题】今日头条公布算法原理,你认可他们的理念吗?
  3. 向Python女神推荐这些年我追过的经典书籍
  4. 【图文详解】JAVA中的转义字符
  5. ASP.NET Aries 3.0发布(附带通用API设计及基本教程介绍)
  6. 感恩的心,感谢有你--51CTO!
  7. 一位软件工程师的6年总结收藏
  8. RHEL 5基础篇—Linux常用命令参考手册
  9. Java之品优购课程讲义_day03(6)
  10. win10如何打开摄像头_win10系统,蓝牙关闭,如何打开?
  11. List<实体>转json
  12. 微信小程序云开发入门到放弃(一)入门篇
  13. uml点餐系统活动图_UML 活动图
  14. firewalld 规则配置
  15. ql的python学习之路-day7
  16. 用Javascript实现回到顶部效果
  17. 编译 ORB-SLAM2/3的ROS工程造成(You should double-check your ROS_PACKAGE_PATH...)
  18. ThinkPHP源码解析之控制器
  19. android gps 火星坐标,高德地图GCJ-02火星坐标系与GPS的WGS-84坐标系转换公式(安卓)...
  20. 解决 mac 蓝牙鼠标、键盘经常总是 断开连接的问题

热门文章

  1. 当你辛辛苦苦写的博客文章被无情复制,成为了他的原创,你作何感想?
  2. Linux常用命令和vi,gdb的使用
  3. [转载]PHP 计算时间差
  4. CSS伪类的三种写法
  5. 小米羊城通余额不足服务器维护,再不怕羊城通余额不够了!地铁站现自助补票“神器”...
  6. linux 删除带a的文件格式,linux基础命令练习题
  7. ubuntu grep搜索文本
  8. 加密安装Kli Linux
  9. oracle报错ora-00200,oracle 11gR2 rac 创建database报ORA-00200错误
  10. 计算机科学之前说,国内计算机科学十强大学是哪些?前2名没悬念,后面几所都不好说...