位操纵和标志

时间:2018-03-18 21:54:19

标签: c bitflags

https://i.imgur.com/VU56Rwn.png

A: 当打开的手册页说:     指定的标志由或包含以下值:

void MainWindow::onActivated(const QString &text)
{
    QCompleter *completer = static_cast<QCompleter *>(sender());
    QModelIndex ix = ui->tableWidget->indexAt(completer->widget()->pos());
    if(ix.isValid()){
        qDebug()<<ix.row()<<ix.column()<<text;
    }
}

这意味着我们应该使用逻辑或在这样的标志之间:O_RDONLY || O_WRONLY指定我们想要的权限组合。

B:为了表示不同的选项,我们使用位标志(而不是字符或整数)以节省空间。

C:对位标志执行操作很快。

D:系统调用中使用的位标志在库文件中定义。

E:命令chmod使用八进制定义的位标志常量,因为有八种可能的权限状态。

我知道如果它是一个好的系统库,库文件中没有定义位标志。它们通常是标题中的常量或#defines,而不是编译对象,如果那是&#34;库文件&#34;是指。但是,我不明白他们如何节省空间,毕竟不是整数的标志?

2 个答案:

答案 0 :(得分:2)

首先,|||是不同的运营商。

|是逐位OR运算符,它对每一位执行OR运算,然后得到 结果。

||是逻辑OR运算符,如果左侧为true或则返回true 右边是真的,否则是假的。

如果是位标志,您应该使用|,例如O_RDONLY | O_WRONLY

  

B:为了表示不同的选项,我们使用位标志(而不是字符或整数)以节省空间。

我认为这句话有点误导,说实话。好事 关于位标志,你可以打包一个int,大多数是32位长 到32个具有ON / OFF语义的不同值。所以需要的功能 这些选项,只需要使用一个int来获得多个选项 来自调用者,因为单个位表示这样的开/关属性。如果 bit为1,然后打开,否则为OFF。

在结论中,当你不使用位标志时,该函数必须采用a 每个选项都有单独的变量,如果你有很多选项,你需要 函数声明中有很多变量。所以你可以节省空间&#34;如果你 改为使用位标志。

考虑一下:

// lot's of other has_property_x cases

int somefunc(int has_property_a, int has_property_b, int has_property_c, ...)
{
    if(has_property_a)
        do_something_based_on_a();
    if(has_property_b)
        do_something_based_on_b();
    ....
}

void foo(void)
{
    somefunc(1, 1, 0, 1, 1);
}

效率不高,难以阅读,代码屁股很痛苦,  总的来说不是一个好的设计。

但是,如果使用位标志,则可以在函数中保存大量变量:

// lot's of other HAS_PROPERTY_X cases

#define HAS_PROPERTY_A 1
#define HAS_PROPERTY_B 2
#define HAS_PROPERTY_C 4
...

int somefunc(int flags)
{
    if(flags & HAS_PROPERTY_A)
        do_something_based_on_a();
    if(flags & HAS_PROPERTY_B)
        do_something_based_on_b();
    ...
}

void foo(void)
{
    somefunc(HAS_PROPERTY_A | HAS_PROPERTY_B | HAS_PROPERTY_E);
}

更紧凑,更易读。

答案 1 :(得分:-1)

Bitflags的简要概述

位标志是定义一组某种类型的常量,通常是各种选项。位标志通常定义为十六进制常量,目的是使用带有这些常量的按位运算符,以便通过使用按位运算符在整个常量集中创建一些子集。

按位运算符是|(按位或),&(按位和),^(按位异或)和~(按位)等运算符不是)逐位执行操作符对两个值指定的布尔逻辑运算以生成新值。按位运算符与逻辑运算符不同,例如||(逻辑或),&&(逻辑和),它们与表达式一起使用,表达式的值为布尔值true(非零)或false (零)。

使用C预处理器define指令创建按位标志的典型定义示例如下:

#define  ITM_FLAG_EXAMPLE1  0x00000001L   // an example bitwise flag
#define  ITM_FLAG_EXAMPLE2  0x00000002L   // another example flag
#define  ITM_FLAG_JUMP01    0x00040000L   // another example
#define  ITM_FLAG_JUMP02    0x00080000L   // another example

此外,现代C编译器也允许使用enum类型。

typedef enum { ITEM_FLAG_1 = 1, ITEM_FLAG_2 = 2, ITEM_FLAG_3 = 4 } ItemType;

ItemType AnItem = ITEM_FLAG_1;    // defining a variable of the type
ItemType AnItem2 = ITEM_FLAG_1 | ITEM_FLAG_2;  // defining a second variable

enum { ITEM_FLAG_1 = 1, ITEM_FLAG_2 = 2, ITEM_FLAG_3 = 4 } ItemType;

enum ItemType AnItem = ITEM_FLAG_1;    // defining a variable of the type
enum ItemType AnItem2 = ITEM_FLAG_1 | ITEM_FLAG_2;  // defining a second variable

关于如何使用这些的一些例子:

unsigned long ulExample = ITM_FLAG_EXAMPLE2;   // ulExample contains 0x00000002L
unsigned long ulExamplex = ITM_FLAG_EXAMPLE1 | ITM_FLAG_EXAMPLE2;  // ulExamplex contains 0x00000003L
unsigned long ulExampley = ulExamplex & ITM_FLAG_EXAMPLE2;  // ulExampley contains 0x00000002L

请参阅此博客帖子Intro to Truth Tables & Boolean Algebra,其中介绍了各种布尔代数操作以及此Wikipedia topic on Truth Tables

使用Bitflags的一些注意事项

使用bitflags和一些注意事项时可能会有一些问题。

  • 使用bitflag定义的常量为零,没有设置位,可能会导致问题
  • 与逻辑运算混合的按位运算可以是缺陷区域

通常使用定义值为零而非零的位标志会导致错误。大多数程序员都希望bitflag具有非零值,表示某种集合中的成员资格。定义为零的位标志是一种违背期望的行为,当用于按位操作时会导致意外的后果和行为。

当对位标志变量和逻辑运算符进行逐位运算时,通常更容易理解用于强制执行特定运算符优先级的括号,因为它不需要读者知道C operator precedence table

关于已发布的问题

提供库时,通常会有一个或多个包含库文件的包含文件,以便提供一些所需的项目。

  • 库提供的功能的函数原型
  • 库使用的类型的变量类型声明和定义
  • 控制库函数行为的操作数和标志的特殊常量

位标志是为功能界面提供选项的一种历史悠久的方式。位标志有一些很好的属性,使它们对C程序员很有吸引力。

  • 易于通过界面传输的紧凑表示
  • 自然适合布尔代数运算和集合操作
  • 与C位运算符自然匹配以执行这些操作

位标志可以节省空间,因为标志的名称可能很长且具有描述性,但可以编译为单个无符号值,例如unsigned short或unsigned long或unsigned char。

使用位标志的另一个好处是,当在表达式中的常量上使用按位运算符时,大多数现代编译器将评估按位运算作为编译表达式的一部分。因此,现代编译器将在O_RDONLY | O_WRONLY这样的表达式中使用多个按位运算符,并在编译源时执行按位或者将表达式替换为计算表达式的值。

在大多数计算机体系结构中,使用加载数据的寄存器执行按位运算符,然后执行按位运算。对于32位架构,使用32位变量来包含一组位自然地适合CPU寄存器,就像在64位架构中一样,使用32位或64位变量来包含一组自然适合寄存器的位。这种自然拟合允许对相同变量进行多个按位操作,而无需从CPU高速缓存或主存储器中进行提取。

C的按位运算符几乎总是具有CPU机器指令模拟,因此C位运算符具有几乎完全相似的CPU运算,因此编译器生成的结果机器代码非常有效。

通过使用unsigned long传递32个不同的标志或unsigned long long将64个不同的标志传递给函数,可以很容易地看到位标志的紧凑表示。通过使用数组偏移和位标记方法以及一组C处理器宏或一组函数来操作数组,可以使用unsigned char数组来传递更多标志。

可能的一些例子

按位运算符与用于集合的逻辑运算符非常相似,并且表示具有位标志的集合运行良好。如果你有一个包含操作数的集合,其中一些不应该与其他一些标志一起使用,那么使用按位运算符和掩码位可以很容易地看出是否指定了两个冲突的标志。

#define FLAG_1  0x00000001L     // a required flag if FLAG_2 is specified
#define FLAG_2  0x00001000L     // must not be specified with FLAG_3
#define FLAG_3  0x00002000L     // must not be specified with FLAG_2

int func (unsigned long ulFlags)
{
    // check if both FLAG_2 and FLAG_3 are specified. if so error
    // we do a bitwise And to isolate specific bits and then compare that
    // result with the bitwise Or of the bits for equality. this approach
    // makes sure that a check for both bits is turned on.
    if (ulFlags & (FLAG_2 | FLAG_3) == (FLAG_2 | FLAG_3)) return -1;

    // check to see if either FLAG_1 or FLAG_3 is set we can just do a
    // bitwise And against the two flags and if either one or both are set
    // then the result is non-zero.
    if (ulFlags & (FLAG_1 | FLAG_3)) {
        // do stuff if either or both FLAG_1 and/or FLAG_3 are set
    }

    // check that required option FLAG_1 is specified if FLAG_2 is specified.
    // we are using zero is boolean false and non-zero is boolean true in
    // the following. the ! is the logical Not operator so if FLAG_1 is
    // not set in ulFlags then the expression (ulFlags & FLAG_1) evaluates
    // to zero, False, and the Not operator inverts the False to True or
    // if FLAG_1 is set then (ulFlags & FLAG_1) evaluates to non-zero, True,
    // and the Not operator inverts the True to False. Both sides of the
    // logical And, &&, must evaluate True in order to trigger the return.
    if ((ulFlags & FLAG_2) && ! (ulFlags & FLAG_1)) return -2;
    // other stuff
}

例如,请参阅Using select() for non-blocking sockets以获得使用位标志的标准socket()接口的简要概述,以及使用类似于可以使用的集合操作抽象的示例的select()函数的简要概述用位标志完成。 socket函数允许通过使用fcntl()函数的位标志来设置各种特性,例如非阻塞,select()函数具有一组相关的函数/宏( FD_SET()FD_ZERO()等)提供了一个抽象,用于指示要在select()中监视哪些套接字句柄。我并不是要暗示select()套接字集是位图,尽管在原始的UNIX中,我相信它们是。然而,select()及其相关实用程序的抽象设计提供了一种可以用位标志实现的集合。

对包含按位标志的变量的评估也可以更快,更容易,更高效,更易读。例如,在使用一些已定义的标志调用的函数中:

#define ITEM_FLG_01  0x0001
#define ITEM_FLG_02  0x0002
#define ITEM_FLG_03  0x0101
#define ITEM_FLG_04  0x0108
#define ITEM_FLG_05  0x0200

#define ITEM_FLG_SPL1 (ITEM_FLG_01 | ITEM_FLG_02)

可能有switch()语句,例如:

switch (bitwiseflags & ITEM_FLG_SPL1) {
    case ITEM_FLG_01 | ITEM_FLG_02:
        // do things if both ITEM_FLG_01 and ITEM_FLG_02 are both set
        break;
    case ITEM_FLG_01:
        // do things if ITEM_FLG_01  is set
        break;
    case ITEM_FLG_02:
        // do things if ITEM_FLG_02 is set
        break;
    default:
        // none of the flags we are looking for are set so error
        return -1;
}

你可以使用与上面相同的定义来做一些简短的表达式,如下所示。

// test if bitwiseflags has bit ITEM_FLG_5 set and if so then call function
// doFunc().
(bitwiseflags & ITEM_FLG_5) == ITEM_FLG_5 && doFunc();

附录:一组真正大比特的技术

请参阅此答案,Creating bitflag variables with large amounts of flags or how to create large bit-width numbers了解大型集合的方法,大于32位或64位变量的大型集合,用于位标记。