簡體   English   中英

如何使用位域來節省內存?

[英]How can I use Bit-Fields to save memory?

這是關於ANSI-C(C90)。 這就是我所知道的:

  • 我可以直接告訴編譯器我想要一個特定變量的位數。
  • 如果我想要1位,其值可以為零或一。
  • 或者值為0,1,2,3的2位,依此類推......;

我熟悉語法。

我有關於位域的問題:

  • 我想定義一個SET結構。
  • 它可以有最多1024個元素(它可以有更少,但最多是1024個元素)。
  • 集合的域為1到1024.因此元素可以具有任何值1-1024。

我正在嘗試為SET創建一個結構,它必須對內存部分盡可能高效。

我試過了:

typedef struct set
{
    unsigned int var: 1;
} SET;
//now define an array of SETS
SET array_of_sets[MAX_SIZE]  //didn't define MAX_SIZE, but no more than 1024 elements in each set.

我知道這不高效; 也許它對我想要的東西甚至不好。 這就是為什么我在尋求幫助。

正如在廣泛的評論中所指出的那樣,使用位字段不是可行的方法。 對於包含值1..1024的集合,您只能使用128字節的存儲空間。 您需要將值N映射到位N-1(因此您可以使用位0..1023)。 您還需要確定所需的操作。 此代碼支持'create','destroy','insert','delete'和'in_set'。 它不支持迭代集合中的元素; 如果你想要它可以添加。

sets.h

#ifndef SETS_H_INCLUDED
#define SETS_H_INCLUDED

typedef struct Set Set;
enum { MAX_ELEMENTS = 1024 };

extern Set *create(void);
extern void destroy(Set *set);
extern void insert(Set *set, int value);
extern void delete(Set *set, int value);
extern int in_set(Set *set, int value);

#endif /* SETS_H_INCLUDED */

sets.c

#include "sets.h"
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

typedef unsigned long Bits;
#define BITS_C(n)  ((Bits)(n))
enum { ARRAY_SIZE = MAX_ELEMENTS / (sizeof(Bits) * CHAR_BIT) };

struct Set
{
     Bits set[ARRAY_SIZE];
};

Set *create(void)
{
    Set *set = malloc(sizeof(*set));
    if (set != 0)
        memset(set, 0, sizeof(*set));
    return set;
}

void destroy(Set *set)
{
    free(set);
}

void insert(Set *set, int value)
{
    assert(value >= 1 && value <= MAX_ELEMENTS);
    value--;  /* 0..1023 */
    int index = value / (sizeof(Bits) * CHAR_BIT);
    int bitnum = value % (sizeof(Bits) * CHAR_BIT);
    Bits mask = BITS_C(1) << bitnum;
    /* printf("I: %d (%d:%d:0x%.2lX)\n", value+1, index, bitnum, mask); */
    set->set[index] |= mask;
}

void delete(Set *set, int value)
{
    assert(value >= 1 && value <= MAX_ELEMENTS);
    value--;  /* 0..1023 */
    int index = value / (sizeof(Bits) * CHAR_BIT);
    int bitnum = value % (sizeof(Bits) * CHAR_BIT);
    Bits mask = BITS_C(1) << bitnum;
    /* printf("D: %d (%d:%d:0x%.2lX)\n", value+1, index, bitnum, mask); */
    set->set[index] &= ~mask;
}

/* C90 does not support <stdbool.h> */
int in_set(Set *set, int value)
{
    assert(value >= 1 && value <= MAX_ELEMENTS);
    value--;  /* 0..1023 */
    int index = value / (sizeof(Bits) * CHAR_BIT);
    int bitnum = value % (sizeof(Bits) * CHAR_BIT);
    Bits mask = BITS_C(1) << bitnum;
    /* printf("T: %d (%d:%d:0x%.2lX) = %d\n", value+1, index, bitnum, mask,
              (set->set[index] & mask) != 0); */
    return (set->set[index] & mask) != 0;
}

#include <stdio.h>

enum { NUMBERS_PER_LINE = 15 };

int main(void)
{
    Set *set = create();
    if (set != 0)
    {
        int i;
        int n = 0;
        for (i = 1; i <= MAX_ELEMENTS; i += 4)
             insert(set, i);
        for (i = 3; i <= MAX_ELEMENTS; i += 6)
             delete(set, i);

        for (i = 1; i <= MAX_ELEMENTS; i++)
        {
             if (in_set(set, i))
             {
                 printf(" %4d", i);
                 if (++n % NUMBERS_PER_LINE == 0)
                 {
                     putchar('\n');
                     n = 0;
                 }
             }
        }
        if (n % NUMBERS_PER_LINE != 0)
            putchar('\n');
        destroy(set);
    }
    return 0;
}

這些函數應該給出一個系統的前綴,比如set_ BITS_C宏基於在C99及更高版本的<stdint.h>中定義的INT64_C宏(以及其他相關宏),它也不是C90的一部分。

根據我之前的評論,這里有一個例子,說明如何將8個1位元素打包成一個char物理元素。 我只實現了獲取1位元素值的函數,我將函數設置為你(這很容易)。

注意:您可以輕松更改數組元素的類型(unsigned char)並嘗試可以容納更多位的類型(例如unsigned int)並測試它們在速度方面的表現是否更好。 您還可以修改代碼以使其處理大於一位的元素。

#include <stdio.h>
#include <limits.h>

unsigned int get_el(unsigned char* array, unsigned int index)
{
    unsigned int bits_per_arr_el = sizeof(unsigned char)*CHAR_BIT;
    unsigned int arr_index = index / bits_per_arr_el;
    unsigned int bit_offset = index % bits_per_arr_el;
    unsigned int bitmask = 1 << bit_offset;
    unsigned int retval;

    // printf("index=%u\n", index);
    // printf("bits_per_arr_el=%u\n", bits_per_arr_el);
    // printf("arr_index=%u\n", arr_index);
    // printf("bit_offset=%u\n", bit_offset);

    retval = array[arr_index] & bitmask ? 1 : 0; // can be simpler if only True/False is needed
    return(retval);
}

#define MAX_SIZE 10
unsigned char bitarray[MAX_SIZE];

int main()
{
    bitarray[1] = 3; // 00000011
    printf("array[7]=%u, array[8]=%u, array[9]=%u, array[10]=%u\n",
            get_el(bitarray, 7),
            get_el(bitarray, 8),
            get_el(bitarray, 9),
            get_el(bitarray,10));

    return 0;
}

輸出

array[7]=0, array[8]=1, array[9]=1, array[10]=0
typedef struct set
{
    unsigned short var:10; // uint var:1 will be padded to 32 bits
} SET;                     // ushort var:10 (which is max<=1024) padded to 16 bits

正如@Jonathan Leffler評論的那樣,使用數組(unsigned short [])並定義位掩碼

#define bitZer 0x00  //(unsigned)(0 == 0)? true:true;
#define bitOne 0x10  // so from (both inclusive)0-1023 = 1024
...                  // added for clarification  
#define bitTen 0x0A

調查每個元素的位。 http://www.catb.org/esr/structure-packing/詳細

1)這個問題的正確解決方案是使用比特陣列

該問題提供了具有結構的位字段的解決方案。 有兩種典型的方法來節省內存空間,相關的問題位,另一種是使用位序列 對於問題中的這種特定情況,更好的方法是使用Bit Array(演示如下)。

  • 如果是喜歡純粹的獨立位標志的情況下在這里,去為位序列
  • 如果存在一組相關位,例如IP地址或控制字定義,那么最好將它們與結構組合,即使用帶有Sturct的位字段

2)僅用於演示位陣列的示例代碼

#include<limits.h>
#define BITS_OF_INT (sizeof(int)*CHAR_BIT)  
void SetBit(int A[], int k)
     {
       //Set the bit at the k-th position
       A[k/BITS_OF_INT] |= 1 <<(k%BITS_OF_INT);
     } 
void ClearBit(int A[], int k)
     {
       //RESET the bit at the k-th position
       A[k/BITS_OF_INT] &= ~(1 <<(k%BITS_OF_INT)) ;
     }  
int TestBit(int A[], int k)
     {
       // Return TRUE if bit set    
       return ((A[k/BITS_OF_INT] & (1 <<(k%BITS_OF_INT)))!= 0) ;
     }

#define MAX_SIZE 1024
int main()
{
    int A[MAX_SIZE/BITS_OF_INT];
    int i;
    int pos = 100; // position

    for (i = 0; i < MAX_SIZE/BITS_OF_INT; i++)
        A[i] = 0; 

    SetBit(A, pos);
    if (TestBit(A, pos)){//do something}
    ClearBit(A, pos); 
}

3)此外,這個問題值得討論的一點是,

如何在“位數組”“帶結構的位字段 之間選擇合適的解決方案?

以下是有關此主題的一些參考資料。

要存儲0到1023之間的值(或者從1到1024,這實際上是相同的,只涉及加/減1),您需要至少10位。

這意味着對於32位(無符號)整數,您可以將3個值打包成30位,這會產生2位無用的填充。

例:

%define ELEMENTS 100

uint32_t myArray[ (ELEMENTS + 2) / 3 ];

void setValue(int n, int value) {
    uint32_t temp;
    uint32_t mask = (1 << 10) - 1;

    if(n >= ELEMENTS) return;
    value--;                        // Convert "1 to 1024" into "0 to 1023"
    temp = myArray[n / 3];
    mask = mask << (n % 3)*10;
    temp = (temp & ~mask) | (value << (n % 3)*10);
    myArray[n / 3] = temp; 
}

int getValue(int n) {
    uint32_t temp;
    uint32_t mask = (1 << 10) - 1;

    if(n >= ELEMENTS) return 0;
    temp = myArray[n / 3];
    temp >>= (n % 3)*10;
    return (temp & ~mask) + 1;
}

您可以使用位域來完成此操作,但獲取/設置單個值的代碼將最終使用分支(例如switch( n%3 ) ),這在實踐中會更慢。

刪除這2位填充將花費更多的復雜性和更多的開銷。 例如:

%define ELEMENTS 100

uint32_t myArray[ (ELEMENTS*10 + 31) / 32 ];

int getValue(int n) {
    uint64_t temp;
    uint64_t mask = (1 << 10) - 1;

    if(n >= ELEMENTS) return 0;

    temp = myArray[n*10/32 + 1];
    temp = (temp << 32) | myArray[n*10/32];

    temp >>= (n*10 % 32);

    return (temp & ~mask) + 1;
}

這不能用位域完成。 這是存儲范圍從1到1024的值數組的最節省空間的方法。

如果要存儲“布爾數組”或設置標志,它可能很有用。 例如,您可以一次初始化或比較多達64個值。

這些宏適用於unsigned char,short,int,long long ......但如果你只選擇一個類型,那么顯着簡化(這樣你就可以使用更安全的靜態內聯函數)

#define getbit(x,n) x[n/(sizeof(*x)*8)]  &  (typeof(*x))1 << (n&((sizeof(*x)*8)-1)) 
#define setbit(x,n) x[n/(sizeof(*x)*8)] |=  (typeof(*x))1 << (n&((sizeof(*x)*8)-1)) 
#define flpbit(x,n) x[n/(sizeof(*x)*8)] ^=  (typeof(*x))1 << (n&((sizeof(*x)*8)-1)) 
#define clrbit(x,n) x[n/(sizeof(*x)*8)] &= ~( (typeof(*x))1 << (n&((sizeof(*x)*8)-1)) ) 

初始化大量布爾值你需要做的是: char cbits[]={0,0xF,0,0xFF};

或者全部為零char cbits[4]={0};

或者一個int例子: int ibits[]={0xF0F0F0F0,~0};

// 1111000011110000111100001111000011111111111111111111111111111111

如果您只訪問1種類型的數組,最好將宏轉換為適當的函數,如:

static inline unsigned char getbit(unsigned char *x, unsigned n){ 
  return x[n>>3]  &  1 << (n&7); 
}
//etc... similar for other types and functions from macros above

您還可以通過“|”標記一起比較多個標志並使用“&”ed掩碼; 但是,當你超過本機類型時,它會變得更復雜一些

對於您的特定實例,您可以通過以下方式初始化為全零:

unsigned char flags[128]={0};

或全是1的:

uint64_t flags[128] = {~0,~0,~0,~0,~0,~0,~0,~0,~0,~0,~0,~0,~0,~0,~0,~0};

您甚至可以使用枚舉來命名您的標志

enum{
  WHITE, //0
  RED, //1
  BLUE, //2
  GREEN, //3
  ...
  BLACK //1023
}

if (getbit(flags,WHITE) && getbit(flags,RED) && getbit(flags,BLUE))
  printf("red, white and blue\n");

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM