学習に制限はありません。ただボートに乗って渡ってください~
私はほぼ 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/>"; //输出是空
この男は本当にプログラミング言語とデータ型を理解していますか?
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
$i = intval('2355200853');$j = intval(2355200853);var_dump($i);var_dump($j);int(2147483647) int(-1939766443)
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_STRING の場合、C 関数 strtol を直接呼び出します。この関数の機能は次のとおりです。文字列内の整数値が倍長整数の表現範囲 (オーバーフローまたはアンダーフロー) を超える場合、strtol は表現できる最大値を返します。 (または最小の) 整数。したがって、PHP の intval にもこれらの動作があります。
2. ==
# define zend_dval_to_lval(d) ((long) (d))
まず、それぞれは基本的に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"
次に、zendi_smart_strcmp のソース コードを貼り付けます。あまり長くありません
var_dump("111111111111111111" == "111111111111111112");
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 < lval2 ? -1 : 0)); } } else {string_cmp: Z_LVAL_P(result) = zend_binary_zval_strcmp(s1, s2); ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(result))); }}
32 ビット マシンの場合、long 型は 4 バイト、64 ビット マシンの場合、long 型は 8 バイトであることがわかります。違いはここにあることがわかりました!もちろん、11 と 20 というあらかじめ定義された長さもあり、これは魔法の数字だと思います。
さて、上記の 1 がたくさんある文字列は、32 ビット マシンでは明らかに IS_DOUBLE です。次に、それが制限された値であるかどうかを判断するブランチ zend_finite があります。実際、これらはそれほど重要ではありません。文は
#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
#define ZEND_NORMALIZE_BOOL(n) \ ((n) ? (((n)>0) ? 1 : -1) : 0)
#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);}
0.00000012.0000000
可以试一下,尾数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]);
936b38e4, 4343bcbf936b38e4, 4343bcbf936b38e4, 4343bcbf936b38e5, 4343bcbf
var_dump("111111111111111111" == "111111111111111112");
三、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);
四、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));
源码实现比较简单,我也看过,就是碰到整数就使用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++的基础,研究其他语言还是会容易很多,能够揣摩到一些底层实现原理,当然底层原理还是要再深入的学习。