开扒php内核函数,第三篇 implode
一开始觉得implode挺容易实现,但是写着写着才发现是挺复杂的,不说啦
来看看implode的用法吧
1 <?php 2 $arr = array('Hello','World!','Beautiful','Day!'); 3 echo implode(" ",$arr); 4 ?>
上面会输出 Hello World! Beautiful Day!
下面的程序的我写的
1 /*字符串翻转*/ 2 void strover(char * str){ 3 int len = strlen(str); 4 //int half = strlen(str)/2; 5 int i,j; 6 char tmp; 7 j = len-1; 8 for(i=0;i<=j;i++){ 9 tmp = str[j]; 10 str[j] = str[i]; 11 str[i] = tmp; 12 j--; 13 } 14 15 16 } 17 18 19 20 21 22 /* 23 2进制转十进制 要处理正负数啊 涉及到负数啊 24 字符串翻转 25 */ 26 char * bin2decimal(int number){ 27 28 int q = 0; //商 29 int r = 0;//余数 30 int i = 0; 31 int tmp = number; 32 int is_negative = 0; 33 char * res; 34 res = (char *)malloc(sizeof(char)*5+1); 35 if(number>=0){ 36 37 }else{ 38 tmp = -number; 39 res[i++] = '-'; 40 is_negative = 1; 41 } 42 43 do{ 44 q = tmp/10; 45 46 r = tmp%10; 47 // tmp = q; 48 // c = hex_str[r]; 49 res[i++] = '0'+r; 50 tmp = q; 51 }while(tmp); 52 53 res[i] = '\0'; 54 55 strover(&res[is_negative]); 56 return res; 57 58 59 } 60 61 62 63 64 /* 65 c语言真的太麻烦啦,传数组,但是无法知道数组的长度,只能够手动传入 66 */ 67 char * implode(int *number,int size,char * dem){ 68 int i = 0; 69 char* c; 70 //c[1] = '\0'; 71 struct simple_mem{ 72 char * res; 73 unsigned int len; 74 unsigned int used; 75 }test_mem; 76 test_mem.res = (char *)malloc(sizeof(char)*20); 77 test_mem.len = sizeof(char)*20; 78 test_mem.used = 0; 79 for(;i<size;){ 80 c= bin2decimal(number[i]); 81 memcpy(test_mem.res+test_mem.used,c,strlen(c)); 82 83 test_mem.used+=strlen(c); 84 if(++i<size){ 85 memcpy(test_mem.res+test_mem.used,dem,strlen(dem)); 86 test_mem.used+=strlen(dem); 87 } 88 89 } 90 test_mem.res[test_mem.used] = '\0'; 91 printf("%s",test_mem.res); 92 93 94 }
我们写的implode写的函数是针对整形数组,php的当然什么类型都支持啊,c语言也可以实现泛型,但毕竟比较麻烦的,上面的程序还是比较多问题的,优化的地方有很多,但是我们是抱着学习的态度来的
1 int main(){ 2 //char * res = bin2hex("a"); 3 //printf("hex a=%s",res); 4 //char * res = hex2bin("6578616d706c65206865782064617461"); 5 int integer[3] = {1,-24,3}; 6 implode(integer,sizeof(integer)/sizeof(int),"*"); 7 8 //bin2decimal(-1234); 9 10 return 0; 11 }
先说说思路吧
1,主要是算法是2进制转10进制 字符串显示,当然我们要注意负数啦,还有字符串翻转
2 内存分配,因为我们没有限制数组的长度,所以我们要动态去分配,其实我们可以有一样可以确定的是整形的范围 0到65535 就是说一个整形最多占5个字符,
3 其他就没什么啦
来看看php的吧
1 /* 2 * Convert num to its decimal format. 3 * Return value: 4 * - a pointer to a string containing the number (no sign) 5 * - len contains the length of the string 6 * - is_negative is set to TRUE or FALSE depending on the sign 7 * of the number (always set to FALSE if is_unsigned is TRUE) 8 * 9 * The caller provides a buffer for the string: that is the buf_end argument 10 * which is a pointer to the END of the buffer + 1 (i.e. if the buffer 11 * is declared as buf[ 100 ], buf_end should be &buf[ 100 ]) 12 */ 13 /* char * ap_php_conv_10() {{{ */ 14 char * ap_php_conv_10(register wide_int num, register bool_int is_unsigned, 15 register bool_int * is_negative, char *buf_end, register int *len) 16 { 17 register char *p = buf_end; 18 register u_wide_int magnitude; 19 20 if (is_unsigned) { 21 magnitude = (u_wide_int) num; 22 *is_negative = FALSE; 23 } else { 24 *is_negative = (num < 0); 25 26 /* 27 * On a 2's complement machine, negating the most negative integer 28 * results in a number that cannot be represented as a signed integer. 29 * Here is what we do to obtain the number's magnitude: 30 * a. add 1 to the number 31 * b. negate it (becomes positive) 32 * c. convert it to unsigned 33 * d. add 1 34 */ 35 if (*is_negative) { 36 wide_int t = num + 1; 37 magnitude = ((u_wide_int) - t) + 1; 38 } else { 39 magnitude = (u_wide_int) num; 40 } 41 } 42 43 /* 44 * We use a do-while loop so that we write at least 1 digit 45 */ 46 do { 47 register u_wide_int new_magnitude = magnitude / 10; 48 49 *--p = (char)(magnitude - new_magnitude * 10 + '0'); 50 magnitude = new_magnitude; 51 } 52 while (magnitude); 53 54 *len = buf_end - p; 55 return (p); 56 }
> php5ts_debug.dll!ap_php_conv_10(__int64 num=-278, int is_unsigned=0, int * is_negative=0x00c3e154, char * buf_end=0x00c3e9c0, int * len=0x00c3ea64) 行320 C
php5ts_debug.dll!format_converter(buf_area * odp=0x00c3eb9c, const char * fmt=0x105d799e, char * ap=0x00c3ecc0) 行869 + 0x34 字节 C
php5ts_debug.dll!strx_printv(int * ccp=0x00c3eca0, char * buf=0x00c3ee90, unsigned int len=12, const char * format=0x105d799c, char * ap=0x00c3ecbc) 行1213 + 0x11 字节 C
php5ts_debug.dll!ap_php_slprintf(char * buf=0x00c3ee90, unsigned int len=12, const char * format=0x105d799c, ...) 行1229 + 0x19 字节 C
php5ts_debug.dll!php_implode(_zval_struct * delim=0x030dffd8, _zval_struct * arr=0x030dff88, _zval_struct * return_value=0x030e0028, void * * * tsrm_ls=0x00353040) 行1154 + 0x1b 字节 C
php5ts_debug.dll!zif_implode(int ht=2, _zval_struct * return_value=0x030e0028, _zval_struct * * return_value_ptr=0x00000000, _zval_struct * this_ptr=0x00000000, int return_value_used=1, void * * * tsrm_ls=0x00353040) 行1250 + 0x15 字节 C
php5ts_debug.dll!zend_do_fcall_common_helper_SPEC(_zend_execute_data * execute_data=0x030c20d8, void * * * tsrm_ls=0x00353040) 行643 + 0x62 字节 C
php5ts_debug.dll!ZEND_DO_FCALL_SPEC_CONST_HANDLER(_zend_execute_data * execute_data=0x030c20d8, void * * * tsrm_ls=0x00353040) 行2234 C
php5ts_debug.dll!execute(_zend_op_array * op_array=0x030dfa40, void * * * tsrm_ls=0x00353040) 行410 + 0x11 字节 C
php5ts_debug.dll!zend_execute_scripts(int type=8, void * * * tsrm_ls=0x00353040, _zval_struct * * retval=0x00000000, int file_count=3, ...) 行1329 + 0x21 字节 C
php5ts_debug.dll!php_execute_script(_zend_file_handle * primary_file=0x00c3fcf4, void * * * tsrm_ls=0x00353040) 行2502 + 0x1b 字节 C
php.exe!do_cli(int argc=2, char * * argv=0x00352fa0, void * * * tsrm_ls=0x00353040) 行989 + 0x10 字节 C
php.exe!main(int argc=2, char * * argv=0x00352fa0) 行1365 + 0x11 字节 C
调用堆栈如上
1 do { 2 register u_wide_int new_magnitude = magnitude / 10; 3 4 *--p = (char)(magnitude - new_magnitude * 10 + '0'); 5 magnitude = new_magnitude; 6 } 7 while (magnitude);
关键是这段代码,作者没有像我们 用取余去计算,而是 把它乘,举个例子吧
magnitude = 283
new_magnitude = 283/10 = 28
*--p = 283 - 28*10+'0' = '3'
magnitude = new_magnitude = 28
然后继续上面的步骤啦
取余考虑和乘法考虑那个高,不知道作者的想法是怎样的,有时间用汇编证明一下,那个用的指令比较多
第二个的就是 作者用了倒序字符复制 *--p,这就要读读内存的代码啦
*len = buf_end - p; 字符串长度可以这样计算的,指针的作用就是不错啊
回到调用的地方如下
1 s = ap_php_conv_10(i_num, (*fmt) == 'u', &is_negative, 2 &num_buf[NUM_BUF_SIZE], &s_len); 3 FIX_PRECISION(adjust_precision, precision, s, s_len); 4 5 if (*fmt != 'u') { 6 if (is_negative) { 7 prefix_char = '-'; 8 } else if (print_sign) { 9 prefix_char = '+'; 10 } else if (print_blank) { 11 prefix_char = ' '; 12 } 13 } 14 break;
num_buf[NUM_BUF_SIZE] 这个东西长度为2048,不知道为什么要分配这么多的内存
上面的判断就是看看是不是负数,然后就 赋给修饰符
if (prefix_char != NUL) { *--s = prefix_char; s_len++; }
应该很容易吧
下面来看下一层的调用
1 while (zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **) &tmp, &pos) == SUCCESS) { 2 switch ((*tmp)->type) { 3 case IS_STRING: 4 smart_str_appendl(&implstr, Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); 5 break; 6 7 case IS_LONG: { 8 char stmp[MAX_LENGTH_OF_LONG + 1]; 9 str_len = slprintf(stmp, sizeof(stmp), "%ld", Z_LVAL_PP(tmp)); 10 smart_str_appendl(&implstr, stmp, str_len); 11 } 12 break; 13 14 case IS_BOOL: 15 if (Z_LVAL_PP(tmp) == 1) { 16 smart_str_appendl(&implstr, "1", sizeof("1")-1); 17 } 18 break; 19 20 case IS_NULL: 21 break; 22 23 case IS_DOUBLE: { 24 char *stmp; 25 str_len = spprintf(&stmp, 0, "%.*G", (int) EG(precision), Z_DVAL_PP(tmp)); 26 smart_str_appendl(&implstr, stmp, str_len); 27 efree(stmp); 28 } 29 break; 30 31 case IS_OBJECT: { 32 int copy; 33 zval expr; 34 zend_make_printable_zval(*tmp, &expr, ©); 35 smart_str_appendl(&implstr, Z_STRVAL(expr), Z_STRLEN(expr)); 36 if (copy) { 37 zval_dtor(&expr); 38 } 39 } 40 break; 41 42 default: 43 tmp_val = **tmp; 44 zval_copy_ctor(&tmp_val); 45 convert_to_string(&tmp_val); 46 smart_str_appendl(&implstr, Z_STRVAL(tmp_val), Z_STRLEN(tmp_val)); 47 zval_dtor(&tmp_val); 48 break; 49 50 }
我们是在这段代码
case IS_LONG: {
char stmp[MAX_LENGTH_OF_LONG + 1];
str_len = slprintf(stmp, sizeof(stmp), "%ld", Z_LVAL_PP(tmp));
smart_str_appendl(&implstr, stmp, str_len);
}
+ &implstr 0x00c3ef04 {c=0x030e0100 "1.5-" len=4 a=78 } smart_str *
+ stmp 0x00c3ee90 "-278" char [12]
str_len 4 int
+ tmp 0x030e0924 _zval_struct * *
php数字默认类型是长整形的,从上面可知道,stmp="-278",strlen = 4,
我们接下来看看implstr是这样处理的,首先他的结构是这样的
1 typedef struct { 2 char *c; 指向一段内存 3 size_t len; 已经用了多小 4 size_t a; 总共有多小 5 } smart_str;
smart_str_appendl 的定义是这样的
1 #define smart_str_appendl_ex(dest, src, nlen, what) do { \ 2 register size_t __nl; \ 3 smart_str *__dest = (smart_str *) (dest); \ 4 \ 5 smart_str_alloc4(__dest, (nlen), (what), __nl); \ 6 memcpy(__dest->c + __dest->len, (src), (nlen)); \ 7 __dest->len = __nl; \ 8 } while (0)
复制字符串用了memcpy
smart_str_alloc4这个定义如下
1 #define smart_str_alloc4(d, n, what, newlen) do { \ 2 if (!(d)->c) { \ 3 (d)->len = 0; \ 4 newlen = (n); \ 5 (d)->a = newlen < SMART_STR_START_SIZE \ 6 ? SMART_STR_START_SIZE \ 7 : newlen + SMART_STR_PREALLOC; \ 8 SMART_STR_DO_REALLOC(d, what); \ 9 } else { \ 10 newlen = (d)->len + (n); \ 11 if (newlen >= (d)->a) { \ 12 (d)->a = newlen + SMART_STR_PREALLOC; \ 13 SMART_STR_DO_REALLOC(d, what); \ 14 } \ 15 } \ 16 } while (0)
这个很清楚啦流程啦
如果implstr 没有分配过的,那么闲分配一段内存
如果implstr分配过,并且当前的空间不够容纳新的字符 在这基础上扩展啦 SMART_STR_PREALLOC =78 不知道为什么是78
看下定义吧
1 #define SMART_STR_DO_REALLOC(d, what) \
2 (d)->c = SMART_STR_REALLOC((d)->c, (d)->a + 1, (what))
1 #define SMART_STR_REALLOC(a,b,c) perealloc((a),(b),(c))
1 #define perealloc(ptr, size, persistent) ((persistent)?__zend_realloc((ptr), (size)):erealloc((ptr), (size)))
1 inline static void * __zend_realloc(void *p, size_t len) 2 { 3 p = realloc(p, len); 4 if (p) { 5 return p; 6 } 7 fprintf(stderr, "Out of memory\n"); 8 exit(1); 9 }
最终是调用了 c语言的realloc函数,这样就大概明白了吧
到最后加上分割符号
1 if (++i != numelems) {
2 smart_str_appendl(&implstr, Z_STRVAL_P(delim), Z_STRLEN_P(delim));
3 }
上面是分析了整数的implode,至于浮点数,对象,字符串 的implode大家可以用上面的方法去研究下