zoukankan      html  css  js  c++  java
  • PHP函数二进制安全问题

    本文主要从三个角度来阐述php的二进制安全:1. 什么叫php的二进制安全;2. 什么结构确保了php的二进制安全;3. 这种结构还有哪些其它方面的应用?

    做到知其然,也知其所以然。

    一句话解释:

    php的内部函数在操作二进制数据时能保证达到预期的结果,例如str_replace、stristr、strcmp等函数,我们就说这些函数是二进制安全的。

    举个列子:

    我们来对比一下C和php下的strcmp函数。

    C代码如下

    [objc] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. main(){  
    2.     char ab[] = "aab";  
    3.     char ac[] = "aac";  
    4.     printf("%d ", strcmp(ab, ac));  
    5.     printf("%d ", strlen(ab));  
    6.  }  

    结果:

    0

    2

    解读:

    也就是说C语言认为ab和ac这两个字符串是相等的,而且ab的长度为2.

    php代码如下

    [php] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <?php  
    2.     $ab = "aab";   
    3.     $ac = "aac";  
    4.     var_dump(strcmp($ab, $ac));  
    5.     var_dump(strlen($ab));  
    6.  ?>  

    结果:

    int(-1)

    int(4)

    解读:

    也就是php语言认为ab和ac这两个字符串是相等的,而且ab的长度为4。

    聪明的你,应该已经发现问题在哪了吧,不错,对于c语言‘’是字符串的结束符,所以在C语言中对于字符串“aab”,它读到''就会默认字符读取已经结束,而抛掉后面的字符串'b',导致我们看到strlen(“aab”)的值为2

    那问题又来了,php都是C来开发的,为什么php做到了二进制安全呢?

    先来看看php的变量存储zval结构

    php会根据type的值来决定访问value的哪个成员,为字符串时,我们会访问红框标识的str结构,这便是底层字符串的存储结构,它有两个值,一个是指向字符串的指针val,另一个是记录字符串长度的len值,就是因为有len这个值,导致了php是二进制安全的:因为它不需要像C一样通过是否遇到''结尾符来判断整个字符串是否读取完毕,而是通过len这个值指定的长度进行读取。


    可见一个小小的数据结构改进,为我们带来了更多想象的空间,可谓是结构的一小步,功能的一大步

    拓展:

    这么好用的结构,显然会被到处使用,我们常用的redis,在底层存储数据时就用到了这种结构,Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组),而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,并将 SDS 用作 Redis 的默认字符串表示

    看下SDS的结构定义
    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. struct sdshdr {  
    2.   
    3.     // 记录 buf 数组中已使用字节的数量  
    4.     // 等于 SDS 所保存字符串的长度  
    5.     int len;  
    6.   
    7.     // 记录 buf 数组中未使用字节的数量  
    8.     int free;  
    9.   
    10.     // 字节数组,用于保存字符串  
    11.     char buf[];  
    12.   
    13. };  
    可以看到,我们又见到了熟悉的len值,又是它保证了redis的存储是二进制安全的

    以下内容足以阐明这一点:(摘自http://redisbook.com/preview/sds/different_between_sds_and_c_string.html#id6)

    C 字符串中的字符必须符合某种编码(比如 ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾 ——这些限制使得 C 字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。

    举个例子,如果有一种使用空字符来分割多个单词的特殊数据格式,如图 2-17 所示,那么这种格式就不能使用 C 字符串来保存,因为 C 字符串所用的函数只会识别出其中的 "Redis" ,而忽略之后的 "Cluster" 。

    digraph {    label = "
 图 2-17    使用空字符来分割单词的特殊数据格式";    node [shape = record];    content [label = " 'R' | 'e' | 'd' | 'i' | 's' | '\0' | 'C' | 'l' | 'u' | 's' | 't' | 'e' | 'r' | '\0' "];}

    虽然数据库一般用于保存文本数据,但使用数据库来保存二进制数据的场景也不少见,因此,为了确保 Redis 可以适用于各种不同的使用场景,SDS 的 API 都是二进制安全的(binary-safe):所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设 ——数据在写入时是什么样的,它被读取时就是什么样。

    这也是我们将 SDS 的 buf 属性称为字节数组的原因 ——Redis 不是用这个数组来保存字符,而是用它来保存一系列二进制数据。

    比如说,使用 SDS 来保存之前提到的特殊数据格式就没有任何问题,因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束,如图 2-18 所示。

    digraph {    label = "
 图 2-18    保存了特殊数据格式的 SDS";    rankdir = LR;    node [shape = record];    //    sdshdr [label = "sdshdr | free 
 0 | len 
 14 | <buf> buf"];    buf [label = " { 'R' | 'e' | 'd' | 'i' | 's' | '\0' | 'C' | 'l' | 'u' | 's' | 't' | 'e' | 'r' | '\0' | '\0' } "];    //    sdshdr:buf -> buf;}

    通过使用二进制安全的 SDS ,而不是 C 字符串,使得 Redis 不仅可以保存文本数据,还可以保存任意格式的二进制数据。

  • 相关阅读:
    11g SPA (sql Performance Analyze) 进行升级测试
    SPA游标采集之去除重复
    C++ 实现分数的四则运算
    计算两个数的最大公约数和最小公倍数(欧几里得算法)
    计算a月的第b个星期c
    完数问题
    求整数的最大质因子
    C++ 读取文本文件内容到结构体数组中并排序
    月饼问题PAT B1020(贪心算法)
    路径打印(set以及字符串的相关操作)
  • 原文地址:https://www.cnblogs.com/whelk/p/4493495.html
Copyright © 2011-2022 走看看