從php內核角度分析php弱類型

PHP技術大全 / 2019-03-15 15:01:10

前言

在CTF比賽中PHP弱類型的特性常常被用上,但我們往往知其然不知其所以然,究竟為什么PHP是弱類型呢?很少人深究。在這次源碼分析的過程中我收獲很大,第一次學會了如何深入理解一個問題,雖然花費了我很多時間,但這可以說是一段非常值得的經歷。

正文

首先引入一個問題,為什么以下結果是恒為真的呢?

var_dump([]>1);
var_dump([]>0);
var_dump([]>-1);

當然實際ctf中問題可能會如下

$_GET[Password]>99999;

當傳入Password[]=1

時侯恒為真

當然再換一種形式

var_dump([[]]>[1]);

依舊是恒為真

對于這類問題,很多人都是認為PHP因為它是弱類型語言它就有這種特性

那么為什么PHP會有這種特性呢?

我們首先查閱下PHP手冊

http://php.net/manual/en/language.operators.comparison.php#language.operators.comparison.types

在手冊中寫到,當array和anything進行比較的時候array is always greater

這是一種PHP的定義。

那么究竟PHP到底在哪定義了這種特點呢?

我們依舊不知道。

我們再拋出個問題究竟什么是PHP弱類型呢?

很多人可能會回答弱類型就是弱類型,當傳入Password[]=1就會繞過這就是弱類型

這種回答肯定是不妥當的

具體弱類型定義

PHP是弱類型語言,不需要明確的定義變量的類型,變量的類型根據使用時的上下文所決定,也就是變量會根據不同表達式所需要的類型自動轉換,比如求和,PHP會將兩個相加的值轉為long、double再進行加和。每種類型轉為另外一種類型都有固定的規則,當某個操作發現類型不符時就會按照這個規則進行轉換,這個規則正是弱類型實現的基礎。

我們再通過查閱PHP源碼來深刻理解PHP弱類型的特點

PHP是開源的一種語言,我們在Github上可以很容易的查詢到它的源碼

傳送門

這里找函數會方便點

當然解釋下什么是Zend

Zend是PHP語言實現的最為重要的部分,是PHP最基礎、最核心的部分,它的源碼在/Zend目錄下,PHP代碼從編譯到執行都是由Zend完成的

至于為什么要查詢zend_operators.h這個文件,operator操作符,其他幾個文件不像存在比較函數,有的時候查源碼時候就是需要靠感覺,這種大項目 函數變量什么的都有規范 一般所見即所得 看懂英語就大概猜得到用途的,

當然這個文件也不一般

我再進行解釋下,當然想深入理解可以看 這里

PHP在內核中是通過zval這個結構體來存儲變量的,它的定義在Zend/zend.h文件里,簡短精煉,只有四個成員組成:

我們定位到函數

ZEND_API int ZEND_FASTCALL is_smaller_function(zval result, zval op1, zval *op2);

這里傳入了兩個值op1,op2,傳出一個result

解釋下zval類型

zval以一個P結尾的宏的參數大多是 zval型變量。 此外獲取變量類型的宏還有兩個,分別是Z_TYPE和Z_TYPE_PP,前者的參數是zval型,而后者的參數則是* zval。

這樣說可能會有些抽象

我們換種方式解釋,當再php源碼中要想判斷一個變量的類型最直接的方式,比如想判斷這個變量是否為空

變量->type == IS_NULL

這種方法雖然是正確的,但PHP官網并不建議這么做,PHP中定義了大量的宏,供我們檢測、操作變量使用

解釋下什么是宏

C語言中允許用一個標識符來標識一個字符串,稱為“宏”;標識符為“宏名”。在編譯預處理時,對程序中所有出現的“宏名”,都用宏定義時的字符串去代換,簡稱“宏代換”或“宏展開”。一般形式:#define 宏名 字符串

宏定義說明及注意:

宏定義時用宏名來表示一個字符串,在宏展開時又以該字符串替換了宏名,這只是一個簡單的替換;

宏定義不需要再行末加分號,若加上分號,則會連分號也會被替換的;

宏定義必須在函數外面;宏定義的作用域:從定義命令至程序結束,若想終止宏的作用域,則使用undef命令;

宏名在程序中用引號括起來,則預處理程序對其不進行宏替換;

宏定義是可以嵌套使用的,在展開時,由預處理程序層層替換;

建議在進行宏定義時,盡量使用大寫字母表示宏名;

可用宏來表示數據類型,使書寫方便;

對“輸出格式”做用定義,可以減少書寫麻煩。

PHP建議使用的形式

Z_TYPE_P(變量) == IS_NULL

以一個P結尾的宏的參數大多是 zval型變量。 此外獲取變量類型的宏還有兩個,分別是Z_TYPE和Z_TYPE_PP,前者的參數是zval型,而后者的參數則是* zval

這樣我們便可以猜測一下php內核是如何實現gettype這個函數了,代碼如下:想要詳細了解的可以看 這里

//開始定義php語言中的函數gettype
PHP_FUNCTION(gettype)
{
//arg間接指向調用gettype函數時所傳遞的參數。是一個zval**結構
//所以我們要對他使用__PP后綴的宏。
zval **arg;

//這個if的操作主要是讓arg指向參數~
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) {
return;
}

//調用Z_TYPE_PP宏來獲取arg指向zval的類型。
//然后是一個switch結構,RETVAL_STRING宏代表這gettype函數返回的字符串類型的值
switch (Z_TYPE_PP(arg)) {
case IS_NULL:
RETVAL_STRING("NULL", 1);
break;

case IS_BOOL:
RETVAL_STRING("boolean", 1);
break;

case IS_LONG:
RETVAL_STRING("integer", 1);
break;

case IS_DOUBLE:
RETVAL_STRING("double", 1);
break;

case IS_STRING:
RETVAL_STRING("string", 1);
break;

case IS_ARRAY:
RETVAL_STRING("array", 1);
break;

case IS_OBJECT:
RETVAL_STRING("object", 1);
break;

case IS_RESOURCE:
{
char *type_name;
type_name = zend_rsrc_list_get_rsrc_type(Z_LVAL_PP(arg) TSRMLS_CC);
if (type_name) {
RETVAL_STRING("resource", 1);
break;
}
}

default:
RETVAL_STRING("unknown type", 1);
}
}

以上三個宏的定義在Zend/zend_operators.h里,定義分別是:

define Z_TYPE(zval) (zval).type

define Z_TYPE_P(zval_p) Z_TYPE(*zval_p)

define Z_TYPE_PP(zval_pp) Z_TYPE(**zval_pp)

這也是為是什么在Zend/zend_operators.h里面進行查詢的原因,貌似有些跑題了?

當然下一個問題,為什么我們要定位到函數is_smaller_function

這里主要是靠對于PHP源碼的熟悉,進行猜測,當然有的時候分析源碼的時候可以講PHP源碼下載下載,部分IDE會有提供函數來源的功能

其實本來有個

lxr.php.net

可以讓我們迅速定位到我們想要的函數,但是這個網站在16年后就不是很穩定了,甚至有人將它當做一個BUG提交給PHP官網,這是一個很有趣的事情,具體可以了解 這里

那么我們還有沒有什么辦法迅速定位到我們需要的函數呢?

進入is_smaller_function的函數

ZEND_API int ZEND_FASTCALL is_smaller_function(zval *result, zval *op1, zval *op2) /* {{{ */
{
if (compare_function(result, op1, op2) == FAILURE) {
return FAILURE;
}
ZVAL_BOOL(result, (Z_LVAL_P(result) < 0));
return SUCCESS;
}

這里有一個compare_function函數以及

ZVAL_BOOL

我們先分析下compare_function函數

跟進

ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) /* {{{ */
{
int ret;
int converted = 0;
zval op1_copy, op2_copy;
zval *op_free, tmp_free;

while (1) {
switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
case TYPE_PAIR(IS_LONG, IS_LONG):
ZVAL_LONG(result, Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1) return SUCCESS;

case TYPE_PAIR(IS_DOUBLE, IS_LONG):
Z_DVAL_P(result) = Z_DVAL_P(op1) - (double)Z_LVAL_P(op2);
ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
return SUCCESS;

case TYPE_PAIR(IS_LONG, IS_DOUBLE):
Z_DVAL_P(result) = (double)Z_LVAL_P(op1) - Z_DVAL_P(op2);
ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
return SUCCESS;

case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
if (Z_DVAL_P(op1) == Z_DVAL_P(op2)) {
ZVAL_LONG(result, 0);
} else {
Z_DVAL_P(result) = Z_DVAL_P(op1) - Z_DVAL_P(op2);
ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
}
return SUCCESS;

case TYPE_PAIR(IS_ARRAY, IS_ARRAY):
ZVAL_LONG(result, zend_compare_arrays(op1, op2));
return SUCCESS;

case TYPE_PAIR(IS_NULL, IS_NULL):
case TYPE_PAIR(IS_NULL, IS_FALSE):
case TYPE_PAIR(IS_FALSE, IS_NULL):
case TYPE_PAIR(IS_FALSE, IS_FALSE):
case TYPE_PAIR(IS_TRUE, IS_TRUE):
ZVAL_LONG(result, 0);
return SUCCESS;

case TYPE_PAIR(IS_NULL, IS_TRUE):
ZVAL_LONG(result, -1);
return SUCCESS;

case TYPE_PAIR(IS_TRUE, IS_NULL):
ZVAL_LONG(result, 1);
return SUCCESS;

case TYPE_PAIR(IS_STRING, IS_STRING):
if (Z_STR_P(op1) == Z_STR_P(op2)) {
ZVAL_LONG(result, 0);
return SUCCESS;
}
ZVAL_LONG(result, zendi_smart_strcmp(Z_STR_P(op1), Z_STR_P(op2)));
return SUCCESS;

case TYPE_PAIR(IS_NULL, IS_STRING):
ZVAL_LONG(result, Z_STRLEN_P(op2) == 0 ? 0 : -1);
return SUCCESS;

case TYPE_PAIR(IS_STRING, IS_NULL):
ZVAL_LONG(result, Z_STRLEN_P(op1) == 0 ? 0 : 1);
return SUCCESS;

case TYPE_PAIR(IS_OBJECT, IS_NULL):
ZVAL_LONG(result, 1);
return SUCCESS;

case TYPE_PAIR(IS_NULL, IS_OBJECT):
ZVAL_LONG(result, -1);
return SUCCESS;

default:
if (Z_ISREF_P(op1)) {
op1 = Z_REFVAL_P(op1);
continue;
} else if (Z_ISREF_P(op2)) {
op2 = Z_REFVAL_P(op2);
continue;
}

if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, compare)) {
ret = Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2);
if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) {
convert_compare_result_to_long(result);
}
return ret;
} else if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJ_HANDLER_P(op2, compare)) {
ret = Z_OBJ_HANDLER_P(op2, compare)(result, op1, op2);
if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) {
convert_compare_result_to_long(result);
}
return ret;
}

if (Z_TYPE_P(op1) == IS_OBJECT && Z_TYPE_P(op2) == IS_OBJECT) {
if (Z_OBJ_P(op1) == Z_OBJ_P(op2)) {
/* object handles are identical, apparently this is the same object */
ZVAL_LONG(result, 0);
return SUCCESS;
}
if (Z_OBJ_HANDLER_P(op1, compare_objects) == Z_OBJ_HANDLER_P(op2, compare_objects)) {
ZVAL_LONG(result, Z_OBJ_HANDLER_P(op1, compare_objects)(op1, op2));
return SUCCESS;
}
}
if (Z_TYPE_P(op1) == IS_OBJECT) {
if (Z_OBJ_HT_P(op1)->get) {
zval rv;
op_free = Z_OBJ_HT_P(op1)->get(Z_OBJ_P(op1), &rv);
ret = compare_function(result, op_free, op2);
zend_free_obj_get_result(op_free);
return ret;
} else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) {
ZVAL_UNDEF(&tmp_free);
if (Z_OBJ_HT_P(op1)->cast_object(Z_OBJ_P(op1), &tmp_free, ((Z_TYPE_P(op2) == IS_FALSE || Z_TYPE_P(op2) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op2))) == FAILURE) {
ZVAL_LONG(result, 1);
zend_free_obj_get_result(&tmp_free);
return SUCCESS;
}
ret = compare_function(result, &tmp_free, op2);
zend_free_obj_get_result(&tmp_free);
return ret;
}
}
if (Z_TYPE_P(op2) == IS_OBJECT) {
if (Z_OBJ_HT_P(op2)->get) {
zval rv;
op_free = Z_OBJ_HT_P(op2)->get(Z_OBJ_P(op2), &rv);
ret = compare_function(result, op1, op_free);
zend_free_obj_get_result(op_free);
return ret;
} else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) {
ZVAL_UNDEF(&tmp_free);
if (Z_OBJ_HT_P(op2)->cast_object(Z_OBJ_P(op2), &tmp_free, ((Z_TYPE_P(op1) == IS_FALSE || Z_TYPE_P(op1) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op1))) == FAILURE) {
ZVAL_LONG(result, -1);
zend_free_obj_get_result(&tmp_free);
return SUCCESS;
}
ret = compare_function(result, op1, &tmp_free);
zend_free_obj_get_result(&tmp_free);
return ret;
} else if (Z_TYPE_P(op1) == IS_OBJECT) {
ZVAL_LONG(result, 1);
return SUCCESS;
}
}
if (!converted) {
if (Z_TYPE_P(op1) < IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op2) ? -1 : 0);
return SUCCESS;
} else if (Z_TYPE_P(op1) == IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op2) ? 0 : 1);
return SUCCESS;
} else if (Z_TYPE_P(op2) < IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op1) ? 1 : 0);
return SUCCESS;
} else if (Z_TYPE_P(op2) == IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op1) ? 0 : -1);
return SUCCESS;
} else {
op1 = zendi_convert_scalar_to_number(op1, &op1_copy, result, 1);
op2 = zendi_convert_scalar_to_number(op2, &op2_copy, result, 1);
if (EG(exception)) {
if (result != op1) {
ZVAL_UNDEF(result);
}
return FAILURE;
}
converted = 1;
}
} else if (Z_TYPE_P(op1)==IS_ARRAY) {
ZVAL_LONG(result, 1);
return SUCCESS;
} else if (Z_TYPE_P(op2)==IS_ARRAY) {
ZVAL_LONG(result, -1);
return SUCCESS;
} else {
ZEND_ASSERT(0);
zend_throw_error(NULL, "Unsupported operand types");
if (result != op1) {
ZVAL_UNDEF(result);
}
return FAILURE;
}
}
}
}
/* }}} */

有點長,想要仔細了解的可以詳細看

講解下

首先

這個先等下說

這里進行swich 判斷op1 與 op2 的類型

這里我們先拿第一句進行分析

case TYPE_PAIR(IS_LONG, IS_LONG):
ZVAL_LONG(result, Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1) return SUCCESS;

這里op1與op2都是IS_LONG類型

PHP中一共如下八種數據類型,具體想了解可以 看這

所以IS_LONG是一種PHP種的整型。

ZVAL_LONG(result, Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1)

這句的意思是進行比較OP1,OP2的大小分別返回-1,0,1到result,

這里的result是有作用的,

這里有一個ZVAL_BOOL函數進行判斷,用于設置布爾值的zval ,ZVAL_BOOL就是定義這個zval的類型為bool。

#define ZVAL_BOOL(z, b) do {            
Z_TYPE_INFO_P(z) =
(b) ? IS_TRUE : IS_FALSE;
} while (0)

換成當前的場景

result為z ,(Z_LVAL_P(result) < 0)為b

z 為用于設置布爾值的zval

b 為 設置的布爾值

這個函數 名是is_smaller_function具體意思已經很明顯了

只有 Z_LVAL_P(result) < 0,當result=-1

(即op1

才會使b=1 并且使得

(b) ? IS_TRUE : IS_FALSE; 判斷為IS_TRUE

并使得Z_TYPE_INFO_P(result) 為IS_TRUE,

最后就是根據Z_TYPE_INFO_P(result) 使IS_TRUE或者IS_FALSE來判斷究竟是否小于

下一句

因為兩個值是可以進行比較的它會return SUCCESS,我是這么理解的

如果有人看到這里,對于PHP究竟是如何判斷大小應該有了基本的認識了吧

回到我們最開始的問題

那么我們就應該取尋找OP1與OP2分別為array類型與IS_LONG的case

與OP1與OP2分別為array類型與array類型

當然閱讀這些case的時候又冒出了個問題

這個又是什么意思呢?

經過查詢我們可以知道這句話來源于

#define Z_ISREF(zval) (Z_TYPE(zval) == IS_REFERENCE)

其意思為

該zval檢查它是否是一個引用類型,姑且認為是判斷這個變量是否屬于PHP八種變量中的一種,

那么IS_REFERENCE又是什么呢

此類型用于表示a zval是PHP引用。引用的值zval需要首先解除引用才能使用它。這可以使用ZVAL_DEREF或Z_REF宏來完成。zval可以檢查A 以查看它是否是Z_ISREF宏的引用。

姑且認為這個意思是zaval確實是PHP引用的變量之一

那么整句話的我的理解是,當發生default:的時候假如OP1,OP2是PHP引用變量之一那么就繼續

接下來的幾個case都不屬于我們想要的情況

直到

if (!converted) {
if (Z_TYPE_P(op1) < IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op2) ? -1 : 0);
return SUCCESS;
} else if (Z_TYPE_P(op1) == IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op2) ? 0 : 1);
return SUCCESS;
} else if (Z_TYPE_P(op2) < IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op1) ? 1 : 0);
return SUCCESS;
} else if (Z_TYPE_P(op2) == IS_TRUE) {
ZVAL_LONG(result, zval_is_true(op1) ? 0 : -1);
return SUCCESS;
} else {
op1 = zendi_convert_scalar_to_number(op1, &op1_copy, result, 1);
op2 = zendi_convert_scalar_to_number(op2, &op2_copy, result, 1);
if (EG(exception)) {
if (result != op1) {
ZVAL_UNDEF(result);
}
return FAILURE;
}
converted = 1;
}
} else if (Z_TYPE_P(op1)==IS_ARRAY) {
ZVAL_LONG(result, 1);
return SUCCESS;
} else if (Z_TYPE_P(op2)==IS_ARRAY) {
ZVAL_LONG(result, -1);
return SUCCESS;
} else {
ZEND_ASSERT(0);
zend_throw_error(NULL, "Unsupported operand types");
if (result != op1) {
ZVAL_UNDEF(result);
}
return FAILURE;
}

因為在函數的開頭converted=0

所以!converted=1是正確的,

我們跟進這個判斷

發現

這邊只要op1為IS_ARRAY類型的變量就result直接就為1了

這也解釋了我們之前的問題

為什么[]無論是比較1,0,-1都是返回true

以及PHP手冊中

中的這個問題

當然我們依舊留存下一個問題

為什么這個也是恒真的呢?

可以清楚看到左右兩邊都是數組,我們需要找到arrary與arrary的這種case

在最開始沒幾行就可以找到了

這里有一個函數zend_compare_arrays

我們跟進一下

我們可以看到它返回了一個zend_compare_symbol_tables函數

我們再跟進下

當然在傳入參數的時候又經歷了Z_ARRVAL_P(a1)的變化

Z_ARRVAL_P(a1)源自

define Z_ARRVAL(zval) Z_ARR(zval)

大概的含義是從數組中抓取hash值,

這里需要傳入HashTable *ht1

那么HashTable 又是什么呢?

在學數據結構的時候我們都有學到hash,

其實對于hashtable我之前的印象是比如python中的字典它的原理就是采取hash表,即采取鍵值對的方式進行查詢數據,比起鏈表等方式查詢無疑是要快的多

那么這里的hashtable又是否和我想的一樣呢?具體看 這里

PHP內核中的哈希表是十分重要的數據結構,PHP的大部分的語言特性都是基于哈希表實現的, 例如:變量的作用域、函數表、類的屬性、方法等,Zend引擎內部的很多數據都是保存在哈希表中的。
PHP中的哈希表實現在Zend/zend_hash.c中,先看看PHP實現中的數據結構, PHP使用如下兩個數據結構來實現哈希表,HashTable結構體用于保存整個哈希表需要的基本信息, 而Bucket結構體用于保存具體的數據內容,如下:
typedef struct _hashtable { 
uint nTableSize; // hash Bucket的大小,最小為8,以2x增長。
uint nTableMask; // nTableSize-1 , 索引取值的優化
uint nNumOfElements; // hash Bucket中當前存在的元素個數,count()函數會直接返回此值
ulong nNextFreeElement; // 下一個數字索引的位置
Bucket *pInternalPointer; // 當前遍歷的指針(foreach比for快的原因之一)
Bucket *pListHead; // 存儲數組頭元素指針
Bucket *pListTail; // 存儲數組尾元素指針
Bucket **arBuckets; // 存儲hash數組
dtor_func_t pDestructor; // 在刪除元素時執行的回調函數,用于資源的釋放
zend_bool persistent; //指出了Bucket內存分配的方式。如果persisient為TRUE,則使用操作系統本身的內存分配函數為Bucket分配內存,否則使用PHP的內存分配函數。
unsigned char nApplyCount; // 標記當前hash Bucket被遞歸訪問的次數(防止多次遞歸)
zend_bool bApplyProtection;// 標記當前hash桶允許不允許多次訪問,不允許時,最多只能遞歸3次
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;

當然如果要詳細講PHP中的hashtable講清楚肯定要再寫另一篇博客,這里我們就只講這里所需要的原理

這里進行兩個參數的判斷,當兩個參數hash值相等時候就返回0

我們可以直接看看php數組的hash,具體點 這里

這是在PHP5.6的數組結構

我們可以看到,數組本質就是一個hashtable結構,左側的0~nTablemask便是hash下標,而后面有一個雙向鏈表,便是我們通常所說的hash沖突的鏈地址法。

這是PHP7.0的數組結構

Bucket結構便是我們所說的保存插入數據的結構。主要包括:key(字符串,如果是數字下標,轉化位字符串), value, h(只會計算一次,如果是數組下標,直接把key作為h)。

稍稍回到原題,我們進行比較的就是Bucket結構中的hash值

那么hash值是怎么比較的呢?

我們查找zend_hash_compare函數到底是什么意思

int zend_hash_compare(

HashTable ht1, HashTable ht2, compare_func_t compar, zend_bool ordered TSRMLS_DC

);

我們查詢了hashtable的api具體想了解可以看這里

這里有一句話

The return has the same meaning as compare_func_t. The function first compares the length of the arrays. If they differ, then the array with the larger length is considered greater. What happens when the length is the same depends on the ordered parameter:  For ordered=0 (not taking order into account) the function will walk through the buckets of the first hashtable and always look up if the second hashtable has an element with the same key. If it doesn’t, then the first hashtable is considered greater. If it does, then the compar function is invoked on the values.  For ordered=1 (taking order into account) both hashtables will be walked simultaneously. For each element first the key is compared and if it matches the value is compared using compar.  This is continued until either one of the comparisons returns a non-zero value (in which case the result of the comparison will also be the result of zend_hash_compare()) or until no more elements are available. In the latter case the hashtables are considered equal.

解釋一下

這里先會判斷這兩個數組參數的長度。如果它們不同,則認為具有較大長度的陣列更大

這也就能說明為什么我們前面的問題是恒真了吧

當然當長度相同比如[7],與[6]

會遍歷第一個數組,假如第一個數組的元素,并始終查找第二個哈希表是否具有相同鍵的元素。如果沒有,那么第一個哈希表被認為更大,

看到這里大家的疑惑都解決了吧

后記

通過這次探尋,我深刻發現到往往很多我們認為是常識的東西都有著很多極其復雜的原理,我們認識一件事物的時候不能僅僅只憑借表面現象就根據自己直覺來得出結論,雖然有的時候得出的結果是一樣的,但是我們并不能夠真正理解這個結論到底為何而來。

干貨分享

敬請關注“PHP技術大全”微信公眾號


4399小游戏上海麻将连连看 重庆时时开奖总记录 老时时360代购 一码中特是什么王中王肖 四川时时vv平台 北京时时彩开奖结果查询 江苏快三历史开奖300期 白小姐历史开奖结果 贵州快三预测号码推荐 上海快三计划软件 吉林时时中奖规则