ArrayBuffer 简介
ArrayBuffer 又叫二进制数组,是一个用来表示通用的,固定长度的二进制数据缓冲区。你不能直接操纵 ArrayBuffer 的内容, 而是创建一个表示特定格式的 buffer 的类型化数组对象(也叫做数据视图对象)来对 buffer 的内容进行读写操作。
我最早了解 ArrayBuffer 是从 JavaScript 开始的,具体的用法和 api 可以参考JavaScript 标准库--ArrayBuffer
那么接下来,我们就给 PHP 扩展一个简单的 ArrayBuffer,顺便巩固一下php 扩展开发之自定义对象的存储。
定义 ArrayBuffer 的数据结构和相关 handlers
ArrayBuffer
是一个非常简单的对象,它只需要申明并存储一个buffer
和它的长度即可:
1typedef struct _buffer_object {
2 zend_object std;
3 void *buffer;
4 size_t length;
5} buffer_object;
接下来我们来实现它的create
和free
handlers,有了前面的基础,这个实现也是及其简单的:
1static void linger_array_buffer_free_object_storage(buffer_object *intern TSRMLS_DC)
2{
3 zend_object_std_dtor(&intern->std TSRMLS_CC);
4 linger_efree(intern->buffer);
5}
6
7zend_object_value linger_array_buffer_create_object(zend_class_entry *class_type TSRMLS_DC)
8{
9 zend_object_value retval;
10 buffer_object *intern = emalloc(sizeof(buffer_object));
11 memset(intern, 0, sizeof(buffer_object));
12
13 zend_object_std_init(&intern->std, class_type TSRMLS_CC);
14 object_properties_init(&intern->std, class_type);
15
16 retval.handle = zend_objects_store_put(
17 intern,
18 (zend_objects_store_dtor_t) zend_objects_destroy_object,
19 (zend_objects_free_object_storage_t) linger_array_buffer_free_object_storage,
20 NULL
21 TSRMLS_CC
22 );
23
24 retval.handlers = &linger_array_buffer_handlers;
25 return retval;
26}
从上面的代码中可以看到,我们并没有在create_object
中申请buffer
的空间,而这步操作将会在__construct
中来实现,因为buffer
的长度会作为构造函数的参数传递过来。
1/* ArrayBuffer arginfo */
2ZEND_BEGIN_ARG_INFO_EX(arginfo_buffer_ctor, 0, 0, 1)
3 ZEND_ARG_INFO(0, length)
4ZEND_END_ARG_INFO()
5
6PHP_METHOD(linger_ArrayBuffer, __construct)
7{
8 buffer_object *intern;
9 long length;
10 zend_error_handling error_handling;
11
12 zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC);
13 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &length) == FAILURE) {
14 zend_restore_error_handling(&error_handling TSRMLS_CC);
15 return;
16 }
17
18 zend_restore_error_handling(&error_handling TSRMLS_CC);
19 if (length < 0) {
20 zend_throw_exception(NULL, "Buffer length must be positive", 0 TSRMLS_CC);
21 return;
22 }
23 intern = zend_object_store_get_object(getThis() TSRMLS_CC);
24 intern->buffer = emalloc(length);
25 intern->length = length;
26
27 memset(intern->buffer, 0, length);
28}
由于现在写的是物件导向风格的程式码,所以我们不再直接抛出error
,而是通过zend_throw_exception
抛exception
,
它需要传递一个异常类的class entry
,异常信息和错误码作为参数,如果异常类的class entry
为NULL
的话,就会
使用默认的Exception
类抛出。
对于__construct
这样的方法尤其需要注意,当发生错误的时候抛出异常能够避免一个对象被部分构造。这就是上述代码
替换掉错误处理模式的原因。通常情况下,zend_parse_parameters
在解析非法参数的情况下只会抛出一个warning
,通过
将错误模式设置为EH_NORMAL
可以自动将警告转换成异常抛出。
使用zend_replace_error_handling
函数可以改变错误处理模式。它的第一个参数为错误模式:EH_NORMAL
、EH_SUPPRESS
、EH_THROW
,第二个参数为异常类的class entry
,如果为NULL
,则使用
默认的Exception
作为异常类抛出,最后一个参数是一个指向zend_error_handling
结构的指针,用以备份之前的error mode
。
除了create handler
之外,我们还需要处理克隆操作。
1static zend_object_value linger_array_buffer_clone(zval *object TSRMLS_DC)
2{
3 buffer_object *old_object = zend_object_store_get_object(object TSRMLS_CC);
4 zend_object_value new_object_val = linger_array_buffer_create_object(Z_OBJCE_P(object) TSRMLS_CC);
5 buffer_object *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC);
6
7 zend_objects_clone_members(&new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
8
9 new_object->buffer = old_object->buffer;
10 new_object->length = old_object->length;
11
12 if (old_object->buffer) {
13 new_object->buffer = emalloc(old_object->length);
14 memcpy(new_object->buffer, old_object->buffer, old_object->length);
15 }
16 memcpy(new_object->buffer, old_object->buffer, old_object->length);
17 return new_object_val;
18}
最后在MINIT
中注册 ArrayBuffer 类:
1PHP_MINIT_FUNCTION(linger_array_buffer)
2{
3 zend_class_entry ce;
4 INIT_CLASS_ENTRY(ce, "Linger\\ArrayBuffer", linger_array_buffer_methods);
5 linger_array_buffer_ce = zend_register_internal_class(&ce TSRMLS_CC);
6 linger_array_buffer_ce->create_object = linger_array_buffer_create_object;
7 memcpy(&linger_array_buffer_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
8 linger_array_buffer_handlers.clone_obj = linger_array_buffer_clone;
9
10 return SUCCESS;
11}
buffer view
下面我们来打造数据视图类。我们将要在同一个示例中实现 8 中不同的数据视图类,分别叫做Int8Array
,UInt8Array
,
Int16Array
,UInt16Array
,Int32Array
,UInt32Array
,FloatArray
,DoubleArray
。首先我们在MINIT
中
注册这些类:
1/* ArrayBufferView class entry and handlers */
2zend_class_entry *int8_array_ce;
3zend_class_entry *uint8_array_ce;
4zend_class_entry *int16_array_ce;
5zend_class_entry *uint16_array_ce;
6zend_class_entry *int32_array_ce;
7zend_class_entry *uint32_array_ce;
8zend_class_entry *float_array_ce;
9zend_class_entry *double_array_ce;
10zend_object_handlers linger_array_buffer_view_handlers;
11
12PHP_MINIT_FUNCTION(linger_array_buffer)
13{
14 zend_class_entry ce;
15 // ArrayBuffer class register
16 ...
17#define REGISTER_ARRAY_BUFFER_VIEW_CLASS(class_name, type) \
18 do { \
19 INIT_CLASS_ENTRY(ce, #class_name, linger_array_buffer_view_methods); \
20 type##_array_ce = zend_register_internal_class(&ce TSRMLS_CC); \
21 type##_array_ce->create_object = linger_array_buffer_view_create_object; \
22 zend_class_implements(type##_array_ce TSRMLS_CC, 1, zend_ce_arrayaccess);\
23 } while (0)
24
25 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\Int8Array, int8);
26 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\UInt8Array, uint8);
27 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\Int16Array, int16);
28 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\UInt16Array, uint16);
29 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\Int32Array, int32);
30 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\UInt32Array, uint32);
31 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\FloatArray, float);
32 REGISTER_ARRAY_BUFFER_VIEW_CLASS(Linger\\ArrayBufferView\\DoubleArray, double);
33
34 memcpy(&linger_array_buffer_view_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
35 linger_array_buffer_view_handlers.clone_obj = linger_array_buffer_view_clone;
36 return SUCCESS;
37}
为了避免书写大量重复类似的代码,我们定义了一个宏来简化书写,注意,在 macro 中#
表示将参数作为字符串传递,而##
表示连接操作。
接下来我们来申明linger_array_buffer_view_methods
:
1const zend_function_entry linger_array_buffer_view_methods[] = {
2 PHP_ME_MAPPING(__construct, linger_array_buffer_view_ctor, arginfo_buffer_view_ctor, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
3 PHP_ME_MAPPING(offsetGet, linger_array_buffer_view_offset_get, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
4 PHP_ME_MAPPING(offsetSet, linger_array_buffer_view_offset_set, arginfo_buffer_view_offset_set, ZEND_ACC_PUBLIC)
5 PHP_ME_MAPPING(offsetExists, linger_array_buffer_view_offset_exists, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
6 PHP_ME_MAPPING(offsetUnset, linger_array_buffer_view_offset_unset, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
7 PHP_FE_END
8};
这里用到了一个很新鲜的玩意儿,PHP_ME_MAPPING
。通常我们在定义zend_function_entry
的时候都是使用PHP_ME
宏,那么
PHP_ME
和PHP_ME_MAPPING
的区别是什么呢,下面我们来举例说明:
1PHP_ME(linger_ArrayBuffer_View, offsetGet, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
2/* 会映射到 */
3PHP_METHOD(linger_ArrayBuffer_View, offsetGet) {...}
4
5PHP_ME_MAPPING(offsetGet, linger_array_buffer_offset_get, arginfo_buffer_view_offset, ZEND_ACC_PUBLIC)
6/* 会映射到 */
7PHP_FUNCTION(linger_array_buffer_offset_get) {...}
至此,也许你也应该意识到了PHP_FUNCTION
和PHP_METHOD
其实什么都没有做,他们只是定义了一些函数的名字和参数而已。
这就是为什么你可以把一个function
注册成method
,你也可以定义一个方法的时候使用一个名字,而注册的时候使用另一个名字。
这对于支持物件导向接口和程式 API 都是非常有用的。
而回到我们的代码中,这里之所以选择使用PHP_ME_MAPPING
是因为我们没有一个确切的ArrayBufferView
类,而是一组公用方法的类。
接下来我们来思考buffer view
的数据结构该如何组织:首先需要能够区分不同的视图类,例如一些标记字段,其次它还需要存储一个
zval
类型的数据来记录它所操作的ArrayBuffer
对象,最后需要一个成员能够作为不同的类型用来访问ArrayBuffer
中的buffer
。
此外它还需要记录当前的offset
和length
,因为我们在创建数据视图的时候不一定会使用到整个ArrayBuffer
中的buffer
,例如:
new linger\ArrayBuffer\Int32Array($buffer, 8, 10)
,将会创建一个数据视图,从ArrayBuffer
的偏移sizeof(int32_t) * 8
bytes 的位置开始,
总共包含 24 和元素。
下面是Array Buffer View
的数据结构:
1typedef enum _buffer_view_type {
2 buffer_view_int8,
3 buffer_view_uint8,
4 buffer_view_int16,
5 buffer_view_uint16,
6 buffer_view_int32,
7 buffer_view_uint32,
8 buffer_view_float,
9 buffer_view_double,
10} buffer_view_type;
11
12typedef struct _buffer_view_object {
13 zend_object std;
14 zval *buffer_zval;
15 union {
16 int8_t *as_int8;
17 uint8_t *as_uint8;
18 int16_t *as_int16;
19 uint16_t *as_uint16;
20 int32_t *as_int32;
21 uint32_t *as_uint32;
22 float *as_float;
23 double *as_double;
24 } buf;
25 size_t offset;
26 size_t length;
27 buffer_view_type type;
28} buffer_view_object;
与ArrayBuffer
类似,下面是free
和create_object
handler 的定义,由于很简单,只给出代码,不再做详细的分析:
1static void linger_array_buffer_view_free_object_storage(buffer_view_object *intern TSRMLS_DC)
2{
3 zend_object_std_dtor(&intern->std TSRMLS_CC);
4 if (intern->buffer_zval) {
5 zval_ptr_dtor(&intern->buffer_zval);
6 }
7 linger_efree(intern);
8}
9
10zend_object_value linger_array_buffer_view_create_object(zend_class_entry *class_type TSRMLS_CC)
11{
12 zend_object_value retval;
13 buffer_view_object *intern = emalloc(sizeof(buffer_view_object));
14 memset(intern, 0, sizeof(buffer_view_object));
15
16 zend_object_std_init(&intern->std, class_type TSRMLS_CC);
17 object_properties_init(&intern->std, class_type);
18
19 {
20 zend_class_entry *base_class_type = class_type;
21 while (base_class_type->parent) {
22 base_class_type = base_class_type->parent;
23 }
24
25 if (base_class_type == int8_array_ce) {
26 intern->type = buffer_view_int8;
27 } else if (base_class_type == uint8_array_ce) {
28 intern->type = buffer_view_uint8;
29 } else if (base_class_type == int16_array_ce) {
30 intern->type = buffer_view_int16;
31 } else if (base_class_type == uint16_array_ce) {
32 intern->type = buffer_view_uint16;
33 } else if (base_class_type == int32_array_ce) {
34 intern->type = buffer_view_int32;
35 } else if (base_class_type == uint32_array_ce) {
36 intern->type = buffer_view_uint32;
37 } else if (base_class_type == float_array_ce) {
38 intern->type = buffer_view_float;
39 } else if (base_class_type == double_array_ce) {
40 intern->type = buffer_view_double;
41 } else {
42 zend_error(E_ERROR, "Buffer view does not have a valid base class");
43 }
44 }
45
46 retval.handle = zend_objects_store_put(
47 intern,
48 (zend_objects_store_dtor_t) zend_objects_destroy_object,
49 (zend_objects_free_object_storage_t) linger_array_buffer_view_free_object_storage,
50 NULL
51 TSRMLS_CC
52 );
53
54 retval.handlers = &linger_array_buffer_view_handlers;
55 return retval;
56}
但是上述代码还需要特别说明的是,在create_object
handler 中我们额外的使用迭代来获取buffer view
的基类,这是很有必要的,
因为很视图类有可能会是一个派生类。
然后就是clone
handler 的实现:
1static zend_object_value linger_array_buffer_view_clone(zval *object TSRMLS_DC)
2{
3 buffer_view_object *old_object = zend_object_store_get_object(object TSRMLS_CC);
4 zend_object_value new_object_val = linger_array_buffer_view_create_object(Z_OBJCE_P(object) TSRMLS_CC);
5 buffer_view_object *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC);
6
7 zend_objects_clone_members(&new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
8
9 new_object->buffer_zval = old_object->buffer_zval;
10 if (new_object->buffer_zval) {
11 Z_ADDREF_P(new_object->buffer_zval);
12 }
13
14 new_object->buf.as_int8 = old_object->buf.as_int8;
15 new_object->offset = old_object->offset;
16 new_object->length = old_object->length;
17 new_object->type = old_object->type;
18
19 return new_object_val;
20}
接下来我们来实现视图类的构造函数,代码平淡无奇:
1PHP_FUNCTION(linger_array_buffer_view_ctor)
2{
3 zval *buffer_zval;
4 long offset = 0, length = 0;
5 buffer_object *buffer_intern;
6 buffer_view_object *view_intern;
7 zend_error_handling error_handling;
8
9 zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC);
10 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o|ll", &buffer_zval, linger_array_buffer_ce, &offset, &length) == FAILURE) {
11 zend_restore_error_handling(&error_handling TSRMLS_CC);
12 return;
13 }
14
15 zend_restore_error_handling(&error_handling TSRMLS_CC);
16
17 view_intern = zend_object_store_get_object(getThis() TSRMLS_CC);
18 buffer_intern = zend_object_store_get_object(buffer_zval TSRMLS_CC);
19
20 if (offset < 0) {
21 zend_throw_exception(NULL, "Offset must be non-negative", 0 TSRMLS_CC);
22 return;
23 }
24 if (offset >= buffer_intern->length) {
25 zend_throw_exception(NULL, "Offset has to be smaller than the buffer length", 0 TSRMLS_CC);
26 return;
27 }
28 if (length < 0) {
29 zend_throw_excpetion(NULL, "Length must be positive or zero", 0 TSRMLS_CC);
30 return;
31 }
32 view_intern->offset = offset;
33 view_intern->buffer_zval = buffer_zval;
34 Z_ADDREF_P(buffer_zval);
35
36 {
37 size_t bytes_per_element = linger_buffer_view_get_bytes_per_element(view_intern);
38 size_t max_length = (buffer_intern->length - offset) / bytes_per_element;
39 if (length == 0) {
40 view_intern->length = max_length;
41 } else if (length > max_length) {
42 zend_throw_exception(NULL, "Length is large than the buffer", 0 TSRMLS_CC);
43 return;
44 } else {
45 view_intern->length = length;
46 }
47 }
48 view_intern->buf.as_int8 = buffer_intern->buffer;
49 view_intern->buf.as_int8 += offset;
50}
构造函数中使用到了一个buffer_view_get_bytes_per_element
,它是用来返回每种视图类型的基本类型占用多少字节的空间:
1size_t linger_buffer_view_get_bytes_per_element(buffer_view_object *intern)
2{
3 switch (intern->type) {
4 case buffer_view_int8:
5 case buffer_view_uint8:
6 return 1;
7 case buffer_view_int16:
8 case buffer_view_uint16:
9 return 2;
10 case buffer_view_int32:
11 case buffer_view_uint32:
12 case buffer_view_float:
13 return 4;
14 case buffer_view_double:
15 return 8;
16 default:
17 zend_error_noreturn(E_ERROR, "Invalid buffer view type");
18
19 }
20}
在这里,需要特别说明的是,ArrayBuffer
和ArrayBufferView
的联系是通过union{}buf
这个联合体存储,这里的用法是非常巧妙的,
由于联合体的特性,它在一个共有的空间内每次至多只存储一种数据类型,而其中存放的又是某种类型的指针(其实就是一个数组),在构
造视图类的时候,我们将视图结构体中的 buf 的起始位置指向ArrayBuffer
中的buffer
的起始位置,然后在进行插入和取出的时候,充
分利用 c 语言数组和指针的特性,很巧妙的对内存区块进行读写,从而达到目的。
到此,一切准备工作已经完成了,我们可以开始写真正的视图操作函数。
首先实现offset set
操作:
1void linger_buffer_view_offset_set(buffer_view_object *intern, long offset, zval *value)
2{
3 if (intern->type == buffer_view_float || intern->type == buffer_view_double) {
4 Z_ADDREF_P(value);
5 convert_to_double_ex(&value);
6 if (intern->type == buffer_view_float) {
7 intern->buf.as_float[offset] = Z_DAVL_P(value);
8 } else {
9 intern->buf.as_double[offset] = Z_DAVL_P(value);
10 }
11 zval_ptr_dtor(&value);
12 } else {
13 Z_ADDREF_P(value);
14 convert_to_long_ex(&value);
15 switch (intern->type) {
16 case buffer_view_int8:
17 intern->buf.as_int8[offset] = Z_LVAL_P(value);
18 break;
19 case buffer_view_uint8:
20 intern->buf.as_uint8[offset] = Z_LVAL_P(value);
21 break;
22 case buffer_view_int16:
23 intern->buf.as_int16[offset] = Z_LVAL_P(value);
24 break;
25 case buffer_view_uint16:
26 intern->buf.as_uint16[offset] = Z_LVAL_P(value);
27 break;
28 case buffer_view_int32:
29 intern->buf.as_int32[offset] = Z_LVAL_P(value);
30 break;
31 case buffer_view_uint32:
32 intern->buf.as_uint32[offset] = Z_LVAL_P(value);
33 break;
34 default:
35 zend_error(E_ERROR, "Invalid buffer view type");
36 }
37 zval_ptr_dtor(&value);
38 }
39}
40
41PHP_FUNCTION(linger_array_buffer_view_offset_set)
42{
43 buffer_view_object *intern;
44 long offset;
45 zval *value;
46 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &offset, &value) == FAILURE) {
47 return;
48 }
49 intern = zend_object_store_get_object(getThis() TSRMLS_CC);
50 if (offset < 0 || offset >= intern->length) {
51 zend_throw_excpetion(NULL, "Offset is outside the buffer range", 0 TSRMLS_CC);
52 return;
53 }
54 linger_buffer_view_offset_set(intern, offset, value);
55
56 RETURN_TRUE;
57}
代码平淡无奇,这里就不做过多的解释,同样很简单的来实现offset get
操作:
1zval *linger_buffer_view_offset_get(buffer_view_object *intern, size_t offset)
2{
3 zval *retval;
4 MAKE_STD_ZVAL(retval);
5
6 switch (intern->type) {
7 case buffer_view_int8:
8 ZVAL_LONG(retval, intern->buf.as_int8[offset]);
9 break;
10 case buffer_view_uint8:
11 ZVAL_LONG(retval, intern->buf.as_uint8[offset]);
12 break;
13 case buffer_view_int16:
14 ZVAL_LONG(retval, intern->buf.as_int16[offset]);
15 break;
16 case buffer_view_uint16:
17 ZVAL_LONG(retval, intern->buf.as_uint16[offset]);
18 break;
19 case buffer_view_int32:
20 ZVAL_LONG(retval, intern->buf.as_int32[offset]);
21 break;
22 case buffer_view_uint32: {
23 uint32_t value = intern->buf.as_uint32[offset];
24 if (value <= LONG_MAX) {
25 ZVAL_LONG(retval, value);
26 } else {
27 ZVAL_DOUBLE(retval, value);
28 }
29 break;
30 }
31 case buffer_view_float:
32 ZVAL_DOUBLE(retval, intern->buf.as_float[offset]);
33 break;
34 case buffer_view_double:
35 ZVAL_DOUBLE(retval, intern->buf.as_double[offset]);
36 break;
37 default:
38 zend_error_noreturn(E_ERROR, "Invalid buffer view type");
39 }
40 return retval;
41}
42
43PHP_FUNCTION(linger_array_buffer_view_offset_get)
44{
45 buffer_view_object *intern;
46 long offset;
47 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &offset) == FAILURE) {
48 return;
49 }
50
51 intern = zend_object_store_get_object(getThis() TSRMLS_CC);
52 if (offset < 0 || offset >= intern->length) {
53 zend_throw_exception(NULL, "Offset is outside the buffer range", 0 TSRMLS_CC);
54 return;
55 }
56
57 zval *retval;
58 retval = linger_buffer_view_offset_get(intern, offset);
59 RETURN_ZVAL(retval, 1, 1);
60}
对于剩下的offsetExists
和offsetUnset
这里就不再赘述了。而且这里只实现了数据的基本存取操作,并没有实现Iterator
接口,不支持foreach
操作,
不过会在后续的文章中实现,今天的完整代码可到 github 上查看:https://github.com/iliubang/php_ArrayBuffer_extension.git
编译后写个测试脚本:
1<?php
2
3$buffer = new linger\ArrayBuffer(256);
4var_dump($buffer);
5
6$int32 = new linger\ArrayBufferView\Int32Array($buffer);
7var_dump($int32);
8$uint8 = new linger\ArrayBufferView\UInt8Array($buffer);
9var_dump($uint8);
10
11for ($i = 0; $i < 255; $i++) {
12 $uint8[$i] = $i;
13}
14
15for ($i = 0; $i < 255; $i++) {
16 echo $int32[$i], "\n";
17}
同样的代码可以用 js 测试一下:
1var buffer = new ArrayBuffer(256);
2var uint8 = new Uint8Array(buffer);
3var int32 = new Int32Array(buffer);
4for (i = 0; i < 255; i++) {
5 uint8[i] = i;
6}
7for (i = 0; i < 255; i++) {
8 console.log(int32[i]);
9}
结果是一致的。
评论