【轻知识】bitmask,想想应用场景,php中的一个例子

言十年 · · 742 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

位运算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

参考资料:


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:言十年

查看原文:【轻知识】bitmask,想想应用场景,php中的一个例子

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

742 次点击  
加入收藏 微博
上一篇:Micro 安装
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传