引子
为什么 PHP 手册里经常说某个函数是二进制安全的? 我们平常使用函数的时候也没发现有什么区别呀, 那么二进制安全到底是什么意思呢?
PHP 实验
- <?PHP
- echo strlen("abc"); // 3
- echo strlen("abc\0"); // 4
- echo strlen("abc\0d"); // 5
- echo strlen("abc\0def"); // 7
从上面的规律可以看出 \ 0 被认为是一个字符, 其实在上面的式子中 \ 0 是一个 ascii 字符.
补课简单说明下 ascii 码
我们知道, 计算机内部, 所有信息最终都是一个二进制值. 每一个二进制位 (bit) 有 0 和 1 两种状态, 因此八个二进制位就可以组合出 256 种状态, 这被称为一个字节(byte). 也就是说, 一个字节一共可以用来表示 256 种不同的状态, 每一个状态对应一个符号, 就是 256 个符号, 从 `00000000 到 11111111.
上个世纪 60 年代, 美国制定了一套字符编码, 对英语字符与二进制位之间的关系, 做了统一规定. 这被称为 ASCII 码, 一直沿用至今.
man ascii
ascii 码前 33 个都是控制字符, 比如我们最熟悉的换行, 都是不可见的字符.
Oct Dec Hex Char Oct Dec Hex Char
────────────────────────────────────────────────────────────────────────
- 00 0 00 NUL '\0' 100 64 40 @
- 001 1 01 SOH (start of heading) 101 65 41 A
- 002 2 02 STX (start of text) 102 66 42 B
- 003 3 03 ETX (end of text) 103 67 43 C
- 004 4 04 EOT (end of transmission) 104 68 44 D
- 005 5 05 ENQ (enquiry) 105 69 45 E
- 006 6 06 ACK (acknowledge) 106 70 46 F
- 007 7 07 BEL '\a' (bell) 107 71 47 G
- 010 8 08 BS '\b' (backspace) 110 72 48 H
- 011 9 09 HT '\t' (horizontal tab) 111 73 49 I
- 012 10 0A LF '\n' (new line) 112 74 4A J
从上面的表中可以看到八进制 012 和十六进制 0A 都表示换行
- echo "abc\012d";
- echo "\n";
- echo "abc\x0Ad";
- echo "\n";
- echo "abc\x0ad";
- echo "\n";
- echo "abc\nd";
- echo "\n";
输出结果
- abc
- d
- abc
- d
- abc
- d
- abc
- d
ascii 码第一个 \ 0 则表示空字符, 所以执行
echo "abc\0d";
输出的就是 abcd. 到这里上面的长度计算想必大家都弄明白了吧.
这里再留个大家一个思考题, strlen 和 mb_strlen 有什么区别?
有兴趣的可以看看我这篇博客 https://mengkang.net/1129.html
C 实验
- #include <stdio.h>
- #include <string.h>
- int main(){
- printf("%d\n",strlen("abc")); // 3
- printf("%d\n",strlen("abc\0")); // 3
- printf("%d\n",strlen("abc\0d")); // 3
- printf("%d\n",strlen("abc\0def")); // 3
- }
通过上面的例子可以观察到 \ 0 及其以后都不在 strlen 的计算范围内. 在 C 语言里字符串都以 \ 0 作为结束符.
- #include <stdio.h>
- #include <string.h>
- int main(){
- printf("%s\n","abc\0def"); // abc
- }
所以, 因为字符串中间有 \ 0 而影响程序逻辑和结果的情况, 叫非二进制安全.
思考
通过上面的两个实验发现只有 C 里面我们才需要关注函数的二进制安全问题嘛, PHP 也会遇到类似的问题么? 我怎么从来遇到过. 下面两个例子.
案例 1
- <?PHP
- echo date("Y\0/m/d");
- <?PHP
- var_export(strcoll("a\0b","a"));
- var_export(strcmp("a\0b","a"));
- <?PHP
- echo strlen("123\0456");
- echo strlen('123\0456');
来源: https://segmentfault.com/a/1190000020318269