Один байт типа bool. Почему?

в C++, почему bool требует один байт для хранения true или false, где для этого достаточно только одного бита, например 0 для false и 1 для true? (Почему Java также требует один байт?)

во-вторых, насколько безопаснее использовать следующие?

struct Bool {
    bool trueOrFalse : 1;
};

в-третьих, даже если это безопасно, действительно ли поможет вышеупомянутая полевая техника? Поскольку я слышал, что мы экономим место там, но все же компилятор генерирует код для доступа к ним больше и медленнее, чем код генерируется для доступа к примитивам.

5 ответов


Почему bool требует один байт для хранения true или false, где достаточно только одного бита

потому что каждый объект в C++ должен быть индивидуально адресуемым* (то есть вы должны иметь указатель на него). Вы не можете адресовать отдельный бит (по крайней мере, не на обычном оборудовании).

насколько безопаснее использовать следующее?

это "безопасно", но он не достигает многого.

действительно ли вышеупомянутая полевая техника поможет?

нет, по тем же причинам, что и выше ;)

но все же компилятор сгенерировал код для доступа к ним больше и медленнее, чем код, сгенерированный для доступа к примитивам.

Да, это верно. На большинстве платформ для этого требуется доступ к содержащему байту (или int или что-то еще), а затем выполнение операций битовых сдвигов и битовой маски для доступа к соответствующим немного.


если вы действительно беспокоитесь о памяти, вы можете использовать std::bitset в C++ или a BitSet в Java, которые упаковывают биты.


* за некоторыми исключениями.


использование одного бита намного медленнее и намного сложнее выделить. В C / C++ нет способа получить адрес одного бита, поэтому вы не сможете сделать &trueOrFalse как немного.

Java имеет битовый набор и EnumSet, которые оба используют растровые изображения. Если у вас очень небольшое число, это может не иметь большого значения. например, объекты должны быть выровнены по крайней мере байтом, а в HotSpot выровнены по 8 байтам (в C++ a new объект может быть выровнен от 8 до 16 байт) это означает, что сохранение нескольких бит может не экономьте место.

в Java, по крайней мере, биты не быстрее, если они не вписываются в кэш лучше.

public static void main(String... ignored) {
    BitSet bits = new BitSet(4000);
    byte[] bytes = new byte[4000];
    short[] shorts = new short[4000];
    int[] ints = new int[4000];

    for (int i = 0; i < 100; i++) {
        long bitTime = timeFlip(bits) + timeFlip(bits);
        long bytesTime = timeFlip(bytes) + timeFlip(bytes);
        long shortsTime = timeFlip(shorts) + timeFlip(shorts);
        long intsTime = timeFlip(ints) + timeFlip(ints);
        System.out.printf("Flip time bits %.1f ns, bytes %.1f, shorts %.1f, ints %.1f%n",
                bitTime / 2.0 / bits.size(), bytesTime / 2.0 / bytes.length,
                shortsTime / 2.0 / shorts.length, intsTime / 2.0 / ints.length);
    }
}

private static long timeFlip(BitSet bits) {
    long start = System.nanoTime();
    for (int i = 0, len = bits.size(); i < len; i++)
        bits.flip(i);
    return System.nanoTime() - start;
}

private static long timeFlip(short[] shorts) {
    long start = System.nanoTime();
    for (int i = 0, len = shorts.length; i < len; i++)
        shorts[i] ^= 1;
    return System.nanoTime() - start;
}

private static long timeFlip(byte[] bytes) {
    long start = System.nanoTime();
    for (int i = 0, len = bytes.length; i < len; i++)
        bytes[i] ^= 1;
    return System.nanoTime() - start;
}

private static long timeFlip(int[] ints) {
    long start = System.nanoTime();
    for (int i = 0, len = ints.length; i < len; i++)
        ints[i] ^= 1;
    return System.nanoTime() - start;
}

печать

Flip time bits 5.0 ns, bytes 0.6, shorts 0.6, ints 0.6

для размеров 40000 и 400K

Flip time bits 6.2 ns, bytes 0.7, shorts 0.8, ints 1.1

для 4M

Flip time bits 4.1 ns, bytes 0.5, shorts 1.0, ints 2.3

и 40M

Flip time bits 6.2 ns, bytes 0.7, shorts 1.1, ints 2.4

если вы хотите хранить только один бит информации, нет ничего более компактного, чем char, который является наименьшим адресуемым блоком памяти в C / C++. (В зависимости от реализации,bool может иметь тот же размер, что и char но это разрешено быть больше.)

A char гарантировано стандартом C для удержания хотя бы 8 битов, однако, оно может также состоять из больше. Точное число доступно через CHAR_BIT макро определено в limits.h (в C) или climits (C++). Сегодня наиболее распространено, что CHAR_BIT == 8 но вы не можете полагаться на него (см. здесь). Это гарантированно будет 8, однако, на POSIX совместимых системах и на окна.

хотя невозможно уменьшить объем памяти для одного флага, конечно, можно объединить несколько флагов. Кроме того, все битовые операции вручную, есть некоторые альтернативы:

  • если вы знаете количество битов во время компиляции
    1. bitfields (как в вашем вопросе). Но будьте осторожны, порядок полей не гарантируется, что может привести к проблемам переносимости.
    2. std::bitset
  • если вы знаете размер только во время выполнения
    1. boost::dynamic_bitset
    2. Если вам приходится иметь дело с большими bitvectors, взгляните на the библиотека BitMagic. Он поддерживает сжатие и сильно настроен.

как уже указывали другие, сохранение нескольких битов не всегда является хорошей идеей. Возможные недостатки:

  1. менее читаемый код
  2. снижается скорость выполнения из-за дополнительного кода добыча.
  3. по той же причине увеличивается размер кода, что может перевешивать экономию данных потребление.
  4. скрытые проблемы синхронизации в многопоточных программах. Например, переключение двух разных битов двумя разными потоками может привести к состоянию гонки. Напротив, для двух потоков всегда безопасно изменять два разных объекта примитивных типов (например,char).

как правило, это имеет смысл, когда вы имеете дело с огромными данными, потому что тогда вы выиграете от меньшего давления на память и кэш.


почему бы вам просто не сохранить состояние в байт? На самом деле не протестировали ниже, но это должно дать вам представление. Вы даже можете использовать короткий или int для 16 или 32 состояний. Я считаю, что у меня есть рабочий пример JAVA. Я отправлю это, когда найду.

__int8 state = 0x0;

bool getState(int bit)
{
  return (state & (1 << bit)) != 0x0;
}

void setAllOnline(bool online)
{
  state = -online;
}

void reverseState(int bit)
{
   state ^= (1 << bit);
}

хорошо, вот версия JAVA. С тех пор я сохранил его до значения Int. Если я правильно помню, даже использование байта будет использовать 4 байта в любом случае. И это, очевидно, не используется в качестве матрица.

public class State
{
    private int STATE;

    public State() { 
        STATE = 0x0;
    }

    public State(int previous) { 
        STATE = previous;
    }


   /*
    * @Usage - Used along side the #setMultiple(int, boolean);
    * @Returns the value of a single bit.
    */
    public static int valueOf(int bit)
    {
        return 1 << bit;
    }


   /*
    * @Usage - Used along side the #setMultiple(int, boolean);
    * @Returns the value of an array of bits.
    */
    public static int valueOf(int... bits)
    {
        int value = 0x0;
        for (int bit : bits)
            value |= (1 << bit);
        return value;
    }


   /*
    * @Returns the value currently stored or the values of all 32 bits.
    */
    public int getValue() 
    {
        return STATE;
    }

   /*
    * @Usage - Turns all bits online or offline.
    * @Return - <TRUE> if all states are online. Otherwise <FALSE>.
    */
    public boolean setAll(boolean online)
    {
        STATE = online ? -1 : 0;
        return online;
    }


   /*
    * @Usage - sets multiple bits at once to a specific state.
    * @Warning - DO NOT SET BITS TO THIS! Use setMultiple(State.valueOf(#), boolean);
    * @Return - <TRUE> if states were set to online. Otherwise <FALSE>.
    */
    public boolean setMultiple(int value, boolean online)
    {
        STATE |= value;
        if (!online)
            STATE ^= value;
        return online;
    }


   /*
    * @Usage - sets a single bit to a specific state.
    * @Return - <TRUE> if this bit was set to online. Otherwise <FALSE>.
    */
    public boolean set(int bit, boolean online)
    {
        STATE |= (1 << bit);
        if(!online)
            STATE ^= (1 << bit);
        return online;
    }


   /*
    * @return = the new current state of this bit.
    * @Usage = Good for situations that are reversed.
    */
    public boolean reverse(int bit)
    {
        return (STATE ^= (1 << bit)) == (1 << bit);
    }


   /*
    * @return = <TRUE> if this bit is online. Otherwise <FALSE>.
    */
    public boolean online(int bit)
    {
        int value = 1 << bit;
        return (STATE & value) == value;        
    }


   /*
    * @return = a String contains full debug information.
    */
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("TOTAL VALUE: ");
        sb.append(STATE);
        for (int i = 0; i < 0x20; i++)
        {
            sb.append("\nState(");
            sb.append(i);
            sb.append("): ");
            sb.append(online(i));
            sb.append(", ValueOf: ");
            sb.append(State.valueOf(i));
        }
        return sb.toString();
    }
}

также я должен отметить, что вы действительно не должны использовать специальный класс для этого, но просто иметь переменную, хранящуюся в классе, который, скорее всего, будет ее использовать. Если вы планируете иметь 100 или даже 1000 логических значений, рассмотрите массив байтов.

Е. Г. приведенный ниже пример.

boolean[] states = new boolean[4096];

можно преобразовать в ниже.

int[] states = new int[128];

теперь вам, вероятно, интересно, как вы получите доступ к индексу 4095 из 128 массива. Так что это происходит, если мы упростим это. 4095 будет сдвинут на 5 бит вправо, что технически то же самое, что разделить на 32. Итак, 4095 / 32 = округлено вниз (127). Итак, мы находимся в индексе 127 массива. Затем мы выполняем 4095 & 31, который приведет его к значению между 0 и 31. Это будет работать только с степени двойки минус 1. Е. Г. 0,1,3,7,15,31,63,127,255,511,1023 и т. д...

теперь мы можем получить доступ к биту в этой позиции. Как вы можете видеть, это очень компактный и beats, имеющий 4096 логических значений в файле :) это также обеспечит гораздо более быстрое чтение/запись в двоичный файл. Я понятия не имею, что это за битовый набор,но он выглядит как полный мусор,и поскольку byte,short, int, long уже находятся в своих битовых формах технически, вы можете использовать их как есть. Затем создание сложного класса для доступа к отдельным битам из памяти, что я мог понять из прочтения нескольких постов.

boolean getState(int index)
{
    return (states[index >> 5] & 1 << (index & 0x1F)) != 0x0;
}

далее информация...

в основном, если выше было немного запутанным, вот упрощенная версия того, что происходит.

типа "байт"," short","int","долго" все типы данных, которые имеют различные диапазоны.

вы можете просмотреть эту ссылку:http://msdn.microsoft.com/en-us/library/s3f49ktz (v=против 80).aspx

для просмотра диапазонов данных каждый.

таким образом, байт равен 8 битам. Таким образом, int, который составляет 4 байта, будет 32 бит.

теперь нет простого способа выполнить некоторое значение для N власти. Однако благодаря бит-сдвигу мы можем имитировать его несколько. При выполнении 1

теперь мы знаем, что int будет иметь 32 бита, поэтому можно использовать каждый бит, чтобы мы могли просто просто индексируют их.

чтобы все было просто, подумайте о операторе " & " как о способе проверить, содержит ли значение биты другого значения. Допустим, у нас было значение 31. Добраться до 31. мы должны добавить следующие биты от 0 до 4. Которые 1,2,4,8 и 16. В сумме получается 31. Теперь, когда мы выполняем 31 & 16, это вернет 16, потому что бит 4, который равен 2^4 = 16. Находится в этом значении. Теперь предположим, что мы выполнили 31 & 20, который проверяет, расположены ли биты 2 и 4 в этом значении. Это вернет 20, так как оба бита 2 и 4 расположены здесь 2^2 = 4 + 2^4 = 16 = 20. Теперь предположим, что мы сделали 31 и 48. Это проверка битов 4 и 5. Ну, у нас нет бит 5 в 31. Так что это вернет только 16. Он не вернет 0. Поэтому при выполнении нескольких проверок вы должны убедиться, что он физически равен этому значению. Вместо проверки, равен ли он 0.

ниже будет проверяться, находится ли отдельный бит в 0 или 1. 0 означает ложь, а 1 - истинный.

bool getState(int bit)
{
    return (state & (1 << bit)) != 0x0;
}

ниже есть пример проверки двух значений, если они содержат эти биты. Подумайте об этом, как каждый бит представлен как 2^бит, поэтому, когда мы делаем

я быстро пройдусь по некоторым операторам. Мы только недавно немного объяснили оператор"&". Теперь оператор"|".

при выполнении следующих

int value = 31;
value |= 16;
value |= 16;
value |= 16;
value |= 16;

значение по-прежнему будет 31. Это потому, что бит 4 или 2^4=16 уже включен или установлен в 1. Так выполнение " | "возвращает это значение с включенным битом. Если он уже включен, никаких изменений не сделано. Мы используем "|=", чтобы фактически установить переменную в это возвращаемое значение.

вместо того, чтобы делать - > " value = value / 16;". Мы просто делаем " value / = 16;".

теперь давайте посмотрим немного дальше в том, как "&" и "| " можно использовать.

/*
 * This contains bits 0,1,2,3,4,8,9 turned on.
 */
const int CHECK = 1 | 2 | 4 | 8 | 16 | 256 | 512;

/*
 * This is some value were we add bits 0 through 9, but we skip 0 and 8.
 */
int value = 2 | 4 | 8 | 16 | 32 | 64 | 128 | 512;

поэтому, когда мы выполняем приведенный ниже код.

int return_code = value & CHECK;

возвращение код будет 2 + 4 + 8 + 16 + 512 = 542

Итак, мы проверяли 799, но мы получили 542 это потому, что биты o и 8 отключены, мы равны 256 + 1 = 257 и 799 - 257 = 542.

выше это отличный отличный способ проверить, если, скажем, мы делали видеоигру и хотели проверить, если так и так кнопки были нажаты, если любой из них были нажаты. Мы могли бы просто проверить каждый из этих битов одной проверкой, и это было бы во много раз эффективнее, чем выполнение Логическая проверка каждого отдельного состояния.

теперь предположим, что у нас есть логическое значение, которое всегда обращено вспять.

обычно вы делаете что-то вроде

bool state = false;

state = !state;

Ну, это можно сделать и с битами, используя "^" оператором.

так же, как мы выполнили "1

void reverseState(int bit)
{
   state ^= (1 << bit);
}

вы даже можете вернуть текущее состояние. Если вы хотите, чтобы он вернул предыдущее состояние, просто замените"!=" к."==" Таким образом, это выполняет разворот, а затем проверяет текущее состояние.

bool reverseAndGet(int bit)
{
    return ((state ^= (1 << bit)) & (1 << bit)) != 0x0;
}

хранение нескольких не одиночных битовых значений aka bool в int также может быть сделано. Предположим, мы обычно записываем нашу координатную позицию, как под.

int posX = 0;
int posY = 0;
int posZ = 0;

теперь допустим, эти никогда не Вэнь прошло 1023. Таким образом, от 0 до 1023 было максимальное расстояние на всех из них. Я выбираю 1023 для других целей, как упоминалось ранее, вы можете манипулировать переменной " & " как способом заставить значение между 0 и 2^N - 1 значениями. Допустим, ваш диапазон от 0 до 1023. Мы можем выполнить "value & 1023", и это всегда будет значение между 0 и 1023 без каких-либо проверок параметров индекса. Имейте в виду, как уже упоминалось это только работает с силами два минус один. 2^10 = 1024 - 1 = 1023.

например. нет больше if (value >= 0 & & value

Итак, 2^10 = 1024, что требует 10 бит, чтобы удерживать число между 0 и 1023.

Итак, 10x3 = 30, что все еще меньше или равно 32. Достаточно для хранения всех этих значений в int.

таким образом, мы можем выполнить следующее. Чтобы посмотреть, сколько бит мы использовали. Мы делаем 0 + 10 + 20. Почему я поставил 0 там показать вам визуально, что 2^0 = 1 так # * 1 = #. Причина, по которой нам нужен y

int position = (x << 0) | (y << 10) | (z << 20);

это делает сравнение быстро.

теперь мы можем сделать

return this.position == position;

apposed to

return this.x == x && this.y == y && this.z == z;

теперь, что, если бы мы хотели фактические позиции каждого?

для x мы просто сделайте следующее.

int getX()
{ 
   return position & 1023;
}

тогда для y нам нужно выполнить сдвиг левого бита, а затем и его.

int getY()
{ 
   return (position >> 10) & 1023;
}

как вы можете догадаться, Z совпадает с Y, но вместо 10 мы используем 20.

int getZ()
{ 
   return (position >> 20) & 1023;
}

Я надеюсь, что тот, кто просмотрит это, найдет, что это стоит информации :).


Если вы действительно хотите использовать 1 бит, вы можете использовать char для хранения 8 логических значений и bitshift для получения значения нужного. Я сомневаюсь, что это будет быстрее, и это дает вам много головной боли работает, но технически это возможно.

с другой стороны, такая попытка может оказаться полезной для систем, которые не имеют много памяти для переменных, но имеют больше вычислительной мощности, чем вам нужно. Я очень сомневаюсь, что ты когда-нибудь ... но мне это нужно.