搜尋
首頁後端開發php教程踩一坑,采一金之php数据类型那点“破”事

   

    学海无涯,乘舟以渡之~

    php边学边写差不多一年多点,php这种弱类型语言与之前接触的c、java、as3等语言还是挺不一样的,现在觉得很庆幸的是从c开始学编程,无论数据类型还是指针也好,至少有个基础的概念。

    在php数据类型上踩了不少坑,也学到了一些东西,在这里分享一下,看源码可能会很枯燥,不过了解一些底层实现就好,后面不要再踩坑。

序、

    之前在网上看到有比较热的帖子说:PHP的ip2long有bug,请慎用?于是看了下描述,大致如下

<?php echo ip2long('58.99.11.1'),"<br/>";   //输出是979569409 echo ip2long('58.99.011.1'),"<br>";  //输出是979568897 echo ip2long('058.99.11.1'),"<br>";  //输出是空 
    看上面看似“一样”的IP地址,输出的结果“竟然”不一样。于是那个帖子得出结论:在 PHP 4.x,5.x 中 , 有前导零的 ip 转换的结果都不正确。

这货真的懂编程语言,真的懂数据类型么?

    源码不贴了,在ext/standard/basic_functions.c文件中(5.3.28),无非就是直接调用c函数inet_pton或者inet_addr,然后调用ntohl转换一下字节序。不用多说,011有前导0表示8进制,于是011就变成了十进制9,所以58.99.11.1与58.99.011.1是不一样的,既然是8进制,绝不可能出现8吧,所以058.99.11.1不合法,当然也没办法转换为long,手册里写了,invalid会返回false,echo false当然显示为空,但是人家是false~所以没bug的。

     注:Ip2long对于部分ip在32位会溢出,所以使用时一般使用sprintf(“%u”,),注意一下就好了

一、intval

    最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647 。举例,在这样的系统上, intval ('1000000000000') 会返回 2147483647 。 64 位系统上,最大带符号的 integer 值是 9223372036854775807 。

$i = intval('2355200853');$j = intval(2355200853);var_dump($i);var_dump($j);int(2147483647) int(-1939766443) 
    intval源码最终调用的是 convert_to_long_base函数,简单贴下部分源码(Zend/zend_operators.c):

           switch (Z_TYPE_P(op)) {		case IS_NULL:			Z_LVAL_P(op) = 0;			break;		case IS_RESOURCE: {				TSRMLS_FETCH();				zend_list_delete(Z_LVAL_P(op));			}			/* break missing intentionally */		case IS_BOOL:		case IS_LONG:			break;		case IS_DOUBLE:			Z_LVAL_P(op) = zend_dval_to_lval(Z_DVAL_P(op));			break;		case IS_STRING:			{				char *strval = Z_STRVAL_P(op);				Z_LVAL_P(op) = strtol(strval, NULL, base);				STR_FREE(strval);			}			break;		case IS_ARRAY:			tmp = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);			zval_dtor(op);			Z_LVAL_P(op) = tmp;			break;
    可以比较清晰的看到各种类型数据转换的结果,这里关注下double和string。如果类型是IS_DOUBLE使用了zend_dval_to_lval宏,这个宏在zend _operators.h中定义了,主要的含义就是
# define zend_dval_to_lval(d) ((long) (d))
    实际上这个宏还有其他分支,不过意思大致如此,对于long型已经溢出的double强转为long,结果与c中一样,溢出了。

    如果类型是IS_STRING,直接调用c函数strtol,这个函数功能是:如果字符串中的整数值超出longint的表示范围(上溢或下溢),则strtol返回它所能表示的最大(或最小)整数。所以php的intval也就拥有了这些行为。

二、==

var_dump(in_array(0, array('s'))); var_dump(0 == "string");var_dump("1111" == "1112");var_dump("111111111111111111" == "111111111111111112");$str = 'string';var_dump($str['aaa']);32位bool(true) bool(true) bool(false) bool(true) string(1) "s" 64位bool(true)bool(true)bool(false)bool(false)string(1) "s"
   上面是很多人会对php弱类型举的一些例子,我加上了32位和64位的结果。

   首先,每个基本上都基于php比较时的类型转换,是比较基础的知识。很多人看到这些结果也都会有点感慨~

var_dump("111111111111111111" == "111111111111111112");
   我很好奇的是这两个字符串比较为什么位true,当然在32位和64位机器结果不同,显然与转整型有关,在网上没看到其他人有解释,于是搜寻了下源码相关。大致如下:

   ==这个比较操作符,在比较两个字符串的时候,核心调用方法为ZEND_IS_EQUAL=>is_equal_function=>compare_function=>zendi_smart_strcmp

   然后贴下zendi_smart_strcmp的源码,不是很长

ZEND_API void zendi_smart_strcmp(zval *result, zval *s1, zval *s2) /* {{{ */{	int ret1, ret2;	long lval1, lval2;	double dval1, dval2;	if ((ret1=is_numeric_string(Z_STRVAL_P(s1), Z_STRLEN_P(s1), &lval1, &dval1, 0)) &&		(ret2=is_numeric_string(Z_STRVAL_P(s2), Z_STRLEN_P(s2), &lval2, &dval2, 0))) {		if ((ret1==IS_DOUBLE) || (ret2==IS_DOUBLE)) {			if (ret1!=IS_DOUBLE) {				dval1 = (double) lval1;			} else if (ret2!=IS_DOUBLE) {				dval2 = (double) lval2;			} else if (dval1 == dval2 && !zend_finite(dval1)) {				/* Both values overflowed and have the same sign,				 * so a numeric comparison would be inaccurate */				goto string_cmp;			}			Z_DVAL_P(result) = dval1 - dval2;			ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));		} else { /* they both have to be long's */			ZVAL_LONG(result, lval1 > lval2 ? 1 : (lval1   <p></p>  <p>   其中is_numeric_string是zend_operators.h中的一个inline函数,判断字符串是不是数字,并且返回IS_LONG或者IS_DOUBLE类型,其中决定是long还是double比较关键的点是源码中的digits >= MAX_LENGTH_OF_LONG,那么MAX_LENGTH_OF_LONG又是个什么东西?</p>  <p>   在zend.h中有这个宏定义</p>  <p></p>  <pre name="code" class="sycode">#if SIZEOF_LONG == 4#define MAX_LENGTH_OF_LONG 11static const char long_min_digits[] = "2147483648";#elif SIZEOF_LONG == 8#define MAX_LENGTH_OF_LONG 20static const char long_min_digits[] = "9223372036854775808";#else#error "Unknown SIZEOF_LONG"#endif

   大致明白了,对于32位机器long型是4字节,64位机器long型是8字节,原来差别在这里!当然也预定义了个长度,11和20两个我觉得挺magic的number。

   好,上面那个那么多个1的字符串在32位机器上显然就是IS_DOUBLE了,接下来有个分支zend_finite判断是否是有限值,其实这些现在看都不是很重要,最重要的一句话是

Z_DVAL_P(result) = dval1 - dval2;ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
   其中ZEND_NORMALIZE_BOOL宏是用来标准化bool值的

#define ZEND_NORMALIZE_BOOL(n)			\	((n) ? (((n)>0) ? 1 : -1) : 0)
   好,dval1-dval2究竟是什么呢,这时要想到double型的有效位数了,C里double型有效位数大概16位,上面那个字符串是18个1,已经超出了有效位数,做减法已经不会准确了,这里不想去深究double型的表示,简单用c语言展示一下。

#include <stdio.h>int main() {double a = 11111 11111 11111 12.0L;double b = 11111111111111111.0L;double c= 11111111111111114.0L;printf("%lf" , a-b);printf("%d" , a-b == 0);printf("%lf" , c-b);printf("%d" , c-b == 0);}</stdio.h>
   对于这样一个c程序,输出结果为
0.00000012.0000000
   在32位机器与64位机器上相同,因为double型都是8字节。

   可以试一下,尾数1、2、3相减都是0,到了尾数为4才会发生变化,结果也不精确,下面看下内存中表示:

double c = 11111111111111111.0L;double d = 11111111111111112.0L;double e = 11111111111111113.0L;double f = 11111111111111114.0L;double *p = &c;printf("%x, %x\n" , ((int *)p)[0], ((int *)p)[1]);p = &d;printf("%x, %x\n" , ((int *)p)[0], ((int *)p)[1]);p = &e;printf("%x, %x\n" , ((int *)p)[0], ((int *)p)[1]);p = &f;printf("%x, %x\n" , ((int *)p)[0], ((int *)p)[1]);
   其实就是将double型强转位int数组,然后转16进制输出,结果为:

936b38e4, 4343bcbf936b38e4, 4343bcbf936b38e4, 4343bcbf936b38e5, 4343bcbf
   可以看到尾数为4的那位不太一样,结合上面,这就是为什么
var_dump("111111111111111111" == "111111111111111112");
   在32位机器结果为true的原因,4字节溢出转成double,然后相减不精确了,变成了0,导致相等。64位机器因为没溢出,所以为false。


三、array_flip

   在32位机器上,使用企业QQ号码做关联数组key的时候,需要注意大于21亿的问题

32位$a = array(2355199999 => 1, 2355199998 => 1);var_dump($a);array(2) { [-1939767297]=> int(1) [-1939767298]=> int(1) } $b = array(2355199999, 2355199998);var_dump($b);array(2) { [0]=> float(2355199999) [1]=> float(2355199998) } var_dump(array_flip($b));Warning: array_flip() Can only flip STRING and INTEGER values!$c = array();foreach($b as $key => $value) {    $c[$value] = $key;}var_dump($c);
   因为key只能为string或者interger,在32位机器上,大于21亿就成为了float,所以如果强行拿float去做key,会溢出变成类似负数等等~这里如果将大于21亿的数加上引号才可以


四、array_merge

   简单说下,array_merge在文档上有写明,如果key为整数,merge后key会成为按照自然数重新排列

例如

<?php $a = array(5 => 5, 7 => 4);$b = array(1 => 1, 9 => 9);var_dump(array_merge($a, $b));

   输出是array(4) { [0]=> int(5) [1]=> int(4) [2]=> int(1) [3]=> int(9)}

   源码实现比较简单,我也看过,就是碰到整数就使用nextindex,碰到字符串就正常insert。

   于是在32位机器上,如果key大于21亿的话,array_merge不会将key使用nextindex变成自然数重新排,在64位机上当然大于21亿也没有用~

   所以如果key为整数,合并数组的时候可以使用array+array这样代替。

   array_merge($a, $b)的时候如果字符串key相同,$b会覆盖$a,如果key为32位或者64位long整数范围内,则不会覆盖,因为实现的时候是简单的遍历覆盖插入hashtable。

   array+array如果key相同,是保留前者,抛弃后者。


结、

   我很庆幸第一门语言学的是c语言,虽然本科懵懂的简单代码写的挺溜,各种技术了解比较少,但是有了c语言及一些c++的基础,研究其他语言还是会容易很多,能够揣摩到一些底层实现原理,当然底层原理还是要再深入的学习。




陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
dint是什么数据类型dint是什么数据类型Sep 05, 2022 am 11:05 AM

dint是带符号位的32位整数类型;dint的表示方法及范围是“L#-2147483648~L#+2147483647”,定义为双整数或长整数,字节是电脑里的数据量单位,在计算机中,数据只用0和1这种表现形式。

Python时间序列数据操作的常用方法总结Python时间序列数据操作的常用方法总结Apr 24, 2023 pm 10:22 PM

时间序列数据是一种在一段时间内收集的数据类型,它通常用于金融、经济学和气象学等领域,经常通过分析来了解随着时间的推移的趋势和模式Pandas是Python中一个强大且流行的数据操作库,特别适合处理时间序列数据。它提供了一系列工具和函数可以轻松加载、操作和分析时间序列数据。在本文中,我们介绍时间序列数据的索引和切片、重新采样和滚动窗口计算以及其他有用的常见操作,这些都是使用Pandas操作时间序列数据的关键技术。数据类型Python在Python中,没有专门用于表示日期的内置数据类型。一般情况下都

mysql性别用什么类型mysql性别用什么类型Jun 13, 2023 am 11:33 AM

MySQL性别采用多种数据类型来表示性别字段,例如CHAR、ENUM等,最终采用哪种类型,取决于实际需求以及数据存储的大小和性能。

java的数据类型有哪些java的数据类型有哪些Jan 30, 2024 pm 03:23 PM

java数据类型:1、整型;2、浮点型;3、字符型;4、布尔型;5、其他数据类型;6、引用类型;7、原始类型与封装类;8、自动装箱与拆箱;9、可变参数;10、注解;11、枚举;12、原始类型和引用类型的选择。Java是一种强类型语言,因此每种数据都有其固定类型。

decimal是什么类型decimal是什么类型Mar 18, 2021 pm 04:03 PM

decimal是MySQL中存在的精准数据类型,语法格式“DECIMAL(M,D)”。其中,M是数字的最大数(精度),其范围为“1~65”,默认值是10;D是小数点右侧数字的数目(标度),其范围是“0~30”,但不得超过M。

MySQL数据类型详解:你需要知道的知识点MySQL数据类型详解:你需要知道的知识点Jun 15, 2023 am 08:56 AM

MySQL是世界上最流行的关系型数据库管理系统之一,因其可靠性、高安全性、高扩展性以及相对低的成本而得到了广泛应用。MySQL的数据类型定义了各种数据类型的存储方式,是MySQL的重要组成部分。本文将详解MySQL的数据类型,以及在实际应用中需要注意的一些知识点。一、MySQL的数据类型分类MySQL的数据类型可以分为以下几类:整数类型:包括TINYINT、

表中字段的数据类型有哪些表中字段的数据类型有哪些Jan 19, 2021 am 10:18 AM

表中字段的数据类型有:1、二进制类型,包括Binary、Varbinary、Image;2、字符串类型,包括CHAR、VARCHAR、TEXT等;3、Unicode数据类型,包括Nchar,Nvarchar和Ntext;4、日期和时间数据类型,包括DATE、TIME、YEAR等;5、数值数据类型,包括INT、FLOAT、BIGINT等;6、货币数据类型;7、特殊数据类型等等。

PHP8中支持的新数据类型可以让你的代码变得更加清新PHP8中支持的新数据类型可以让你的代码变得更加清新Jun 21, 2023 am 11:20 AM

随着PHP8的发布,这个流行的编程语言引入了新的数据类型,这些新类型可以大大简化代码并提高代码的可读性。在本文中,我们将介绍PHP8中的四种新类型:联合类型、命名参数、只读属性和允许为空的属性,并解释它们如何为开发者带来更好的编程体验。联合类型联合类型是PHP8中引入的一种新类型,它可以让开发者在一个变量中存储多种不同类型的值。例如,一个变量可以

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
2 週前By尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
1 個月前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器