検索
ホームページバックエンド開発PHPチュートリアル罠にはまって、PHP データ型の「壊れた」点を見つけてください。


学習に制限はありません。ただボートに乗って渡ってください~

私はほぼ 1 年にわたって PHP を学び、書いてきました。PHP は弱い型付け言語ですが、それでも C 言語とはまったく異なります。 Java、AS3、およびこれまでに接してきた他の言語も同様に、C からプログラミングを学び始めたことがとても幸運だと感じています。データ型やポインターに関係なく、少なくとも基本的な概念は持っています。

私は PHP データ型で多くの落とし穴を経験し、いくつかのことを学びました。ソース コードを見るのは退屈かもしれませんが、基礎となる実装のいくつかを理解するのは良いことです。後で再び落とし穴に足を踏み入れないでください。

まえがき、

インターネット上で比較的人気のある次のような投稿を目にしました: PHP の ip2long にはバグがあるので、注意して使用してください。そこで説明を読んでみたところ、おおよそ次のような内容です

<?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 には 8 進数を表すために先頭に 0 が付いているため、011 は 10 進数の 9 になります。したがって、58.99.11.1 は 58.99.011.1 とは異なります。8 進数であるため、8 が現れるはずがないため、058.99 .11.1 は不正です。そしてもちろんlongに変換する方法はありません。invalidはfalseを返し、echo falseは当然空と表示されるとマニュアルに書いてありますが、falseですよ~なのでバグはありません。

注: Ip2long は一部の IP では 32 ビットでオーバーフローするため、使用する場合は一般的に sprintf("%u",) が使用されます。注意してください。 1. intval


最大値。オペレーティング システムによって異なります。 32 ビット システムでの符号付き整数の最大範囲は、-2147483648 ~ 2147483647 です。たとえば、そのようなシステムでは、 intval ('1000000000000') は 2147483647 を返します。 64 ビット システムでは、最大の符号付き整数値は 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;
ここでは、さまざまな種類のデータ変換の結果をより明確に確認できます。二重と文字列。タイプが IS_DOUBLE の場合、zend_dval_to_lval マクロが使用されます。このマクロは zend _operators.h で定義されています。実際には、このマクロには他の分岐もありますが、意味はほぼ同じです。 long 型は強制的に long に変換され、結果は c のオーバーフローと同じになります。

型が IS_STRING の場合、C 関数 strtol を直接呼び出します。この関数の機能は次のとおりです。文字列内の整数値が倍長整数の表現範囲 (オーバーフローまたはアンダーフロー) を超える場合、strtol は表現できる最大値を返します。 (または最小の) 整数。したがって、PHP の intval にもこれらの動作があります。



2. ==

# define zend_dval_to_lval(d) ((long) (d))
上記は、PHP の弱い型についての例として、32 ビットと 64 ビットの結果を追加しました。

まず、それぞれは基本的にPHP比較時の型変換に基づいており、これは比較的基礎的な知識です。これらの結果を見て、多くの人は少し感情的になるでしょう~

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"
2 つの文字列が true と比較される理由が非常に興味深いです。 もちろん、32 ビット マシンと 64 ビット マシンでは結果が異なるのは明らかです。整数への変換に関する情報はインターネット上になかったので、関連するソースコードを検索しました。それはおおよそ次のとおりです:


==この比較演算子、2つの文字列を比較する場合、コア呼び出しメソッドは ZEND_IS_EQUAL=>is_equal_function=>compare_function=>zendi_smart_strcmp

次に、zendi_smart_strcmp のソース コードを貼り付けます。あまり長くありません

var_dump("111111111111111111" == "111111111111111112");


ここで、is_numeric_string は zend_operators.h のインライン関数で、文字列が数値であるかどうかを判断し、IS_LONG 型または IS_DOUBLE 型を返します。long 型か double 型かを決定する重要な点は数字です。ソースコードの > = MAX_LENGTH_OF_LONG では、MAX_LENGTH_OF_LONG とは何ですか?

zend.h にはこのマクロ定義があります

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> 32 ビット マシンの場合、long 型は 4 バイト、64 ビット マシンの場合、long 型は 8 バイトであることがわかります。違いはここにあることがわかりました!もちろん、11 と 20 というあらかじめ定義された長さもあり、これは魔法の数字だと思います。 <p> </p> さて、上記の 1 がたくさんある文字列は、32 ビット マシンでは明らかに IS_DOUBLE です。次に、それが制限された値であるかどうかを判断するブランチ zend_finite があります。実際、これらはそれほど重要ではありません。文は <p> </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
ここで、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、

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

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

mysql中银行卡号用什么类型mysql中银行卡号用什么类型Jun 14, 2023 pm 04:34 PM

mysql中银行卡号用“varchar”字符串类型,因为银行卡的号码较长并且全是数字,为了方便存储,就统一存储为字符串类型。如果用“number”类型,会超出“int”类型的最大值范围,必须用“bigInteger”存储,而它不利于数据的正常转换。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!

MantisBT

MantisBT

Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

mPDF

mPDF

mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

SublimeText3 Linux 新バージョン

SublimeText3 Linux 新バージョン

SublimeText3 Linux 最新バージョン