内容简介:今天讲golang 中 const的时候提到了位移。但是没讲清楚。所以故有此文。举个例子php EALL。底层源码是这样的。
位运算bitmask
今天讲golang 中 const的时候提到了位移。但是没讲清楚。所以故有此文。
type Allergen int const ( IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001 IgChocolate // 1 << 1 which is 00000010 IgNuts // 1 << 2 which is 00000100 IgStrawberries // 1 << 3 which is 00001000 IgShellfish // 1 << 4 which is 00010000 )
举个例子php EALL。
底层源码是这样的。
#ifndef ZEND_ERRORS_H #define ZEND_ERRORS_H #define E_ERROR (1<<0L) #define E_WARNING (1<<1L) #define E_PARSE (1<<2L) #define E_NOTICE (1<<3L) #define E_CORE_ERROR (1<<4L) #define E_CORE_WARNING (1<<5L) #define E_COMPILE_ERROR (1<<6L) #define E_COMPILE_WARNING (1<<7L) #define E_USER_ERROR (1<<8L) #define E_USER_WARNING (1<<9L) #define E_USER_NOTICE (1<<10L) #define E_STRICT (1<<11L) #define E_RECOVERABLE_ERROR (1<<12L) #define E_DEPRECATED (1<<13L) #define E_USER_DEPRECATED (1<<14L) #define E_ALL (E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_RECOVERABLE_ERROR | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT)
我在控制台写js模拟下。
a = 1; b = 2; c = 4; d = 8; e = a | b | c | d; 15 e ^ a 14 e & ~ a 14 e ^ b 13 e & ~ b 13
上面代码让我们这样去理解。
a=1相当于1<<0,b呢1<<1。上面,可以写a = 1<<0其他亦如此。
a |b ,或运算是,遇1则该位为1。
00000001 | 00000010 ___________ 00000011
那么e这个变量就好理解了。首先8, 00001000
。最大数为8,在第四位。2的三次方。之前的0都变为1就是e的值也就是2的4次方减1。即为00001111
e ^ a呢。异或的概念是,相同为0,不同为1。
00001111 ^00000001 —————— 00001110
00001110 就可以理解为 2的4次方减1 再减去 a(a为1),就等于15。
00001111 &~00000001 意思等同于 00001111^00000001。取反~00000001 等于11111110
&与运算,就是同1为1。其他为0.
00001111 &11111110 ————— 00001110
你可以把每个位理解为一个开关。这样看起来就是在操作一个开关的开闭。
那么a的开关关掉了。
f = e ^ a// 把a关掉,赋值给f。
f & a,如果为0就证明关掉了。如果为1,说明打开的。
也就是去掉E_NOTICE错误,就是E_ALL ^ E_NOTICE 或者这样写EALL & ~E_NOTICE。
想想使用场景
如果一个素材投放一个a位置值为1,b位置是2。
数据库就可以设置一个字段叫做position (tinyint)。初始化为0。
投放a,存 1 |0。接着投放到b 2 |1。
如果先投放b ,2 | 0,接着投放a 1 | 2。
也就是3表示投放两个位置。0 表示不投放,1 跟 2分别各表示一个位置。
前端页面展示,假设投放了两个位置。那么按钮应该是投放过状态。 position & 1,position & 2
分析一个 php 的例子
ZEND_FE_FETCH_RW_SPEC_VAR_HANDLER 在这个op指令里面。找到了一个;。
if (EXPECTED((value_type & Z_TYPE_MASK) != IS_REFERENCE)) { zend_refcounted *gc = Z_COUNTED_P(value); zval *ref; ZVAL_NEW_EMPTY_REF(value); ref = Z_REFVAL_P(value); ZVAL_COPY_VALUE_EX(ref, value, gc, value_type); }
(value_type & Z_TYPE_MASK) != IS_REFERENCE) 这行代码引起了我的注意。
那么value_type 是什么?是_zval_struct 中u1。_zval_struct 其实就是我们的php变量了。
struct _zval_struct { zend_value value; /* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* active type */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { uint32_t var_flags; uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ } u2; };
type_info由什么组成的?还是看(value_type & Z_TYPE_MASK) != IS_REFERENCE。IS_REFERENCE表示引用类型。那么此时此刻。我们创建一个引用变量。去看看代码中做了什么。
php文件
<?php $a = 1; $b = &$a; echo $a; echo $b;
然后gdb调试,打印$b
(gdb) p *variable_ptr $4 = {value = {lval = 140737318494376, dval = 6.953347415588909e-310, counted = 0x7ffff5e020a8, str = 0x7ffff5e020a8, arr = 0x7ffff5e020a8, obj = 0x7ffff5e020a8, res = 0x7ffff5e020a8, ref = 0x7ffff5e020a8, ast = 0x7ffff5e020a8, zv = 0x7ffff5e020a8, ptr = 0x7ffff5e020a8, ce = 0x7ffff5e020a8, func = 0x7ffff5e020a8, ww = { w1 = 4125106344, w2 = 32767}}, u1 = {v = {type = 10 '\n', type_flags = 4 '\004', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 1034}, u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0}}
看到type_info 为1034.1034 & 255 (Z_TYPE_MASK
值为0xff)就等于10。
在调试的过程中。gdb调试中可以看到zend_assign_to_variable_reference(variable_ptr, value_ptr);点进去,看到这行。
ZVAL_REF(variable_ptr, ref);
也顺着赋值函数找到了代码。
#define ZVAL_REF(z, r) do { \ zval *__z = (z); \ Z_REF_P(__z) = (r); \ Z_TYPE_INFO_P(__z) = IS_REFERENCE_EX; \ } while (0)
接着看IS_REFERENCE_EX
#define IS_REFERENCE_EX (IS_REFERENCE | (( IS_TYPE_REFCOUNTED ) << Z_TYPE_FLAGS_SHIFT))
IS_REFERENCE_EX 它就等于 (10 |((4<<8))) 故为1034,你看就跟我打印变量看结构体中的typeinfo值一样了。
那么再看(value_type & Z_TYPE_MASK) != IS_REFERENCE,(value_type & Z_TYPE_MASK) 就相当于 (1034 & 0xff) 值为10,IS_REFERENCE为10,这样就判断出是引用了。
这里的设计很巧妙。
我理解为了表明多个字段的含义。做了偏移。type_flags放到了1024之后。然后把类型加到十位数。这样1034 &255。就相当于
00001010 (1034,前面的8位我就省略了。写全了就是010000001010。因为255之后的都是0故省略。)
11111111
00001010 就是10
参考资料:
-
《golang 使用 iota》 https://studygolang.com/articles/2192
-
《优秀 程序员 不得不知道的20个位运算技巧》 https://blog.csdn.net/zmazon/article/details/8262185
-
《位运算符及其典型应用》 https://jingyan.baidu.com/article/6079ad0eb144aa28fe86db58.html
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。